mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 01:07:04 +02:00
194 lines
7.2 KiB
YAML
194 lines
7.2 KiB
YAML
name: 'Util: Claude Task Runner'
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
task:
|
|
description: 'Task description - what should Claude do?'
|
|
required: true
|
|
type: string
|
|
user_token:
|
|
description: 'Your GitHub PAT (required for PR authorship - you cannot approve PRs you author)'
|
|
required: true
|
|
type: string
|
|
resumeUrl:
|
|
description: 'Optional callback URL to call with the result when the workflow completes'
|
|
required: false
|
|
type: string
|
|
createPr:
|
|
description: 'Create a pull request with the changes'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
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'
|
|
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: Mask user token
|
|
run: echo "::add-mask::${{ inputs.user_token }}"
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
with:
|
|
token: ${{ inputs.user_token }}
|
|
ref: ${{ inputs.ref }}
|
|
fetch-depth: 1
|
|
|
|
- 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_TOKEN: ${{ inputs.user_token }}
|
|
run: |
|
|
# Set git author from the authenticated user (token owner)
|
|
USER_DATA=$(gh api user)
|
|
USER_NAME=$(echo "$USER_DATA" | jq -r '.name // .login')
|
|
USER_LOGIN=$(echo "$USER_DATA" | jq -r '.login')
|
|
USER_ID=$(echo "$USER_DATA" | jq -r '.id')
|
|
USER_EMAIL="${USER_ID}+${USER_LOGIN}@users.noreply.github.com"
|
|
git config user.name "$USER_NAME"
|
|
git config user.email "$USER_EMAIL"
|
|
echo "Git author configured as: $USER_NAME <$USER_EMAIL>"
|
|
|
|
- name: Prepare Claude prompt
|
|
env:
|
|
INPUT_TASK: ${{ inputs.task }}
|
|
run: |
|
|
# Build the prompt with task and pointer to templates
|
|
{
|
|
echo 'CLAUDE_PROMPT<<EOF'
|
|
echo "# Task"
|
|
echo "$INPUT_TASK"
|
|
echo ""
|
|
echo "# Guidelines"
|
|
echo "Check .github/claude-templates/ for relevant guides before starting."
|
|
echo "Read any templates that match your task type (e.g., security-fix.md for CVE fixes)."
|
|
echo ""
|
|
echo "# Instructions"
|
|
echo "1. Read relevant templates from .github/claude-templates/ first"
|
|
echo "2. Complete the task described above"
|
|
echo "3. Follow the guidelines from the templates"
|
|
echo "4. Make commits as you work - the last commit message will be used as the PR title"
|
|
echo "5. IMPORTANT: End every commit message with: Co-authored-by: Claude <noreply@anthropic.com>"
|
|
echo "6. Ensure code passes linting and type checks before finishing"
|
|
echo ""
|
|
echo "# Token Optimization"
|
|
echo "When running lint/typecheck, suppress verbose output:"
|
|
echo " pnpm lint 2>&1 | tail -30"
|
|
echo " pnpm typecheck 2>&1 | tail -30"
|
|
echo 'EOF'
|
|
} >> "$GITHUB_ENV"
|
|
|
|
- name: Run Claude
|
|
id: claude
|
|
uses: anthropics/claude-code-action@1b8ee3b94104046d71fde52ec3557651ad8c0d71 # v1
|
|
with:
|
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
github_token: ${{ inputs.user_token }}
|
|
prompt: ${{ env.CLAUDE_PROMPT }}
|
|
claude_args: |
|
|
--model ${{ inputs.model }}
|
|
--allowedTools Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite
|
|
|
|
- name: Extract PR title
|
|
run: |
|
|
# Use the last commit message as PR title
|
|
PR_TITLE=$(git log -1 --format='%s' 2>/dev/null | head -1)
|
|
# Strip Co-authored-by suffix if present
|
|
PR_TITLE="${PR_TITLE%%[Cc]o-[Aa]uthored-[Bb]y:*}"
|
|
PR_TITLE="${PR_TITLE%% }"
|
|
if [ -z "$PR_TITLE" ]; then
|
|
PR_TITLE="chore: Claude automated task (run ${{ github.run_id }})"
|
|
fi
|
|
echo "PR_TITLE=$PR_TITLE" >> "$GITHUB_ENV"
|
|
echo "Extracted PR title: $PR_TITLE"
|
|
|
|
- name: Push branch and create PR
|
|
env:
|
|
GH_TOKEN: ${{ inputs.user_token }}
|
|
INPUT_TASK: ${{ inputs.task }}
|
|
TRIGGERED_BY: ${{ github.actor }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
run: |
|
|
if ! git diff --quiet "${{ inputs.ref }}"; then
|
|
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
|
git push -u origin "$BRANCH_NAME"
|
|
|
|
if [ "${{ inputs.createPr }}" = "true" ]; then
|
|
{
|
|
echo "## Summary"
|
|
echo "Automated task completed by Claude."
|
|
echo ""
|
|
echo "### Task Description"
|
|
echo "$INPUT_TASK"
|
|
echo ""
|
|
echo "### Generated by"
|
|
echo "[Workflow Run #${{ github.run_id }}]($RUN_URL)"
|
|
echo ""
|
|
echo "---"
|
|
echo "*Triggered by @$TRIGGERED_BY via the Claude Task Runner workflow.*"
|
|
} > /tmp/pr-body.md
|
|
|
|
gh pr create --title "$PR_TITLE" --body-file /tmp/pr-body.md --base "${{ inputs.ref }}"
|
|
else
|
|
echo "::notice::PR creation is disabled. Changes pushed to branch but no PR created."
|
|
fi
|
|
else
|
|
echo "::notice::No changes made by Claude. Skipping PR creation."
|
|
fi
|
|
|
|
- name: Summary
|
|
env:
|
|
INPUT_TASK: ${{ inputs.task }}
|
|
run: |
|
|
{
|
|
echo "## Claude Task Runner Summary"
|
|
echo ""
|
|
echo "**Task:** $INPUT_TASK"
|
|
echo "**Branch:** $BRANCH_NAME"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Call resume url
|
|
if: inputs.resumeUrl != ''
|
|
env:
|
|
EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }}
|
|
run: |
|
|
# Extract only the result message (last element) from Claude's output
|
|
if [ -f "$EXECUTION_FILE" ]; then
|
|
jq --arg branch "$BRANCH_NAME" \
|
|
'{success: true, branch: $branch, result: .[-1]}' "$EXECUTION_FILE" \
|
|
| curl -X POST "${{ inputs.resumeUrl }}" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary @-
|
|
else
|
|
curl -X POST "${{ inputs.resumeUrl }}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"success\": true, \"branch\": \"$BRANCH_NAME\", \"result\": null}"
|
|
fi
|