n8n/.github/workflows/util-claude-task.yml
n8n-assistant[bot] 880e40cde6
ci: Clean up Template Injection surface in Actions (backport to 1.x) (#29367)
Co-authored-by: Matsu <huhta.matias@gmail.com>
2026-04-28 09:10:37 +00:00

197 lines
6.6 KiB
YAML

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
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
echo "Git author configured as: ${{ github.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
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|${{ secrets.MCP_LINEAR_API_KEY }}|g" /tmp/mcp-config.json
sed -i "s|MCP_NOTION_TOKEN_PLACEHOLDER|${{ secrets.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
settings: |
{
"permissions": {
"allow": [
"Bash",
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"WebFetch",
"WebSearch",
"TodoWrite",
"Skill",
"Task",
"mcp__linear__*",
"mcp__notion__*"
]
}
}
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