name: 'Util: Claude Task Runner' on: workflow_dispatch: inputs: task: description: 'Task description - what should Claude do?' required: true type: string resumeUrl: description: 'Optional callback URL to call with the result when the workflow completes' required: false type: string model: description: 'Claude model to use (e.g., claude-opus-4-6, claude-sonnet-4-5, claude-haiku-4-5)' required: false type: string default: 'claude-sonnet-4-5-20250929' use_raw_prompt: description: 'Pass the task input directly as the prompt without wrapping it with guidelines and instructions' required: false type: boolean default: false max_turns: description: 'Maximum conversation turns before Claude stops gracefully' required: false type: number default: 50 suppress_output: description: 'Suppress console output and job summary' required: false type: boolean default: true ref: description: 'Git ref (branch/tag/SHA) to check out and work on' required: false type: string default: 'master' jobs: run-claude-task: runs-on: blacksmith-4vcpu-ubuntu-2204 timeout-minutes: 60 permissions: contents: write pull-requests: write issues: write steps: - name: Generate GitHub App Token id: generate_token uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }} private-key: ${{ secrets.N8N_ASSISTANT_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.ref }} fetch-depth: 1 token: ${{ steps.generate_token.outputs.token }} - name: Configure git remote with token run: git remote set-url origin "https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/${{ github.repository }}.git" - name: Setup Node.js uses: ./.github/actions/setup-nodejs with: build-command: '' - name: Create working branch run: | BRANCH_NAME="claude/task-${{ github.run_id }}-${{ github.run_attempt }}" echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV" git checkout -b "$BRANCH_NAME" - name: Configure git author env: GH_ACTOR: ${{ github.actor }} GH_ACTOR_ID: ${{ github.actor_id }} run: | git config user.name "$GH_ACTOR" git config user.email "$GH_ACTOR_ID+$GH_ACTOR@users.noreply.github.com" echo "Git author configured as: $GH_ACTOR" - name: Prepare Claude prompt env: INPUT_TASK: ${{ inputs.task }} USE_RAW_PROMPT: ${{ inputs.use_raw_prompt }} run: node .github/scripts/claude-task/prepare-claude-prompt.mjs - name: Create MCP config env: MCP_LINEAR_API_KEY: ${{ secrets.MCP_LINEAR_API_KEY }} MCP_NOTION_TOKEN: ${{ secrets.MCP_NOTION_TOKEN }} run: | cat > /tmp/mcp-config.json << 'MCPEOF' { "mcpServers": { "linear": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.linear.app/sse", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer MCP_LINEAR_API_KEY_PLACEHOLDER" } }, "notion": { "command": "npx", "args": ["-y", "@notionhq/notion-mcp-server"], "env": { "OPENAPI_MCP_HEADERS": "{\"Authorization\":\"Bearer MCP_NOTION_TOKEN_PLACEHOLDER\",\"Notion-Version\":\"2025-02-19\"}" } } } } MCPEOF sed -i "s|MCP_LINEAR_API_KEY_PLACEHOLDER|${MCP_LINEAR_API_KEY}|g" /tmp/mcp-config.json sed -i "s|MCP_NOTION_TOKEN_PLACEHOLDER|${MCP_NOTION_TOKEN}|g" /tmp/mcp-config.json - name: Run Claude id: claude continue-on-error: true uses: anthropics/claude-code-action@d668cc452deb931732f24c6b1bbf24d97bc0c586 # v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ steps.generate_token.outputs.token }} prompt: ${{ env.CLAUDE_PROMPT }} show_full_output: ${{ inputs.suppress_output != true }} display_report: false plugin_marketplaces: './.claude/plugins/n8n' plugins: 'n8n@n8n' settings: | { "permissions": { "allow": [ "Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch", "TodoWrite", "Skill", "Task", "mcp__linear__*", "mcp__notion__*" ] }, "extraKnownMarketplaces": { "n8n": { "source": { "source": "directory", "path": "./.claude/plugins/n8n" } } }, "enabledPlugins": { "n8n@n8n": true } } claude_args: | --model ${{ inputs.model }} --max-turns ${{ inputs.max_turns }} --mcp-config /tmp/mcp-config.json - name: Push branch if: always() env: REF: ${{ inputs.ref }} run: | if ! git diff --quiet "$REF" 2>/dev/null; then git push -u origin "$BRANCH_NAME" echo "::notice::Changes pushed to branch $BRANCH_NAME" else echo "::notice::No changes to push." fi - name: Summary if: always() env: CLAUDE_OUTCOME: ${{ steps.claude.outcome }} CLAUDE_SESSION_ID: ${{ steps.claude.outputs.session_id }} run: | { echo "## Claude Task Runner" echo "**Branch:** ${BRANCH_NAME:-N/A}" echo "**Claude outcome:** $CLAUDE_OUTCOME" if [ -n "$CLAUDE_SESSION_ID" ]; then echo "**Session ID:** \`$CLAUDE_SESSION_ID\`" fi } >> "$GITHUB_STEP_SUMMARY" - name: Call resume url if: always() && inputs.resumeUrl != '' env: RESUME_URL: ${{ inputs.resumeUrl }} EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} CLAUDE_OUTCOME: ${{ steps.claude.outcome }} CLAUDE_SESSION_ID: ${{ steps.claude.outputs.session_id }} run: node .github/scripts/claude-task/resume-callback.mjs - name: Check final status if: always() run: | if [ "${{ steps.claude.outcome }}" = "failure" ]; then echo "::error::Claude task failed. Check the 'Run Claude' step logs for details." exit 1 fi