Summary
In tag mode, setupBranch() runs git fetch before configureGitAuth(). On a private repository checked out with actions/checkout's persist-credentials: false, there is no credential for that fetch, so the action dies in setup — after it has already posted its "Claude Code is working…" tracking comment, which then never resolves.
The result is a permanently pending comment on the PR and no review, for every mention.
Repro
Workflow (private repo):
on:
issue_comment: { types: [created] }
jobs:
claude:
runs-on: ubuntu-latest
permissions: { contents: read, pull-requests: write, issues: write }
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false # <- the trigger
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# no `prompt:` -> tag mode
Comment @claude review this on an open same-repo PR.
Action failed with error: Command failed: git fetch origin --depth=20 <branch>
Cause (v1)
src/modes/detector.ts — a comment event with no prompt input and a matched trigger phrase resolves to "tag".
src/modes/tag/index.ts — posts the tracking comment, then calls setupBranch(...) at line 67; configureGitAuth(...) is called only afterwards, at lines 84 / 97.
src/github/operations/branch.ts:212 — for an open, same-repo PR: execGit(["fetch", "origin", ...depthArgs, branchName]), unconditionally. There is no check for whether the requested ref is already present in the working copy.
So the ordering is: tracking comment → unauthenticated fetch → (never reached) git auth.
Agent mode is unaffected: src/modes/agent/index.ts never calls setupBranch, and reads branch state from GITHUB_HEAD_REF / GITHUB_REF_NAME instead. That is why a workflow_dispatch-triggered workflow using the identical checkout config works fine — it takes a different code path, not a safer configuration.
Why persist-credentials: false is not simply user error
actions/checkout writes the job token into .git/config as an http.extraheader. A workflow that runs an agent with file-read tools over the checkout is handing that agent its own token — so persist-credentials: false is the correct hardening for exactly this action's use case, not an exotic choice. Right now that hardening is incompatible with tag mode.
Pinning actions/checkout's ref: to the PR head does not work around it: setupBranch fetches by branch name regardless of what is already checked out. (Verified — that was our first attempted fix.)
Suggested fixes, roughly in order of preference
- Configure git auth before
setupBranch(), so the fetch it performs is authenticated. Smallest change, fixes the ordering bug directly.
- Skip the fetch when the requested ref is already checked out — cheap, and a speedup for every consumer that pins
ref:.
- Failing either, fail before posting the tracking comment, so a setup crash does not leave a permanently pending "Claude Code is working…" on the PR. The dangling comment is arguably the worse half of this bug: it reads as "still running" forever.
Environment
anthropics/claude-code-action@v1, actions/checkout@v7, ubuntu-latest, private repository, tag mode (no prompt: input).
Summary
In tag mode,
setupBranch()runsgit fetchbeforeconfigureGitAuth(). On a private repository checked out withactions/checkout'spersist-credentials: false, there is no credential for that fetch, so the action dies in setup — after it has already posted its "Claude Code is working…" tracking comment, which then never resolves.The result is a permanently pending comment on the PR and no review, for every mention.
Repro
Workflow (private repo):
Comment
@claude review thison an open same-repo PR.Cause (v1)
src/modes/detector.ts— a comment event with nopromptinput and a matched trigger phrase resolves to"tag".src/modes/tag/index.ts— posts the tracking comment, then callssetupBranch(...)at line 67;configureGitAuth(...)is called only afterwards, at lines 84 / 97.src/github/operations/branch.ts:212— for an open, same-repo PR:execGit(["fetch", "origin", ...depthArgs, branchName]), unconditionally. There is no check for whether the requested ref is already present in the working copy.So the ordering is: tracking comment → unauthenticated fetch → (never reached) git auth.
Agent mode is unaffected:
src/modes/agent/index.tsnever callssetupBranch, and reads branch state fromGITHUB_HEAD_REF/GITHUB_REF_NAMEinstead. That is why aworkflow_dispatch-triggered workflow using the identical checkout config works fine — it takes a different code path, not a safer configuration.Why
persist-credentials: falseis not simply user erroractions/checkoutwrites the job token into.git/configas anhttp.extraheader. A workflow that runs an agent with file-read tools over the checkout is handing that agent its own token — sopersist-credentials: falseis the correct hardening for exactly this action's use case, not an exotic choice. Right now that hardening is incompatible with tag mode.Pinning
actions/checkout'sref:to the PR head does not work around it:setupBranchfetches by branch name regardless of what is already checked out. (Verified — that was our first attempted fix.)Suggested fixes, roughly in order of preference
setupBranch(), so the fetch it performs is authenticated. Smallest change, fixes the ordering bug directly.ref:.Environment
anthropics/claude-code-action@v1,actions/checkout@v7,ubuntu-latest, private repository, tag mode (noprompt:input).