You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The pipeline checks out the head branch only via single-branch shallow clone, but every PR-aware prompt and the auto-rebase workflow then tells the agent to use origin/<baseBranch> — a remote-tracking ref the clone never created. So the agent's git diff / git rebase / git log commands cannot resolve the ref on every PR mention.
src/core/checkout.ts:59 runs git clone --depth=<cloneDepth> --branch=<branch> --single-branch … where branch = ctx.isPR ? ctx.headBranch : ctx.defaultBranch (src/core/checkout.ts:40). Per the git-clone docs, --single-branch rewrites remote.origin.fetch to +refs/heads/<headBranch>:refs/remotes/origin/<headBranch>. Only that one ref is fetched and a later plain git fetch origin will not populate any other branch. Yet src/core/prompt-builder.ts:38-39 tells the agent the PR base is origin/<baseBranch> and to run git diff origin/<baseBranch>...HEAD, repeated at L122 and again in the allowed-tools list at L205 (Bash(git diff origin/<baseBranch>...HEAD)).
The same shape breaks the auto-rebase loop. src/workflows/handlers/branch-refresh.ts:86-88 (added in commit befe07c, reinforced by 3079014's cascade review/resolve loop) emits git fetch origin then git rebase origin/<baseRef> — the rebase exits with fatal: invalid upstream 'origin/main'. cloneDepth (src/config.ts:93, default 50) controls history length, not which refs are fetched. No git fetch of the base branch exists anywhere in src/core/ or src/daemon/ (verified by grep). The agent has to discover the failure at runtime, eat retry turns probing git remote -v / git branch -r, and either give up or self-heal by guessing git remote set-branches --add + git fetch. Direct burned cost on every PR comment.
Diagram
flowchart TD
Trigger["Webhook: PR comment with the bot mention"]:::event
Clone["src/core/checkout.ts:59<br/>git clone --depth=50 --branch=headBranch<br/>--single-branch repoUrl workDir"]:::current
Refspec["remote.origin.fetch in workDir/.git/config:<br/>+refs/heads/headBranch:refs/remotes/origin/headBranch<br/>baseBranch is NOT in the refspec"]:::warn
Promptpath["src/core/prompt-builder.ts L38-39, L122, L205<br/>tells agent: git diff origin/baseBranch...HEAD"]:::current
Refreshpath["src/workflows/handlers/branch-refresh.ts L86-88<br/>tells agent: git fetch origin<br/>then git rebase origin/baseRef"]:::current
Diff["Agent: git diff origin/baseBranch...HEAD"]:::action
Rebase["Agent: git rebase origin/baseRef"]:::action
Fatal["fatal: ambiguous argument origin/baseBranch<br/>or fatal: invalid upstream origin/baseRef"]:::error
Waste["Wasted turns and tokens<br/>review may run against HEAD only<br/>auto-rebase aborts mid-loop"]:::error
Fix["Fix in src/core/checkout.ts after clone:<br/>git -C workDir remote set-branches --add origin baseBranch<br/>git -C workDir fetch --depth=cloneDepth origin baseBranch"]:::fix
Healthy["origin/baseBranch resolves<br/>diff and rebase work first try"]:::ok
Trigger --> Clone --> Refspec
Refspec --> Promptpath --> Diff
Refspec --> Refreshpath --> Rebase
Diff --> Fatal
Rebase --> Fatal
Fatal --> Waste
Waste -. proposed fix .-> Fix --> Healthy
classDef event fill:#0b5394;color:#ffffff;stroke:#053061;stroke-width:1px
classDef current fill:#856404;color:#ffffff;stroke:#533f03;stroke-width:1px
classDef warn fill:#a04000;color:#ffffff;stroke:#5e2607;stroke-width:1px
classDef action fill:#21618c;color:#ffffff;stroke:#1b4f72;stroke-width:1px
classDef error fill:#922b21;color:#ffffff;stroke:#641e16;stroke-width:1px
classDef fix fill:#196f3d;color:#ffffff;stroke:#0e6251;stroke-width:1px
classDef ok fill:#196f3d;color:#ffffff;stroke:#0e6251;stroke-width:1px
Loading
Rationale
This is a silent correctness issue on the most common code path — every bot mention on a PR. Default agent model is Opus 4.7 (src/config.ts:371), so wasted recovery turns cost real money: a typical non-trivial PR review is ~15-40 turns; observed Opus per-turn cost is roughly US$0.05–0.20, so even three burned recovery turns is ~US$0.15–0.60 of pure waste, doubled by the cascade review/resolve loop (reviewResolveMaxIterations defaults to 2 — src/config.ts:354). When the agent falls back to git diff HEAD (no base ref) it reviews the PR against itself, masking regressions. The cascade work in #61, #63 made it strictly worse — branch-refresh now relies on origin/<baseRef> for clean rebase, and formatRefreshDirective (src/workflows/handlers/branch-refresh.ts:65-69) prints a clean-looking directive whose underlying rebase fails.
The fix is small, local, additive: a single git remote set-branches --add origin <baseBranch> plus git fetch --depth=<cloneDepth> origin <baseBranch> after the existing clone. Pattern is documented in git-fetch(1) and the Jay Goodby writeup on tracking remote branches after --single-branch. Bandwidth cost is one extra shallow fetch (typically a few hundred KB) only paid on PR events where head and base differ. It also aligns this server with upstream claude-code-action, which gets origin/<baseBranch> for free via actions/checkout@v4 with fetch-depth: 0.
References
Internal:
src/core/checkout.ts:40-59 — single-branch shallow clone command and branch selection
src/config.ts:93 — cloneDepth default 50 (history length, not refspec)
src/core/prompt-builder.ts:38-39, 122, 205 — agent told to use git diff origin/<baseBranch>...HEAD in three places
src/workflows/handlers/branch-refresh.ts:86-88 — auto-rebase emits git fetch origin then git rebase origin/<baseRef>
src/core/pipeline.ts:212-216 — enrichedCtx.baseBranch is computed and passed to the prompt builder
src/core/fetcher.ts:317 — baseBranch hydrated from GraphQL baseRefName but never used to drive a fetch
CLAUDE.md Pipeline step 5 — never mentions fetching the base ref
In src/core/checkout.ts, after the clone (line 59) and before returning, conditionally run git -C <workDir> remote set-branches --add origin <baseBranch> then git -C <workDir> fetch --depth=<cloneDepth> origin <baseBranch> when ctx.isPR is true, a baseBranch is available, and it differs from the cloned branch. Cleanest plumbing: do the supplemental fetch inside runPipeline between the checkout call and executeAgent (so the GraphQL-derived baseBranch from src/core/fetcher.ts:317 is authoritative), or pass enrichedCtx.baseBranch into checkoutRepo as an explicit parameter.
Trim the now-redundant "(NOT 'main' or 'master')" recovery prose in src/core/prompt-builder.ts once the fix lands, and verify formatRefreshDirective works end-to-end on a same-repo PR that is behind base.
Add a Pino info-level log line at the supplemental-fetch site (fields: baseBranch, headBranch, depth) anchored to the existing log at src/core/checkout.ts:58.
Areas Evaluated
src/core/pipeline.ts — confirmed where checkout happens and what baseBranch is available
src/core/checkout.ts — identified the --single-branch clone command on line 59 as root cause
src/core/fetcher.ts — baseBranch hydrated from GraphQL, never used to drive an extra fetch
src/core/prompt-builder.ts — enumerated every place the agent is told to use origin/<baseBranch>
src/core/context.ts — baseBranch is set on BotContext for pull_request_review_comment events
src/core/executor.ts — no fetch logic in the executor either
src/workflows/handlers/branch-refresh.ts — auto-rebase path is also affected
src/config.ts — cloneDepth controls history depth only, not refspec
Grep across src/ and test/ for git fetch, set-branches, --no-single — no compensating fetch exists anywhere
Earlier same-run sibling issues #71, #72, #73 were created while probing harness payload-size and write-permission limits. They carry no findings and can be closed safely; this is the canonical research artifact for run #25011408648.
Generated by scheduled research workflow run #25011408648 on 2026-04-27
Finding
The pipeline checks out the head branch only via single-branch shallow clone, but every PR-aware prompt and the auto-rebase workflow then tells the agent to use
origin/<baseBranch>— a remote-tracking ref the clone never created. So the agent'sgit diff/git rebase/git logcommands cannot resolve the ref on every PR mention.src/core/checkout.ts:59runsgit clone --depth=<cloneDepth> --branch=<branch> --single-branch …wherebranch = ctx.isPR ? ctx.headBranch : ctx.defaultBranch(src/core/checkout.ts:40). Per the git-clone docs,--single-branchrewritesremote.origin.fetchto+refs/heads/<headBranch>:refs/remotes/origin/<headBranch>. Only that one ref is fetched and a later plaingit fetch originwill not populate any other branch. Yetsrc/core/prompt-builder.ts:38-39tells the agent the PR base isorigin/<baseBranch>and to rungit diff origin/<baseBranch>...HEAD, repeated at L122 and again in the allowed-tools list at L205 (Bash(git diff origin/<baseBranch>...HEAD)).The same shape breaks the auto-rebase loop.
src/workflows/handlers/branch-refresh.ts:86-88(added in commitbefe07c, reinforced by3079014's cascade review/resolve loop) emitsgit fetch originthengit rebase origin/<baseRef>— the rebase exits withfatal: invalid upstream 'origin/main'.cloneDepth(src/config.ts:93, default 50) controls history length, not which refs are fetched. Nogit fetchof the base branch exists anywhere insrc/core/orsrc/daemon/(verified by grep). The agent has to discover the failure at runtime, eat retry turns probinggit remote -v/git branch -r, and either give up or self-heal by guessinggit remote set-branches --add+git fetch. Direct burned cost on every PR comment.Diagram
flowchart TD Trigger["Webhook: PR comment with the bot mention"]:::event Clone["src/core/checkout.ts:59<br/>git clone --depth=50 --branch=headBranch<br/>--single-branch repoUrl workDir"]:::current Refspec["remote.origin.fetch in workDir/.git/config:<br/>+refs/heads/headBranch:refs/remotes/origin/headBranch<br/>baseBranch is NOT in the refspec"]:::warn Promptpath["src/core/prompt-builder.ts L38-39, L122, L205<br/>tells agent: git diff origin/baseBranch...HEAD"]:::current Refreshpath["src/workflows/handlers/branch-refresh.ts L86-88<br/>tells agent: git fetch origin<br/>then git rebase origin/baseRef"]:::current Diff["Agent: git diff origin/baseBranch...HEAD"]:::action Rebase["Agent: git rebase origin/baseRef"]:::action Fatal["fatal: ambiguous argument origin/baseBranch<br/>or fatal: invalid upstream origin/baseRef"]:::error Waste["Wasted turns and tokens<br/>review may run against HEAD only<br/>auto-rebase aborts mid-loop"]:::error Fix["Fix in src/core/checkout.ts after clone:<br/>git -C workDir remote set-branches --add origin baseBranch<br/>git -C workDir fetch --depth=cloneDepth origin baseBranch"]:::fix Healthy["origin/baseBranch resolves<br/>diff and rebase work first try"]:::ok Trigger --> Clone --> Refspec Refspec --> Promptpath --> Diff Refspec --> Refreshpath --> Rebase Diff --> Fatal Rebase --> Fatal Fatal --> Waste Waste -. proposed fix .-> Fix --> Healthy classDef event fill:#0b5394;color:#ffffff;stroke:#053061;stroke-width:1px classDef current fill:#856404;color:#ffffff;stroke:#533f03;stroke-width:1px classDef warn fill:#a04000;color:#ffffff;stroke:#5e2607;stroke-width:1px classDef action fill:#21618c;color:#ffffff;stroke:#1b4f72;stroke-width:1px classDef error fill:#922b21;color:#ffffff;stroke:#641e16;stroke-width:1px classDef fix fill:#196f3d;color:#ffffff;stroke:#0e6251;stroke-width:1px classDef ok fill:#196f3d;color:#ffffff;stroke:#0e6251;stroke-width:1pxRationale
This is a silent correctness issue on the most common code path — every bot mention on a PR. Default agent model is Opus 4.7 (
src/config.ts:371), so wasted recovery turns cost real money: a typical non-trivial PR review is ~15-40 turns; observed Opus per-turn cost is roughly US$0.05–0.20, so even three burned recovery turns is ~US$0.15–0.60 of pure waste, doubled by the cascade review/resolve loop (reviewResolveMaxIterationsdefaults to 2 —src/config.ts:354). When the agent falls back togit diff HEAD(no base ref) it reviews the PR against itself, masking regressions. The cascade work in #61, #63 made it strictly worse — branch-refresh now relies onorigin/<baseRef>for clean rebase, andformatRefreshDirective(src/workflows/handlers/branch-refresh.ts:65-69) prints a clean-looking directive whose underlying rebase fails.The fix is small, local, additive: a single
git remote set-branches --add origin <baseBranch>plusgit fetch --depth=<cloneDepth> origin <baseBranch>after the existing clone. Pattern is documented in git-fetch(1) and the Jay Goodby writeup on tracking remote branches after--single-branch. Bandwidth cost is one extra shallow fetch (typically a few hundred KB) only paid on PR events where head and base differ. It also aligns this server with upstreamclaude-code-action, which getsorigin/<baseBranch>for free viaactions/checkout@v4withfetch-depth: 0.References
Internal:
src/core/checkout.ts:40-59— single-branch shallow clone command and branch selectionsrc/config.ts:93—cloneDepthdefault 50 (history length, not refspec)src/core/prompt-builder.ts:38-39, 122, 205— agent told to usegit diff origin/<baseBranch>...HEADin three placessrc/workflows/handlers/branch-refresh.ts:86-88— auto-rebase emitsgit fetch originthengit rebase origin/<baseRef>src/core/pipeline.ts:212-216—enrichedCtx.baseBranchis computed and passed to the prompt buildersrc/core/fetcher.ts:317—baseBranchhydrated from GraphQLbaseRefNamebut never used to drive a fetchCLAUDE.mdPipeline step 5 — never mentions fetching the base refExternal:
--single-branch— documents the single-ref refspec rewrite--single-branchcheckout (Jay Goodby, 2022) — canonicalgit remote set-branches --add+git fetchremediationactions/checkout— upstream usesfetch-depth: 0, masking this in CI but not hereSuggested Next Steps
src/core/checkout.ts, after the clone (line 59) and before returning, conditionally rungit -C <workDir> remote set-branches --add origin <baseBranch>thengit -C <workDir> fetch --depth=<cloneDepth> origin <baseBranch>whenctx.isPRis true, abaseBranchis available, and it differs from the clonedbranch. Cleanest plumbing: do the supplemental fetch insiderunPipelinebetween the checkout call andexecuteAgent(so the GraphQL-derivedbaseBranchfromsrc/core/fetcher.ts:317is authoritative), or passenrichedCtx.baseBranchintocheckoutRepoas an explicit parameter.test/core/(newcheckout.test.ts) that runs the checkout against a local fixture repo with two branches and asserts bothorigin/<headBranch>andorigin/<baseBranch>show up ingit branch -r. DI groundwork already landed in chore(test): DI refactor for checkout.ts and executor.ts to enable module-level unit tests #7.src/core/prompt-builder.tsonce the fix lands, and verifyformatRefreshDirectiveworks end-to-end on a same-repo PR that is behind base.baseBranch,headBranch,depth) anchored to the existing log atsrc/core/checkout.ts:58.Areas Evaluated
src/core/pipeline.ts— confirmed where checkout happens and whatbaseBranchis availablesrc/core/checkout.ts— identified the--single-branchclone command on line 59 as root causesrc/core/fetcher.ts—baseBranchhydrated from GraphQL, never used to drive an extra fetchsrc/core/prompt-builder.ts— enumerated every place the agent is told to useorigin/<baseBranch>src/core/context.ts—baseBranchis set onBotContextforpull_request_review_commenteventssrc/core/executor.ts— no fetch logic in the executor eithersrc/workflows/handlers/branch-refresh.ts— auto-rebase path is also affectedsrc/config.ts—cloneDepthcontrols history depth only, not refspecsrc/andtest/forgit fetch,set-branches,--no-single— no compensating fetch exists anywhereNote on sibling placeholders
Earlier same-run sibling issues #71, #72, #73 were created while probing harness payload-size and write-permission limits. They carry no findings and can be closed safely; this is the canonical research artifact for run #25011408648.
Generated by scheduled research workflow run #25011408648 on 2026-04-27