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. |
…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 👍 / 👎.
There was a problem hiding this comment.
This reply was generated by AI.
Addressed in #1848 (commit 1f70ec8b), which lands the fix this finding asked for.
This finding was stranded — it posted after #1405 merged, so the thread-resolution gate never saw it. It was recovered by the stranded-findings sweep, re-verified against origin/main, and triaged as item 4 of #1786.
The fix: babysit-loop/SKILL.md's dimension-flooring rule was scoped only to "Before invoking" the babysit-prs tier, and reference/pre-escalation-dispatch.md contained no occurrence of dimension at all. Resolving review threads is an exercise of autonomy dimension 3, so the flooring rule now binds every capability the cycle exercises rather than only the tier keyword it passes on: a floored thread-resolution dimension withholds the dispatch outright and the PR escalates, reported as override-constrained — never dispatched and then narratively told not to resolve. The reference doc gains a gating section stated ahead of everything else in the file. New eval 6 covers exactly the autopilot --merge c3-this-run --thread-resolution safe shape this finding named.
…#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>
…1318/#1320/#1405 Five review findings landed on those PRs after each merged, so the thread-resolution gate never saw them. All five re-verified live against origin/main before fixing. claude-config / audit-pass run-contract: - Lock reclamation gains a liveness conjunct where the platform exposes no process start identity: the lock now records the holder's run id, and the holder's lease -- classified by the contract's existing two-sided test -- decides. A live lease defers the reclaim instead of losing the lock, so assertion 3.1 holds for a run that legitimately exceeds 30 minutes. Same remedy shape as claude-ops' restart-consumer (#1759/#1760), whose deferral needs a hard ceiling only because its holder publishes no lease. Assertions 3.12/3.13, eval 27. - The matching table gains an anchor-collision clause, tested ahead of the anchor comparison and routed to the existing OLD CLOSED, NEW OPENED disposition, so 1.10a's "no suppression carries forward" is reachable rather than contradicted by the SAME, UNCHANGED row. Assertion 4.7, eval 28. loop-lane prompts and convention: - The prompts file stripped the discipline preamble from every dispatch brief and asserted that dispatched subagents inherit the root sweep's posture, which fresh-context subagents cannot. Reconciled toward the normative owner doc: every dispatch brief carries the preamble; the narrow exemption is sweep-all's own audit forks. The preamble does not recurse -- at a subagent's conversation start that skill reports its posture digest with no audit fan-out. The owner doc drops its hand-copied discipline enumeration, which its own no-enumeration rule forbade. source-control / babysit-loop: - The pre-escalation resolution dispatch is gated on the resolved thread-resolution dimension: dimension flooring binds every capability the cycle exercises, not only the tier keyword handed to babysit-prs, so --thread-resolution safe withholds the dispatch instead of narrating at it. Eval 6. - The dispatch names --independent-resolver as its mode, maps the D7.5 ledger onto that mode's validated evidence flags, and names the two thread shapes it refuses (multi-finding, severity-flagged), which escalate. As written the implied --autonomous could never clear the current non-outdated thread class the dispatch exists for. Eval 7. Refs #1786 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVoZoMYXqf8ZVbQYixPVPW
…1318/#1320/#1405 Five review findings landed on those PRs after each merged, so the thread-resolution gate never saw them. All five re-verified live against origin/main before fixing. claude-config / audit-pass run-contract: - Lock reclamation gains a liveness conjunct where the platform exposes no process start identity: the lock now records the holder's run id, and the holder's lease -- classified by the contract's existing two-sided test -- decides. A live lease defers the reclaim instead of losing the lock, so assertion 3.1 holds for a run that legitimately exceeds 30 minutes. Same remedy shape as claude-ops' restart-consumer (#1759/#1760), whose deferral needs a hard ceiling only because its holder publishes no lease. Assertions 3.12/3.13, eval 27. - The matching table gains an anchor-collision clause, tested ahead of the anchor comparison and routed to the existing OLD CLOSED, NEW OPENED disposition, so 1.10a's "no suppression carries forward" is reachable rather than contradicted by the SAME, UNCHANGED row. Assertion 4.7, eval 28. loop-lane prompts and convention: - The prompts file stripped the discipline preamble from every dispatch brief and asserted that dispatched subagents inherit the root sweep's posture, which fresh-context subagents cannot. Reconciled toward the normative owner doc: every dispatch brief carries the preamble; the narrow exemption is sweep-all's own audit forks. The preamble does not recurse -- at a subagent's conversation start that skill reports its posture digest with no audit fan-out. The owner doc drops its hand-copied discipline enumeration, which its own no-enumeration rule forbade. source-control / babysit-loop: - The pre-escalation resolution dispatch is gated on the resolved thread-resolution dimension: dimension flooring binds every capability the cycle exercises, not only the tier keyword handed to babysit-prs, so --thread-resolution safe withholds the dispatch instead of narrating at it. Eval 6. - The dispatch names --independent-resolver as its mode, maps the D7.5 ledger onto that mode's validated evidence flags, and names the two thread shapes it refuses (multi-finding, severity-flagged), which escalate. As written the implied --autonomous could never clear the current non-outdated thread class the dispatch exists for. Eval 7. Refs #1786 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVoZoMYXqf8ZVbQYixPVPW
…1318/#1320/#1405 (#1848) ## Summary Closes the five stranded review findings triaged in #1786 — findings that landed on #1318, #1320, and #1405 *after* each merged, so the thread-resolution gate never saw them. Each was re-verified against `origin/main` before being fixed; none was refuted, and none was already fixed. **1 — Lock reclamation had no liveness conjunct where the platform exposes no process start identity (P1).** `claude-config/skills/audit-pass/reference/run-contract.md` said *"age alone reclaims"* there, and mentioned the lease heartbeat only as prose no reclamation test consulted — so a `--fix` run legitimately exceeding 30 minutes lost its lock to a second run, against the contract's own assertion 3.1 (*"exactly one proceeds"*). The lock now records the holder's **run id**, and reclamation reads that holder's lease and applies the contract's *existing* two-sided liveness test as the second conjunct: a stale, `released`, missing, or unreadable lease reclaims; a **live** lease refuses exactly as it would inside the 30-minute window. Same remedy shape as `claude-ops`' restart-consumer (#1759/#1760) — cited by its actual heading phrase, *"age alone never reclaims"* — with the one deliberate divergence stated at the site: restart-consumer needs a hard 24-hour deferral ceiling because its holder publishes no lease; a lease-bearing run needs none, because a dead holder stops heartbeating within five minutes. New assertions 3.12 / 3.13, new eval 27. **2 — The `SAME, UNCHANGED` table row had no anchor-collision exception (P2).** A collided site's anchor is by construction *unchanged* (the occurrence discriminator digests the heading path), so a previously-suppressed excerpt that later gained an identical duplicate matched row 1 exactly and silently re-suppressed itself — contradicting assertion 1.10a's *"no suppression carries forward"*. Collision is now tested **ahead of** the anchor comparison in every row and routes to the existing `OLD CLOSED, NEW OPENED` disposition (entry stale per 4.2, finding unsuppressed, collision named with its occurrence count), reusing the section's established fail-closed answer rather than adding a fifth disposition. New assertion 4.7, new eval 28. **3 — The prompts file stripped the discipline preamble from every dispatch brief (P2).** `prompts/loops/loop-lane-prompts.md` forbade the sweep *"inside a dispatch brief"* and claimed dispatched subagents *"inherit the posture the root sweep already set"* — which a fresh, non-inherited context cannot — directly contradicting the normative owner doc (`docs/conventions/loop-lane/README.md`, "Subagent discipline preamble"). Reconciled **toward the owner doc**, as the issue's acceptance criterion required: every dispatch brief carries the preamble; the one recursion still forbidden is narrowed to `sweep-all`'s own audit forks. Verified against the skill itself — at a subagent's conversation start `sweep-all` reports its cheap posture digest and runs no audit fan-out, and its preflight degrades to that digest when the fan-out cannot inherit a conversation (a fresh subagent never can), so the preamble costs one skill read per dispatch and does not recurse. The owner doc also drops its hand-copied enumeration of the individual disciplines, which its own no-enumeration rule forbade. **4 — The resolver dispatch ignored per-dimension overrides (P1).** The *"Dimension overrides bind by tier flooring"* rule in `babysit-loop/SKILL.md` was scoped only to *"Before invoking"* the babysit-prs tier, and `reference/pre-escalation-dispatch.md` contained no occurrence of `dimension` at all — so `autopilot --merge c3-this-run --thread-resolution safe` still dispatched a fresh subagent to mutate bot threads the operator's own argument had just denied, against `config-resolution.md`'s *"invocation arguments win"*. Resolving review threads **is** an exercise of autonomy dimension 3, so the flooring rule now binds every capability the cycle exercises, not only the tier keyword it passes on: a floored thread-resolution dimension withholds the dispatch outright and the PR escalates, reported as override-constrained — never a dispatch made and then narratively told not to resolve. New eval 6. **5 — The dispatch named no resolver mode, and the implied one could not work (P2).** As written, *"the full per-PR worker lifecycle"* implied `--autonomous`, which hard-refuses any thread not already `isOutdated` before its own push — precisely the current, non-outdated bot thread D7.5 routes to this dispatch. The mode is now stated as **`--independent-resolver`**, with the D7.5 ledger mapped onto that mode's machine-validated evidence flags, and the two thread shapes it refuses named where the dispatch meets them (multi-finding, severity-flagged — both escalate). The worker-lifecycle sentence is scoped to how a *code change* is made rather than to mode selection. New eval 7. **Issue AC item 4 — checked before any work started.** `9df80ba5aa` is **not** an ancestor of `origin/main`, but `--independent-resolver` **is** present on `origin/main` (`babysit-prs/scripts/babysit_resolve_thread.py`), so the mode landed by another route and finding 5 is not done twice. Every flag, constraint, and refusal this PR asserts about that mode was verified against the script by an independent reader: flag spellings, the `--disposition` requirement, the single-pinned-`--thread-id` requirement, the `--autonomous` / `--include-human` / `--allow-unpinned-thread` conflict set, and the `isOutdated` refusal. **Already fixed — recorded so nobody re-checks it.** The sixth triaged item, #1320:475 (the unbounded babysit-loop launch command), was fixed by `d8575516cd` in #1405; the generic command carries `{{MERGE}}` and both remaining invocation examples carry an explicit merge cap. No change here. **Not in this PR.** The issue's last acceptance criterion — resolving the five threads on #1318 / #1320 / #1405 — can only happen once these fixes land, so it is a post-merge follow-up on #1786's own thread rather than a change in this diff. ## Test plan Every surface here is documentation and contract prose, so the executable regression net is the repo's own gates plus the skills' eval sets. All run locally against this branch: - `python3 scripts/check-contract-clause-coverage.py` — **passed** (4 canonical surfaces, 14 tagged restatements, 10 pointing surfaces). It initially **failed** on this branch, catching an untagged restatement of `D7.5-thread-eligibility` the new resolver-mode section had introduced; reduced to a pointer, which the gate's own guidance calls the stronger fix. - `scripts/check-changed-skills.sh origin/main` — **PASS** for the changed skills, 0 errors (`audit-pass` 2 warnings, `babysit-loop` 1 warning; all pre-existing soft-target line-count and fresh-eyes notes). `babysit-loop/SKILL.md` is 499/500 lines, inside the hard cap — the base itself is at 499, so the net-new prose was reflowed and the dimension-flooring detail pushed down to `reference/pre-escalation-dispatch.md`, which already owns it, rather than restated twice. `babysit-prs` reports one failure, `engine.test.sh`, which is **environment drift and not this branch**: it fails identically on the unmodified base with 82 ruff findings, and under CI's pinned `ruff==0.15.22` (run via `uvx`) the same tree reports `All checks passed!`. Local ruff is 0.16.0, and CI agrees with the pin — the `ci` lane is green. - `scripts/check-changelog-parity.sh` `--check`, `--check-order`, `--check-bump origin/main` — all **passed**; both bumped plugins carry a matching entry and every changelog reads newest-first with no duplicate versions. - `scripts/validate-plugins.sh` — **passed** (all plugin manifests and the catalog). - Eval sets validated against `plugins/skill-quality/reference/evals.schema.json` — **passed**. - `scripts/check-orphaned-fixtures.sh --check` and `scripts/check-contract-slice-prune.sh --check` / `--check-diff origin/main` — **passed**. - `markdownlint-cli2` over every changed tree — **0 issues**. - `typos --config _typos.toml` — **clean**. New regression coverage added with the fixes: `audit-pass` evals **27** (a live lease refuses the lock past the age bound; a stale, missing, or unreadable one reclaims) and **28** (an anchor collision never carries a suppression forward); `babysit-loop` evals **6** (`--thread-resolution safe` withholds the resolver dispatch entirely) and **7** (the dispatch runs `--independent-resolver`, and refuses a multi-finding thread). Two defects were found in the in-flight work and fixed before this PR opened, both by checking the artifact rather than the claim: a **duplicate assertion id `4.6`** in run-contract.md's §4 table (the new row now appends as `4.7`, matching the table's append-at-end convention), and a **fabricated section citation** — restart-consumer.md was cited as *"the lock reclaims"*, a phrase that does not appear in it; corrected to its actual bolded phrase *"age alone never reclaims"*, and the accompanying claim that a live holder *"never loses the lock"* corrected, since restart-consumer does impose a hard 24-hour ceiling. ## Round 2 — review findings, and a rebase The second commit (`e6b91b4c`) addresses three review findings from the first round; each carries a per-thread reply with its disposition. All three were valid. - **P1, run-contract.** A lock persisted by an earlier plugin version carries no run id, so the new lease-locating conjunct could not be constructed for it — and treating that unlocatable lease as stale fell straight back to age-only reclamation, the exact failure this change removes, at the moment it is most likely (an operator who just upgraded and is re-running). Fixed by establishing the conjunct the other way round for such a lock: enumerate every lease under `runs/<state-key>/`, and any one live by the same two-sided test defers the reclaim. Over-deferral on a live read-only lease is stated as the deliberate fail-closed direction and is bounded by that lease's own release/staleness. **New assertion 3.14.** - **P2, `babysit-prs/reference/safety.md`.** The section titled *"Security/P1 escalation: the one named exception"* cast this resolver as that exception, citing the loop-lane convention's §1 — but §1's exception widens the **merge rung** for a single run and never touches severity, while the wrapper refuses a severity-flagged thread in every unattended mode. The documented exception was unreachable, and it contradicted the refusal this PR had just made explicit. Retitled and reframed: the bright line has no exception, the typed pair unlocks the dispatch *path*. `babysit-prs/SKILL.md`'s one-line restatement corrected to match. - **P2, `babysit-loop/SKILL.md`.** The surface that actually builds dispatch briefs still hand-copied the discipline plugin's membership inline — the enumeration this PR had just removed from the owner doc. Now points at the sweep skill, which resolves its own membership. **Rebased onto current `main`.** The branch was several commits behind, and the round-2 push had triggered only the `pull_request_target` workflows, leaving the full `ci` suite unrun against the new code; rebasing forced a clean re-trigger. Two upstream commits had touched the same two files this PR edits (`babysit-loop/SKILL.md`, `babysit-prs/reference/safety.md`) — both auto-merged, and the merged result was re-read rather than assumed. Version bumps re-derived against the moved base: `source-control` 0.42.4 → **0.44.1**; `claude-config` stays **0.16.1**. Every gate above was re-run against the rebased head, and all **32 PR checks are green**, `ci` included. ## Related Fixes #1786 Refs #1318 Refs #1320 Refs #1405 Refs #1777 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…sit-loop C5 trust test (#1830) ## Summary Resolves the reserved design decision on #1525 (decision comment with delegated-authority statement, chosen design, rejected alternatives, and cross-vendor review: #1525 (comment)) and implements it, which also fixes #1520's misclassification of repository-owned automation identities. - **New team-tracked loop-lane key `babysit_loop_trusted_internal_bot_logins`** (`config-resolution.md`: key-table row plus a dedicated "the C5 trust test's one reviewed widening" section). A flat Markdown bullet list of exact GitHub App bot logins; honored **only** from the TARGET repository's team-tracked `.claude/source-control.md` (default branch when the checkout differs), so every trust grant is a recorded, reviewable, versioned config change; appearances in the user-global layer, local overlay, or invocation arguments are ignored and reported; unset/unreadable/malformed **fails closed** to the empty set. - **The babysit-loop C5 trust test gains a second positive arm** (`babysit-loop/SKILL.md` rung partition): `authorAssociation` `OWNER`/`MEMBER`, or a structural bot (`[bot]` suffix / provider `Bot` type) whose login matches a listed entry. Neither arm positively passing is C5, fail closed, exactly as before. The fork test stays independent (a listed bot authoring from a cross-repository head is still C5), and the key is consumed by the trust test and nowhere else. - **Composition with the dependency-manager hold:** independent, and the hold wins on intersection — the trust key affects classification only; a login also in the built-in dependabot/renovate set or `babysit_extra_dependency_manager_logins` is still never merged autonomously. `babysit_watched_owners` remains never a trusted-author list (per the acceptance criteria, the key does not reuse it). - **Convention + changelog in lockstep:** loop-lane convention README's C4/C5 floor paragraph now admits the tracked-seam internal-bot attestation; convention CHANGELOG bumped to 7.0.0 (major, with bump-ambiguity rationale per the convention's own rule); plugin CHANGELOG 0.43.0 + `plugin.json` bump. - **Eval added** (`babysit-loop/evals/evals.json` id 6) covering the bot-author cases: listed same-repo bot (not C5), listed bot from a fork (still C5), unlisted bot (C5), listed dependency manager (not C5 but held from merge), overlay-only listing (ignored; C5). To keep `babysit-loop/SKILL.md` under the 500-line hard cap, the trust-test bullet drops the parenthetical `babysit-prs` "Scope resolution" citation for the watched-owners rule — the rule itself, with its rationale, remains stated in the SKILL, `config-resolution.md`, and the convention. ## Test plan - `scripts/check-changed-skills.sh origin/main` — PASS (babysit-loop; 499/500 lines, description trigger phrases preserved). - `scripts/check-changelog-parity.sh --check`, `--check-bump origin/main`, `--check-order` — all PASS (71 changelogs, version/entry parity, newest-first order). - `markdownlint-cli2` on all five changed markdown files — 0 errors. - `evals.json` validated against `plugins/skill-quality/reference/evals.schema.json` — valid. - `typos` and `editorconfig-checker` on the changed paths — clean. ## Related - Fixes #1525 - Fixes #1520 - Deferred from PR #1405 review (Codex thread "Preserve trust for repository-owned bot authors"). - Concurrency note: authored on a branch based directly on `origin/main` while sibling sessions work #1660 and #1641 on their own branches. Those issues target `babysit-prs/reference/safety.md` and `orchestration.md` (non-convergence tripwire durability; worker-tier disproved-thread retirement) — this diff touches neither file, so no overlap with the regions they describe is expected; the shared `plugins/source-control/CHANGELOG.md` head and `plugin.json` version are the one likely merge-order conflict point (version renumber on whichever lands second). - This session may not merge, and does not remove `needs-human`: merge and decision ratification stay with a human reviewer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C --------- Co-authored-by: Claude Fable 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.