fix(pr): substitute anchor-matching canonical wrapper for stale worktrees (#124710)

A linked worktree whose base predates (or carries) wrapper changes relative
to origin/main previously hit a hard refusal, even though the canonical
checkout held exactly the trusted origin/main wrapper the refusal message
told the operator to go run by hand. When the canonical checkout is clean
and byte-identical to fetched refs/remotes/origin/main, exec it with a loud
stderr notice instead; advisory dev-wrapper opt-in keeps precedence, and
the refusal remains when no anchor-matching wrapper exists on disk.

Also records the squash-merged stacked-branch rebase gotcha
(git rebase --onto origin/main <landed-branch>) in the PR maintainer skill.
This commit is contained in:
Peter Steinberger
2026-08-16 10:48:26 -07:00
committed by GitHub
parent 0e7deeec45
commit cae9ecaba4
4 changed files with 74 additions and 14 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ This directory owns local tooling, script wrappers, and generated-artifact helpe
## PR Prepare Gates
- `scripts/pr` serializes review, prepare, and merge operations per PR across linked worktrees; `scripts/pr gc` skips active or indeterminate locks. Its subcommand classification table is the canonical wrapper trust boundary: a mismatched local wrapper may run only a classified `advisory` subcommand with `--dev-wrapper` or `OPENCLAW_PR_DEV_WRAPPER=1`; classified `landing` subcommands always require canonical/origin-main wrapper code. A successful command return is the trusted synchronous-completion contract: every PR-state-mutating child must be joined before returning, and such work must never daemonize or explicitly escape both the operation group and lock-notification FD. Release on clean exit requires the leader's completion marker; an escaped descendant that merely holds the notify pipe then produces a loud warned release instead of retention (#124583), while all failure shapes still retain. A failed command auto-releases only while its explicit pre-side-effect validation marker remains active; failures after mutation/tool launch, interruptions, and controller loss stay locked because detached children cannot be disproved. After verifying no child tools remain, use the reported exact-OID `scripts/pr lock-recover` command. Never bypass or delete these refs manually.
- `scripts/pr` serializes review, prepare, and merge operations per PR across linked worktrees; `scripts/pr gc` skips active or indeterminate locks. Its subcommand classification table is the canonical wrapper trust boundary: a mismatched local wrapper may run only a classified `advisory` subcommand with `--dev-wrapper` or `OPENCLAW_PR_DEV_WRAPPER=1`; classified `landing` subcommands always require canonical/origin-main wrapper code. A worktree whose wrapper differs from origin/main (stale base or wrapper-editing branch) loudly substitutes the canonical checkout's wrapper when that checkout is clean and byte-identical to fetched `refs/remotes/origin/main`; it refuses only when no anchor-matching wrapper is available. A successful command return is the trusted synchronous-completion contract: every PR-state-mutating child must be joined before returning, and such work must never daemonize or explicitly escape both the operation group and lock-notification FD. Release on clean exit requires the leader's completion marker; an escaped descendant that merely holds the notify pipe then produces a loud warned release instead of retention (#124583), while all failure shapes still retain. A failed command auto-releases only while its explicit pre-side-effect validation marker remains active; failures after mutation/tool launch, interruptions, and controller loss stay locked because detached children cannot be disproved. After verifying no child tools remain, use the reported exact-OID `scripts/pr lock-recover` command. Never bypass or delete these refs manually.
- `scripts/pr prepare-gates` holds the heavy-check lock for its whole local gate block (`scripts/pr-gates-lock.mts`), so concurrent gate runs across `.worktrees` queue as units instead of dying on child lock timeouts or vitest no-output watchdog kills.
- `OPENCLAW_PR_GATES_REMOTE=testbox` runs the full-suite `pnpm test` gate on a Blacksmith Testbox through `scripts/crabbox-wrapper.mjs` (same delegation as `check:changed`); `pnpm build`/`pnpm check` stay local. The `tbx_` lease id and Actions run URL land in `.local/gates.env` (`REMOTE_GATES_*`) and `.local/prep.md`. Use it for reviewed trusted code when a loaded host makes the local 88-shard run stall-kill; contributor/fork code stays on secretless CI or sanitized AWS unless a maintainer explicitly approves credentialed execution.
+10
View File
@@ -102,6 +102,16 @@ if common_git_dir=$(git -C "$script_parent_dir" rev-parse --path-format=absolute
if [ "$dev_wrapper_opt_in" = "1" ] && [ -n "$requested_classification" ]; then
echo "subcommand '$requested_subcommand' is classified $requested_classification; dev-wrapper opt-in is unavailable." >&2
fi
# Worktrees routinely sit on a base that predates (or carries) wrapper
# changes relative to main. When the canonical checkout is byte-identical
# to the fetched origin/main anchor, exec-ing it runs exactly the trusted
# anchor code; announce the substitution so it is never silent.
if [ -n "$anchor_wrapper_revision" ] &&
[ "$canonical_wrapper_revision" = "$anchor_wrapper_revision" ] &&
[ "$canonical_wrapper_clean" = "1" ]; then
echo "scripts/pr wrapper in this worktree differs from origin/main; running the canonical checkout's wrapper (matches the origin/main trust anchor): $canonical_repo_root" >&2
exec "$canonical_self" "$@"
fi
# HEAD blobs are authoritative here: the uncommitted-wrapper guard above
# already exited for any staged or unstaged edit to these paths, so
# the working tree matches HEAD and this list matches what was rejected.