Minify Claude dispatcher workflow

This commit is contained in:
Glenn Jocher
2026-07-24 12:45:19 +02:00
parent 5aa722d756
commit 4ce44caa74
+19 -77
View File
@@ -1,10 +1,5 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
# Central Claude dispatcher: trigger via API or UI with a target repo + a free-text task.
# Claude has the gh CLI authenticated, so a prompt can just reference an issue/PR URL and
# Claude pulls it for context; it implements the change on a new branch and the workflow
# opens a PR on the target repo.
name: Claude Dispatcher
on:
@@ -14,79 +9,36 @@ on:
description: "Target repo (owner/name)"
required: true
prompt:
description: "What Claude should implement (may reference an issue/PR URL)"
description: "Task to implement"
required: true
model:
description: "Claude model"
required: false
default: claude-sonnet-5
max_turns:
description: "Max agent turns before Claude stops (raise for complex tasks)"
required: false
default: "100"
permissions:
contents: read # Cross-repo writes use the PAT below, not the default token
contents: read
jobs:
dispatch:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout target repo
uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: ${{ inputs.repo }}
token: ${{ secrets._GITHUB_TOKEN }} # PAT with org read/write; also given to Claude below for gh access
fetch-depth: 0
token: ${{ secrets._GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- run: npm install -g @anthropic-ai/claude-code
- name: Run Claude
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # subscription auth (see plan §2)
GH_TOKEN: ${{ secrets._GITHUB_TOKEN }} # gh CLI auth so Claude can read issues/PRs/comments during the run
PROMPT: ${{ inputs.prompt }} # via env to avoid shell injection
MODEL: ${{ inputs.model }}
MAX_TURNS: ${{ inputs.max_turns }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
GH_TOKEN: ${{ secrets._GITHUB_TOKEN }}
PROMPT: ${{ inputs.prompt }}
run: |
unset ANTHROPIC_API_KEY # force subscription auth; an API key would override it and bill per-token
# Standing preamble tells Claude it has gh and that the workflow — not Claude — owns git/PR.
unset ANTHROPIC_API_KEY
TASK="$(printf '%s\n' \
"You are running in a CI job. The gh CLI is authenticated with a GitHub token that has org read/write access." \
"Use gh freely to gather context: read any referenced issue, its comments, linked PRs, and related code/docs." \
"Implement the requested change by editing files in this checked-out repository only." \
"Do NOT create branches, commit, push, or open a pull request — the workflow does that after you finish." \
"If you are resolving a GitHub issue, end your final summary with a line 'Closes #<number>' so the PR links it." \
"" \
"TASK:" \
"Use gh to gather any referenced issue or PR context." \
"Implement the task in this repository, but do not commit, push, or open a PR." \
"$PROMPT")"
# Don't let a non-zero Claude exit (e.g. hitting --max-turns) abort the job under `bash -e`
# and silently discard partial work — capture the code and surface the JSON instead.
set +e
claude -p "$TASK" \
--model "$MODEL" \
--allowedTools "Read,Edit,Write,Glob,Grep,Bash" \
--max-turns "${MAX_TURNS:-100}" \
--output-format json > "$RUNNER_TEMP/result.json" # do NOT add --bare (it ignores the OAuth token)
CODE=$?
set -e
# Surface what happened regardless of exit code (the JSON is otherwise lost with the runner).
{
echo "### Claude run (exit $CODE)"
echo '```'
jq -r '"is_error=\(.is_error) subtype=\(.subtype // "n/a") turns=\(.num_turns // "n/a") cost_usd=\(.total_cost_usd // "n/a")"' "$RUNNER_TEMP/result.json" 2>/dev/null \
|| echo "no parseable JSON — Claude likely failed to start (auth?)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
if [ "$CODE" -ne 0 ]; then
echo "⚠️ Claude exited $CODE (commonly \`error_max_turns\`). Continuing so any partial changes still open a PR." >> "$GITHUB_STEP_SUMMARY"
fi
claude -p "$TASK" --allowedTools "Read,Edit,Write,Glob,Grep,Bash"
- name: Open pull request
env:
@@ -94,23 +46,13 @@ jobs:
REPO: ${{ inputs.repo }}
PROMPT: ${{ inputs.prompt }}
run: |
if git diff --quiet && git diff --cached --quiet; then
echo "⚠️ Claude produced no changes — nothing to open a PR for." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
BASE="$(git rev-parse --abbrev-ref HEAD)" # target's default branch
# Branch slug: lowercased prompt, non-alphanumerics → "-", trimmed to 40 chars.
SLUG="$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed -E 's/-+/-/g; s/^-+//; s/-+$//' | cut -c1-40 | sed -E 's/-+$//')"
[ -z "$SLUG" ] && SLUG="task" # fallback if the prompt has no alphanumerics
BRANCH="ultralyticsAI/${SLUG}-${GITHUB_RUN_ID}"
TITLE="$(printf '%s' "$PROMPT" | head -c 100 | tr '\n' ' ')" # single-line, capped for a clean PR/commit title
git config --global user.name "UltralyticsAssistant"
git config --global user.email "[email protected]"
git checkout -b "$BRANCH"
git status --porcelain | grep -q . || exit 0
BRANCH="claude/${GITHUB_RUN_ID}"
TITLE="$(printf '%s' "$PROMPT" | tr '\n' ' ' | cut -c1-100)"
git config user.name "UltralyticsAssistant"
git config user.email "[email protected]"
git switch -c "$BRANCH"
git add -A
git commit -m "Claude: $TITLE"
git push origin "$BRANCH"
BODY="$(jq -r '.result // "Automated change via the Claude dispatcher."' "$RUNNER_TEMP/result.json")"
URL="$(gh pr create --repo "$REPO" --base "$BASE" --head "$BRANCH" \
--title "Claude: $TITLE" --body "$BODY")"
echo "✅ Opened $URL" >> "$GITHUB_STEP_SUMMARY"
gh pr create --repo "$REPO" --head "$BRANCH" --title "Claude: $TITLE" --body "$PROMPT"