feat(source-control): explicit-autopilot merge-rung exception with fresh-agent resolution#1405
Conversation
…esh-agent resolution Adds one named, explicit-argument exception to the babysit-loop merge rung: typing the literal `autopilot` tier argument widens that single invocation's merge authority up to C3, dispatching a fresh independent frontier-tier agent (sharing no context with the PR's authoring/reviewing sessions) to resolve needs-human labels, security findings, and contradictory review threads through babysit-prs's existing guarded-mutation path before the deterministic gate runs. C4-structural and C5-untrusted-provenance stay unconditionally human-merge — no rung, seam, or argument, including this one, ever reaches them, per the autonomy matrix's own promotion contract. Closes #1309 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kb58mL6JnfEos5zrhgn4GD
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3896f0443b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Resolve the loop-lane CHANGELOG collision: main landed 2.0.0 (single-account gap reframing + second re-derivation trigger), so this branch's autonomy-ladder entry renumbers to 3.0.0 and stacks above it. Both entries retained.
…inheritance paths Addresses the three review threads on #1405. Version and delivery (P1): plugin.json 0.26.9 -> 0.27.0 with a matching CHANGELOG entry — per docs/MIGRATION-PLAYBOOK.md the manifest version is the update cache key, so an unbumped plugin never delivers the exception to an installed consumer. Confirmed against the current plugins reference fetched this session (https://code.claude.com/docs/en/plugins-reference): "Setting this pins the plugin to that version string, so users only receive updates when you bump it." Marketplace description (P2): the plugin.json description no longer claims merge-rung raises bind only from the team-tracked layer — it now says STANDING raises do and names the one exception plus the C4/C5 floor. README.md regenerated with scripts/generate-catalog.mjs (the catalog block is generated, CI-checked, never hand-edited). docs/conventions/config-cascade/README.md carried the same stale claim and is corrected too. Eval alignment (P2): babysit-loop eval 1 graded the exact invocation this change widens as "stays c2-mechanical", so running it would have rejected the intended behavior. It now distinguishes the two argument-shaped raises on that line — the typed autopilot tier keyword widens this one invocation to C3; the --merge full-autonomy dimension argument is still ignored — and asserts the persist- nothing and C4/C5-floor properties. New eval 4 covers the safety half that had no coverage: a tier resolved from babysit_loop_tier or babysit_default_tier rather than typed does NOT widen the merge dimension. Inheritance paths closed: the exception's "never inherited, never defaulted" clause named only babysit_loop_tier. config-resolution.md and babysit-loop SKILL.md now state that no config layer or key supplies the keyword, calling out babysit_default_tier explicitly as a babysit-prs userConfig scalar that is not a loop-lane key. babysit-prs SKILL.md was 4 lines over the 500-line hard cap after the merge; the security/P1 exception detail moves to reference/safety.md's new "Security/P1 escalation: the one named exception" subsection, where it is also stated as a contract on how the lane dispatches rather than a claim the dispatch presents about itself.
source-control landed 0.26.10 on main (worker cwd anchoring, #1299) while this branch bumped to 0.27.0 for the merge-rung exception. Kept 0.27.0 — a minor bump still leads 0.26.10 — with this branch's description, and stacked both changelog entries newest-first.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 028c668f1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
source-control reached 0.26.11 on main (in_reply_to_id projection, #587). Kept this branch's 0.27.0 and description; main did not touch the description text, only the version. Changelog entries stacked newest-first.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b4826ad861
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…eption An explicitly argued lower rung is now applied after the autopilot widening, so `autopilot --merge human-only` merges nothing; the resolution order is stated identically in the convention, the skill's rung partition, and config-resolution. C5 becomes a provenance test on the PR itself rather than a lookup of the close-linked item's stamp, so a fork PR closing an internally classified C2/C3 item stays outside the exception. Human blocking feedback and merge conflicts are named as outside the pre-escalation dispatch: the former stays a stop-and-ask condition, the latter routes to the merge-only conflict path, never a rebase.
Composes both sides of the source-control plugin manifest and changelog: main's 0.28.0-0.30.0 entries keep their history, and this branch's merge-rung exception entry renumbers to 0.31.0 on top of them. The manifest description keeps this branch's exception clause at the new version.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fab489e049
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
main took 0.31.0 for the argparse-abbreviation hardening while this branch held that number, so this branch's merge-rung exception entry moves to 0.32.0 above it. Both changelog sections are kept; the manifest keeps this branch's description at the new version.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcdacf741f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…d cap The exception pointer added here pushed the file to exactly 500 lines, which skill-quality-gate fails. Tightened to a single pointer sentence; the independence and scope conditions are stated in full in safety.md's "Security/P1 escalation" section, which owns them.
main took 0.31.1 for the extra-bot-logins fix; this branch's minor stays 0.32.0 above it and both changelog sections are kept. main also grew babysit-prs/SKILL.md to the 499-line ceiling, so the security/P1 exception pointer folds into the existing sentence rather than adding a line — safety.md still carries the full scope and independence conditions.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 884afa1228
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
main reached 0.31.3 while this branch held 0.32.0; both changelog sections are kept and this branch's minor stays above main's patches.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50d2f77523
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…g exception The C4/C5 floor now reads the pull request rather than the linked item's stamp on both axes: C5 from the code's cross-repository provenance, and C4 from the diff's blast radius, with a class/diff mismatch failing closed to escalation. The author-login-versus-watched-owners test is gone — that key is a repository-owner allowlist, so it would have classified every internally authored PR on an org-owned repository as C5. The pre-escalation dispatch gains three limits: it fires only on a machine-escalated needs-human item, never on the role label alone, so an operator-parked item stays the attended queue's; it holds the PR's own worker lease for its duration, because the guarded-mutation wrappers pin comment state without conferring concurrency ownership; and its capability tier resolves through the convention's tier binding instead of a fable/opus family alias that rots across model generations.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc0a5a2664
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
main reached 0.31.5; both changelog sections are kept and this branch's minor stays 0.32.0 above main's patches.
|
Claude finished @kyle-sexton's task in 5m 28s —— View job PR Review —
|
|
Claude finished @kyle-sexton's task in 3m 56s —— View job Security Review — PR #1405
This is a policy-document PR — every changed file is Markdown or JSON evals. No executable code, shell scripts, or workflow files are modified. The security surface is the behavioral contract the documents define for AI agents: authorization logic, access-control invariants, and the scope of autonomous action. Three findings follow, ordered by severity. FINDING 1 — IMPORTANT | Confidence: HIGHUnconditional security/P1 escalation rule converted to a conditional oneLocation: Before this PR, the babysit-prs agent-judgment rule was unconditional: "Escalate a security/P1 thread instead of resolving it, even in autopilot." After this PR, it reads: "even in autopilot — one named exception, scoped in Attack path: A review bot or crafted PR review comment containing a security-flagged finding can also embed adversarial instructions (prompt injection). The pre-escalation resolver is briefed with the blocker content, has no prior context to be skeptical, and is explicitly instructed to resolve it. A resolver manipulated into marking the thread The previous rule was unconditional precisely because the cost of a false dismissal on a security finding exceeds any benefit from automation. Converting it to conditional on a model-enforced "freshness" and "uncertainty" check is a meaningful security regression, regardless of how that check is worded. FINDING 2 — IMPORTANT | Confidence: MEDIUMMachine-escalation marker is not bound to a trusted identityLocation: The dispatch condition for the pre-escalation resolver distinguishes machine-escalated The SKILL.md describes this as "a machine-marked escalation comment" but does not specify that verification of "machine-escalated" checks the comment's author against a known-trusted identity (lane identity, bot login). The check as described is purely content-based: does this issue have a comment whose first line matches the sentinel? Attack path: Any user with comment access to the linked GitHub issue (which on most repos is any repo collaborator, and on public repos, any authenticated user) can post a comment beginning with that sentinel. An operator who manually parked an item with Confidence is MEDIUM because there may be identity verification in the marker-reading implementation not shown in these policy files. The SKILL.md does not describe such a check, and the added dispatch logic cites the sentinel grammar as the discriminator without qualifying it. If no identity check exists, this becomes a straightforward privilege-escalation from commenter to autonomous-merge authority. FINDING 3 — IMPORTANT | Confidence: HIGHC3 autonomous merge introduced in direct conflict with the governing guardrails matrixLocation: The PR adds a path by which typing This is a policy-level authorization bypass: the mechanism is live and invocable now, while the operator decision on whether C3 autonomous merge should exist at all is unresolved (open issue #1388, labelled The PR body carries an explicit reviewer gate about this, which the PR author deliberately left open. That gate is correct. This finding records it at the security-review layer: the gap between the behavior this PR ships and the governing matrix it has not amended is a real authorization inconsistency, not just a documentation concern. No findings related to GitHub Actions patterns, injection into shell commands, credential handling, or dependency pins — none of those surfaces changed in this PR. |
…ternal gaps The rung partition's verdict is now pinned to the head SHA it examined, for every push and not only the pre-escalation resolver's: the merge-capable invocation carries the partitioned head as its merge gate's --expected-head, an ordinary worker fix-push moves the head off the pin, the gate's head-match refusal blocks the merge deterministically, and the invocation reports the new head instead of re-pinning. babysit-prs gains the matching named "Lane-pinned merge authorization" exception in reference/safety.md; the lane reruns provenance, C4-diff, and rung on the post-push head before any merge-capable re-invocation. The C5 provenance test is now executable and fail-closed instead of "another external-contribution signal": a fork test (isCrossRepository / headRepositoryOwner) plus a trust test on the provider-computed authorAssociation, where anything other than OWNER or MEMBER is external - catching the outside collaborator whose base-repository branch passes the fork test - and a missing or unreadable field is C5, never a pass. The cycle-start snapshot enumerates the provenance fields it must carry. Loop-lane convention README, its changelog 3.0.0 entry, and the plugin changelog updated in lockstep.
|
Claude finished @kyle-sexton's task in 4m 8s —— View job PR Review —
|
|
Claude finished @kyle-sexton's task in 3m 44s —— View job Security Review — PR #1405 (HEAD:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79bde72cff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…raise token
An independent audit of the final diff found the widening keyed to bare
token-presence of the pre-existing autopilot tier keyword, which was
merge-inert before this change: every saved invocation, alias, or expanded
template already passing autopilot would have silently acquired C3 merge
authority, and the two trigger phrases the description added ("drain hard",
"merge everything you can in autopilot") invited a model to compose the
keyword from a request that never contained it.
The exception now fires only when the invocation's own argument line types
both the autopilot tier keyword AND the dedicated raise argument
--merge c3-this-run. The raise token exists for this exception alone, so
its presence cannot be a leftover: autopilot alone stays merge-inert
beyond the tracked rung (its pre-exception semantics), c3-this-run is not
a rung name and is invalid in any config layer, and the token is never
composed by a model on the caller's behalf. The safer-cap rule becomes
mutually exclusive with the raise by grammar - every other merge value
only ever lowers. The two inviting trigger phrases are removed.
Convention README, both changelogs, config-resolution, safety.md's
dispatch-path scoping, the prompts document, evals 1 and 4, plugin.json,
and the marketplace README updated in lockstep.
|
Claude finished @kyle-sexton's task in 5m 28s —— View job PR Review —
|
|
Claude finished @kyle-sexton's task in 6m 48s —— View job Security Review — PR #1405 (HEAD:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf16f30c99
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…line hard cap The skill-quality gate fails at >= 500 lines; babysit-loop had grown to 537 and babysit-prs sat exactly at 500. Condensed by pointing instead of restating: the exception's token mechanics now cite config-resolution.md as their single home, the widening section folds its safer-argument paragraph in and hands the dispatch contract to the Escalation section, and verbose passages are reflowed without dropping any normative statement. Also fixes three bare reference/... cites introduced by the condensation to full babysit-prs/reference/... paths so the internal-ref check resolves them, and trims the frontmatter description under the 1536-char listing cap.
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94164e40ec
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…-trust to #1525 Three review findings showed paths around the rung partition's guarantees, each fixed at the partition itself: - Operator-parked items: a PR whose close-linked item wears the human-gated role label without the machine escalation marker is now withheld from the merge-capable set (routed to safe), not merely spared the resolver dispatch - the base merge gate never inspects the linked item's labels. - Human-blocked PRs: a human CHANGES_REQUESTED review, explicit blocking language, or an unresolved inline human thread likewise withholds the PR from the merge-capable set, because a merge-capable tier's own runbook widens thread scope to human threads. - Worker-tier head pin: the lane pin now rides the invocation brief at every merge-capable tier - babysit-prs's worker-mode step 7 gains the same lane-pinned no-re-pin exception Autopilot step 3 carries, and safety.md's section says worker and autopilot alike. The canonical merge-lane prompt's dispatch-model passage gains the explicit frontier exception for the pre-escalation resolver, so the template's review-work model binding no longer contradicts the frontier requirement. The bot-author trust finding (repository-owned App bots classified C5 by the trust test) is deferred to #1525: the misclassification is conservative - it only escalates org-automation PRs a C2 rung could merge - and the fix needs a reviewed internal-bot trust surface on the tracked seam, which would widen the scope the operator ratified for this change.
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c56eec1e09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rs; restore description guard The Subagents section routed every dedicated blocker through the Merge Conflict Resolution contract's no-push rule, contradicting Escalation's requirement that a code-change blocker runs the full per-PR worker lifecycle with its own commit and refspec push. The section now distinguishes them: conflict workers never push (the dispatching context does); every other blocker worker, the pre-escalation resolver included, lands its own push. The frontmatter description's raise-token parenthetical regains the "never inferred from a drain/merge phrasing" guard the cap condensation dropped - the description is the model-routing surface, exactly where a phrasing-only match must not read as an implied raise. 499/500 lines, 1470/1536 description chars.
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e74d9d5f27
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| **Pre-escalation resolution attempt, explicit-`autopilot` only.** Before a merge-eligible (C1-C3) | ||
| PR is escalated for a **machine-escalated** `needs-human` item, an open machine-authored finding, or | ||
| a contradictory/unresolved **bot** review thread, and only when this invocation's own argument line | ||
| typed both the literal `autopilot` tier argument and `--merge c3-this-run` (the widening pair |
There was a problem hiding this comment.
Apply dimension floors before dispatching the resolver
For an invocation such as autopilot --merge c3-this-run --thread-resolution safe, this condition dispatches solely because the paired raise tokens are present, and the resolver can mutate bot threads before the tier-flooring at lines 254–265 is applied to the normal invocation. This bypasses the explicit safer override: reference/config-resolution.md:114-122 gives invocation arguments precedence, while babysit-prs/SKILL.md:98-104 defines safe thread resolution as disabled. Compute the effective dimension floors before this dispatch and skip thread mutation when the resolved thread-resolution dimension forbids it.
Useful? React with 👍 / 👎.
| **A blocker needing a code change runs the full per-PR worker lifecycle** — isolated PR worktree, | ||
| HEAD asserted at the live PR head, commit and refspec push (`babysit-prs/reference/safety.md`) — not |
There was a problem hiding this comment.
Exempt the resolver from the pre-push-outdated rule
When the blocker is a current bot review thread and fixing it requires a push, routing the resolver through the regular per-PR worker lifecycle leaves the advertised path unable to clear the blocker: babysit-prs/reference/orchestration.md:754-762 permits a worker to resolve only threads that were already outdated before its push and explicitly bars resolving a thread made outdated by that push, while babysit_merge.py:689-694 refuses any merge with an unresolved thread. Give this named resolver an explicit pinned current-thread resolution carve-out, or route its addressed-thread resolution through the autopilot mechanic.
AGENTS.md reference: AGENTS.md:L27-L28
Useful? React with 👍 / 👎.
…#1539) ## Summary `plugins/source-control/bin/source-control-babysit-merge` exists to add exactly one refusal on top of `babysit_merge.py`: it rejects `--allow-unpinned-head` so that no unattended, allow-rule-covered invocation can merge an unvetted head. The guard's own comment says it "must not depend on the interpreter behind it" — but an `=value` spelling of the flag did exactly that. **Reproduced on `origin/main` before touching anything:** ```console $ bash bin/source-control-babysit-merge owner/repo#1 --allow-unpinned-head source-control-babysit-merge: --allow-unpinned-head is not permitted through the wrapper (--allow-unpinned-head or a prefix of it). Invoke babysit_merge.py directly for interactive unpinned use. RC=2 # the WRAPPER refused $ bash bin/source-control-babysit-merge owner/repo#1 --allow-unpinned-head=true usage: babysit_merge.py ... babysit_merge.py: error: argument --allow-unpinned-head: ignored explicit argument 'true' RC=2 # argparse refused — the wrapper let it through ``` Both exit 2, but for different reasons. The guard is `[[ "$arg" == --a* && "--allow-unpinned-head" == "$arg"* ]]` — a test that `$arg` is a *prefix* of the flag. `--allow-unpinned-head=true` is not a prefix of `--allow-unpinned-head` (the `=true` tail breaks the comparison), so the wrapper's own filter never fires; the CLI happens to reject it today only because the flag is `argparse store_true`, which refuses an explicit value. The refusal is real, but incidental — the moment the guarded flag (or an equivalent guarded flag) accepts a value, this same test stops refusing anything while looking identical, and nothing fails loudly to say so. ### Fix Strip a `--flag=value` tail (`stem="${arg%%=*}"`) before the prefix comparison, so the guard tests the option name rather than the raw argument. No exact spelling that previously refused changed behavior — the stem of an already-refused argument is unchanged, and a stemmed sibling flag (`--allow-dependency`, `--allow-unprotected`, `--allowed-owners=<value>`) still isn't a prefix of `--allow-unpinned-head`, so it still reaches the CLI unmolested. ## Test plan - [x] **Red first.** Wrote the `check_wrapper_refusal` rows in `engine.test.sh` and the two new `guard_contract.py` rows against unmodified `origin/main` and confirmed they fail: 3 bash rows FAIL (`--allow-unpinned-head=true`, `--allow-unpinned=1`, `--allow-unpinned-hea=1` all reach argparse instead of the wrapper) and 2 Python `test_every_refusal_row` subtests FAIL the same way. - [x] **Assertions check the wrapper's own refusal *text*, not exit 2.** Exit 2 is overloaded between the wrapper's refusal and argparse's own usage/rejection errors on this path, so an exit-code-only assertion would have passed before and after this fix for different reasons (exactly the trap `--allow-unpinned-head=true` is). `engine.test.sh` gained a `check_wrapper_refusal` helper that greps stderr for the wrapper's refusal text; `guard_contract.py`'s existing framework already asserts no JSON envelope was emitted (proof Python never ran) for `bash-wrapper`-attributed rows. - [x] **New engine.test.sh rows:** `--allow-unpinned-head=true`, `--allow-unpinned=1`, `--allow-unpinned-hea=1` (all refused by the wrapper) plus no-over-refusal rows for `--allow-dependency`, `--allow-unprotected`, and `--allowed-owners=owner` (all still reach the fail-closed CLI, verified via an out-of-scope-owner exit 3 so no network call is needed). - [x] **New guard_contract.py rows:** `merge.equals-value-unpinned-head-refused-by-wrapper` and `merge.equals-value-abbreviated-unpinned-head-refused-by-wrapper`, each citing #1522. Also updated `wrapper_denies()` (used by the doc/parser cross-check `test_every_wrapper_refusal_row_reaches_the_denial_table`) to strip the same `=value` tail before its own prefix check, so the two new rows don't fail that self-consistency test for an unrelated reason. Regenerated `reference/guard-contract.md` via `python tests/guard_contract.py --emit`. - [x] **Green after the fix:** all `engine.test.sh` wrapper rows PASS; full `unittest discover` suite (442 tests, includes both new rows and the existing 5 unpinned-head-family rows) OK; `ruff check` clean; `shellcheck -x` on the wrapper clean. - [x] **CHECK-SKILL babysit-prs: PASS** via `scripts/check-changed-skills.sh origin/main` (trigger phrases preserved, `engine.test.sh` passes). Note: this machine's default `python3`/`python` on PATH resolve to a broken local interpreter install unrelated to this change (a `dataclasses` import failure); the gate was run with a working Python 3.11+ interpreter on PATH, and the identical failure reproduces on unmodified `origin/main` too, confirming it's pre-existing and environmental, not a regression. - [x] Repo gates run locally: `scripts/check-changelog-parity.sh --check` and `--check-bump origin/main` ✔; `scripts/validate-plugins.sh` ✔; `markdownlint-cli2` on the touched docs clean. - [x] Version bumped `0.32.0` → `0.32.1` with the matching CHANGELOG entry. ## Related Closes #1522. Same failure family as #1371 (fixed by #1354, which made the guard prefix-aware) — this closes the one spelling that fix's prefix comparison still missed. Found during independent verification of #1405, per #1522's own description; not introduced by that PR. --- *This was generated by AI during work-loop execution.* 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
autopilottier keyword and the dedicated raise argument--merge c3-this-run(each never inherited, never defaulted, never config-supplied, never model-composed on the caller's behalf) widens that single invocation's merge authority up to C3, in a repository that has already adopted the loop-lane baseline. The raise token exists for this exception alone, so its presence cannot be a legacy leftover:autopilotalone stays merge-inert beyond the tracked rung, exactly as before this change, so saved invocations and templates that already pass the tier keyword acquire no new authority.needs-humanlabel, open machine-authored finding, contradictory or unresolved bot review thread) get a fresh frontier-tier subagent — sharing no context with whatever produced or previously reviewed the PR — dispatched to resolve the blocker through babysit-prs's existing guarded-mutation path, before the deterministic merge gate runs.isCrossRepository/headRepositoryOwner) and a trust test on the provider-computedauthorAssociation(anything other thanOWNER/MEMBERis external — including a same-repository outside collaborator), never from the class stamped on the item it closes.--expected-head; any worker push — an ordinary CI or review-finding fix, not only the pre-escalation resolver's — moves the head off the pin, the gate's head-match refusal blocks the merge deterministically, and the lane re-partitions the post-push head before any merge-capable re-invocation. babysit-prs carries the matching named "Lane-pinned merge authorization" exception inreference/safety.md.--mergevalue other thanc3-this-runonly ever selects a lower rung, soautopilot --merge human-onlymerges nothing. Human blocking feedback and merge conflicts stay outside the dispatch entirely.docs/conventions/loop-lane/README.mdto 3.0.0 (a change to the autonomy-ladder invariant is a major bump per its own changelog rule) and updatesconfig-resolution.md/babysit-loop/SKILL.md/babysit-prs/SKILL.mdin lockstep.source-controlplugin bumps to 0.32.0.Reviewer gate — closed by operator decision
An earlier revision of this body held the PR from autonomous merge while #1388 — the operator decision on whether C3 autonomous merge may exist at all — was open.
c2-mechanical. No standing C3 raise lands; the recorded reopening path is contract-first — amend the guardrail matrix to define a C3 promotion cell, then flip the seam, never the reverse. This PR makes no seam edit.autopilot+--merge c3-this-runpair, persisting nothing, ratifying no standing rung, and never reaching C4/C5.docs/conventions/loop-lane/README.mdthat was deliberately held open carries the closure record and is resolved on that operator decision.autopilotkeyword alone (legacy invocations, template expansion, and model-composed launches could activate it without a human authorizing a merge raise); the paired dedicated raise token above is the fix, and the two trigger phrases that invited model composition ("drain hard", "merge everything you can in autopilot") are removed from the skill description.Test plan
python plugins/source-control/skills/babysit-prs/scripts/tests/test_skill_contract.py— 23/23 pass at the final head.python plugins/source-control/skills/babysit-prs/scripts/tests/test_guards.py— pass at the final head.scripts/check-changed-skills.sh origin/mainandscripts/check-changelog-parity.sh --check-bump origin/main— pass at the final head.markdownlint-cli2clean on every changed markdown file.origin/mainmerged in (manifest + changelog conflicts composed, not overwritten).c2-mechanical), with the operator's explicit approval to land this PR's per-invocation exception.Related
No linked issue — this PR deliberately closes nothing.
chore: raise babysit_loop_merge rung to c3-autonomous) is not closed by this PR. Its acceptance criteria are the.claude/source-control.mdconfig edit, which this PR explicitly does not make; decision: raise babysit_loop_merge to c3-autonomous, or leave it — the guardrail matrix has no C3 auto-merge cell #1388's closure decided that edit is not made at all.c2-mechanical; this PR's exception is the operator-ratified, per-invocation path that remains.