Files
shockbot/mcp
Colin McDonnell 74b7329f64 fix(action): dedupe concurrent checkout_pr + guard cross-PR clobber (#735)
* fix(action): dedupe concurrent checkout_pr calls + guard cross-PR clobber (#642)

agents occasionally emit duplicate parallel `checkout_pr` tool_use blocks
in one turn, causing two `checkoutPrBranch` invocations to race the same
`.git/shallow.lock` and one to fail with `File exists`. the prior fix
(#564) added a 30s staleness sweep, but that very threshold protects the
within-run concurrent case from itself.

dedupe at the tool layer: a module-level `Map<pull_number, Promise>`
shares a single in-flight promise across concurrent same-PR calls. the
fetch race becomes architecturally impossible — first call does the work,
duplicate gets the same `CheckoutPrResult`. cleared in `finally` so
subsequent same-PR calls re-do the work normally.

also reject cross-PR checkouts when the working tree is dirty, surfacing
a clear error instead of silently overwriting uncommitted work from a
prior PR. uses existing `toolState.issueNumber` (no new state).

* review: use dedicated `pullNumber` toolState field for cross-PR guard

per copilot review: the prior guard used `toolState.issueNumber`, which
is also set by issue/comment lookup tools (issueInfo, issueComments,
issueEvents, review). that conflation is intentional and correct for
its only consumer (`report_progress` falls back to `issueNumber` to
choose which issue/PR to comment on, and GitHub treats both via the
same comment API). but it makes the field wrong for the cross-PR
guard: a same-PR re-checkout after `get_issue(other)` would falsely
fire and surface a misleading "from PR #other" message.

introduce a separate `pullNumber` field, set only by `checkoutPrBranch`
alongside `issueNumber` and `checkoutSha`. narrower invariant, no
disturbance to the existing `issueNumber` semantics.

* review: drop dual-write — single `issueNumber` is sufficient for the guard

reverting the `pullNumber` addition. setting both `issueNumber` and
`pullNumber` to the same value at the same site was a code smell — there
is no scenario where they diverge. issues and PRs share GitHub's number
space, and the cross-PR guard's actual job is "refuse to clobber a dirty
tree when switching to a different number"; that's expressible with
`issueNumber` alone.

addresses copilot's original concern (misleading "from PR #X" message
when X was an issue) by removing the prior-number reference from the
error message entirely. the dirty paths are the actionable detail.
2026-05-14 05:08:11 +00:00
..
2025-11-21 14:08:36 -05:00
2026-01-16 18:43:09 +00:00
2026-03-12 05:22:51 +00:00