fix(drive): trust an escalation only when this tick wrote it - #429
Conversation
ops/NEEDS_HUMAN.md is committed on main (082c62a, 2026-09-06) and nothing has ever deleted it. assess-gate escalated on the file's mere existence, so every drive tick since 2026-09-12 exited 75 before doing any work: PRs #417, #420, #422, #424, #426, #427 and #428 are seven consecutive cloud runs whose entire diff is that file and ops/NEXT.md. None merged. The escalation was also correct about a real defect nobody had traced: ops/autodrive.sh launched every run with `launch-gate.sh 3` while passing it the gate-2 brief, so launch-gate.sh synthesised "TARGET — gate 3" wrapped around gate-2 work on every tick. The assessors were reporting a launcher bug, once per run, for four days. Operator decision: the next gate is Gate 2, not Gate 3. - assess-gate now requires two independent signals before trusting an escalation — the file exists AND this tick wrote it. Freshness reuses the `git log --oneline main..HEAD -- <path>` idiom already used for ops/NEXT.md a few lines below, widened by the uncommitted case because per-step propagation is lossy and losing a live escalation is the worse error. A stale file is ignored loudly; a live one still exits 75. - ops/drive-assess-gate.test.mjs pins both directions, extracting the gate script from workflows/drive.yaml so the test cannot drift from it. Verified by mutation: against unmodified main the stale case fails with exit 75, reproducing the wedge. - ops/NEEDS_HUMAN.md deleted; its durable content preserved in a dated ops/STATE.md block, including the still-open question that ops/TARGET.md is synthesised into a throwaway worktree and never reaches the diff. - ops/autodrive.sh launches gate 2, matching the brief it passes. - ops/AUTODRIVE_BRIEF.md retargeted off the hn-monitor runner PR #120 already shipped, onto the open half of RFC-0001 deviation D1. - ops/NEXT.md rewritten as that Gate 2 package. - ops/STATE.md gate-2 clause 1 corrected: it claimed trigger-plane liveness was unimplemented, but PR #122 shipped it two weeks ago. That entry would have sent the next run to rebuild working code. - workflows/drive-cloud.yaml regenerated with ops/gen-drive-cloud.py; only assess-gate-1 differs semantically, the rest is pre-existing PyYAML reflow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7a17d31. Configure here.
Bugbot found a real defect in the first commit, and it was the one that mattered: the freshness check could not tell "git says this file is stale" from "git could not answer". Both printed nothing, so with no .git, with main absent, or on any git failure, a LIVE escalation classified as stale and the builder walked straight past a human decision — inverting the tradeoff the comment right above it claims to make. A sandbox is exactly where git cannot answer. SYNC_MODE=snapshot runs `git init` over an extracted tarball, so main does not exist until sync creates it, and the gate would have dropped live escalations there. The default is now to TRUST the escalation. Only a positive, SUCCESSFUL answer from git downgrades it to stale: the tree must be a repo, main must resolve, and both `git log` and `git status` must exit 0. Anything else prints ASSESS_ESCALATION_FRESHNESS_UNPROVABLE and exits 75, because ignoring a real escalation is the worse of the two errors. Exit codes are now checked rather than inferred from empty output, which also drops the `| grep -q .` that silently swallowed git's own exit status. Two tests cover the shapes Bugbot correctly noted were unexercised: no git repo at all, and a repo whose branch is not main with the escalation COMMITTED (the shape where a naive main..HEAD prints nothing and the file looks stale). Both fail against 7a17d31 and pass here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review swarm: maintainabilityNo fresh transcript was produced for run |
Review swarm: historyNo fresh transcript was produced for run |
Review swarm: structureNo fresh transcript was produced for run |
|
🎯 review-swarm: FAILED (M:fail H:fail S:missing) Lens transcripts posted as sibling comments above. |
Review swarm: FAILED
Cloud run: |

The loop has been dead-locked since 2026-09-12
ops/NEEDS_HUMAN.mdis committed onmain(added 2026-09-06,082c62aa).assess-gateescalated on the mere existence of that file. Nothing in the repo has ever deleted it — norm, nogit rm, andgit log --diff-filter=Dover that path is empty — andops/launch-gate.shbuilds each run's worktree fromorigin/mainwithout stripping it.So every drive tick escalated and died at the gate before doing any work. PRs #417, #420, #422, #424, #426, #427 and #428 are seven consecutive cloud runs whose entire diff is
ops/NEEDS_HUMAN.mdandops/NEXT.md, re-litigating the same conflict. None merged. A full cloud run was burned on each.What the escalation actually said, and the root cause nobody had found
The escalation reported that
ops/TARGET.mdpinned the run to gate 3 while describing gate 2 hn-monitor work that PR #120 had already merged, and thatops/NEXT.mddescribed unrelated gate-3 review-swarm documentation. The assessor could not tell which was real and asked for a human decision.It was right, and it was reporting a real defect — once per run, for four days.
ops/autodrive.shlaunched every run withsh ops/launch-gate.sh 3 "$brief"while passing itops/AUTODRIVE_BRIEF.md, whose content is gate-2 work. The launcher synthesised# TARGET — gate 3wrapped around a gate-2 brief, on every single tick. The contradiction was manufactured fresh each run.Operator decision: the next gate is Gate 2, not Gate 3. This matches
ops/AUTODRIVE_BRIEF.md:67and the gate ladder inops/STATE.md.What this PR does
1. Unwedges. Resolves the escalation and deletes
ops/NEEDS_HUMAN.mdfrommain. Its durable content is preserved in a dated block inops/STATE.md— the file an assessor with no git history reads instead — including the one question that outlives the gate-2/3 confusion:ops/TARGET.mdis synthesised per run into a throwaway worktree and is never in the delivered diff, so escalations keep quoting a file no reviewer can open. Review lenses failed #422 and #428 for exactly that. Recorded as still-open.2. Makes escalation non-sticky.
assess-gatenow trustsops/NEEDS_HUMAN.mdonly when two independent signals agree: the file exists and this tick wrote it. Freshness reuses the idiom already in the file a few lines below (git log --oneline main..HEAD -- <path>), widened by the uncommitted case, because propagation between per-step sandboxes is lossy and assess may write the file without committing it. Losing a live escalation is the worse of the two errors, so a dirty working-tree file still parks the run. Same principle as cloud #3671.A stale file now prints
ASSESS_STALE_NEEDS_HUMAN_IGNOREDand the tick proceeds. A live one still printsASSESS_BLOCKED_NEEDS_HUMANand exits 75.3. Fixes the three stale sources so the next assessor is not re-confused:
ops/autodrive.sh— launches gate 2, matching the brief it passes.ops/AUTODRIVE_BRIEF.md— retargeted off the already-merged hn-monitor runner.ops/NEXT.md— rewritten as a Gate 2 package.ops/STATE.md— clause 1 was stale and dangerous: it said trigger-plane liveness was "Not implemented insiderelayflowd", but PR feat(kernel): trigger-plane liveness sweep — RFC-0001 gate 2 done-when #122 shipped it two weeks ago (server/liveness.rs+tests/subscription_liveness.rs). That entry would have sent the next run to rebuild working code — the third time an escalation proposed rebuilding something already merged.4. The new work package is genuinely open work. I checked before writing it: liveness (#122) and the hn-monitor runner (#120) are both shipped. The remaining Gate 2 blocker is RFC-0001's own deviation D1, half-closed by PR #252 — the journal scan error propagates, but rule 10a's split by cause was never implemented and
wake_context_unresolvedappears nowhere in the kernel.resolve_wake_context's own doc comment says so: "none of that classification exists yet." D2 stays out of scope: the RFC sequences it rollover → D2 → gate 2 and says D2 is not implementable in isolation.Verification — both directions, by execution
New test
ops/drive-assess-gate.test.mjsextracts the gate script out ofworkflows/drive.yamlrather than copying it, so it fails if the workflow changes and the test does not.Verified by mutation — the same test against the unmodified
maingate reproduces the wedge:A gate that cannot fire is as broken as one that always fires, so the live-escalation direction is pinned by two of the four cases.
node --test ops/drive-assess-gate.test.mjsnode --test ops/local-work-*.test.mjs(5 suites)node --test scripts/cloud-artifact.test.mjsnode --test scripts/publish.test.mjspython3 -c "yaml.safe_load(...)"on both drive YAMLssh -nonautodrive.sh,launch-gate.sh,deliver-run.shvalidateNextWorkPackage(ops/NEXT.md){"accepted":true}flows checkwas deliberately not run against these workflows. Perworkflows/README.md:12-13andops/RUNTIME-STATUS.md:123-126,drive.yaml/drive-cloud.yamlare the previousrelayflowsengine and all five legacy YAMLs returnexit 2 REFUSED [invalid_spec]. That refusal is expected and is not a defect to fix.On the
drive-cloud.yamldiff sizeworkflows/drive-cloud.yamlis generated, so it was regenerated withpython3 ops/gen-drive-cloud.pyrather than hand-edited. Most of its ~199-line diff is pre-existing drift: the committed file onmainalready did not match its own generator's output, because a different PyYAML version wrapped the literal blocks at different columns. Regenerating from unmodifiedmainreproduces that reflow with no change of mine involved.Proven semantically clean by parsing both and comparing structures:
Only the gate step differs. Every other step is identical after parsing.
Review response — Bugbot found a real defect (fixed in
89ff6ce3)Bugbot's check conclusion was
neutral, but its summary said "found 1 potential issue" and the finding was correct, so it is recorded here rather than waved through.The finding: the freshness check could not distinguish "git says this file is stale" from "git could not answer". Both print nothing, so with no
.git, withmainabsent, or on any git failure, a live escalation classified as stale and the builder continued past a human decision — inverting the exact tradeoff the comment above it claims to make. Bugbot also correctly noted the tests never exercised a tree where git is absent ormainis missing.Why it mattered more than it looks: a sandbox is precisely where git cannot answer.
SYNC_MODE=snapshotrunsgit initover an extracted tarball, somaindoes not exist until sync creates it. The first commit would have dropped live escalations in exactly the environment this gate exists to serve.The fix: the default is now to trust the escalation. Only a positive, successful answer from git downgrades it to stale — the tree must be a repo,
mainmust resolve, and bothgit logandgit statusmust exit 0. Anything else printsASSESS_ESCALATION_FRESHNESS_UNPROVABLEand exits 75. Exit codes are now checked rather than inferred from empty output, which also removes the| grep -q .that swallowed git's own exit status.Two tests cover the shapes Bugbot named. Both fail against
7a17d31band pass here:Other automated reviewers produced no signal
Recorded so their greens are not mistaken for review:
NEUTRAL; its summary says the monthly line limit is exhausted and reviews resume 1 October. It reviewed nothing.Not done here
ops/drive-assess-gate.test.mjsis not wired into CI — no CI workflow runsops/*.test.mjstoday, and adding one means editing.github/workflows/, which AGENTS.md forbids to the author of the work it would judge. Flagging rather than doing it.ops/autodrive.sh,ops/AUTODRIVE_BRIEF.mdandops/STATE.mdare listed inops/IMMUTABLE_PATHS. That list exists to stop a drive run silently rewriting the guards and ground truth that judge its own output. This is an operator-directed repair of those exact files, made by hand and not delivered byops/deliver-run.sh, and the launcher defect could not be fixed without touching one of them. Calling it out explicitly rather than letting it pass unnoticed.Status
HOLD — do not merge. Awaiting operator authorization.
🤖 Generated with Claude Code
Note
Medium Risk
Changes drive-loop gating and ops ground truth that every autodrive tick depends on; incorrect freshness logic could either re-wedge the loop or skip real human blocks. New tests are not wired into CI per the PR description.
Overview
Unwedges the autodrive loop after seven consecutive ticks died at assess-gate because committed
ops/NEEDS_HUMAN.mdtriggered escalation on file existence alone.assess-gate now treats
ops/NEEDS_HUMAN.mdas live only when this tick wrote it (committed inmain..HEAD, uncommitted dirty file, or fail-safe unprovable when git cannot answer). Stale escalations emitASSESS_STALE_NEEDS_HUMAN_IGNOREDand the tick continues; live ones still exit 75 withASSESS_BLOCKED_NEEDS_HUMAN. The same logic lands inworkflows/drive.yamland regeneratedworkflows/drive-cloud.yaml.Ops alignment: removes
ops/NEEDS_HUMAN.md, archives the wedge story inops/STATE.md(including marking trigger-plane liveness closed per PR #122), retargetsops/AUTODRIVE_BRIEF.mdandops/NEXT.mdto gate 2 / RFC D1 (kernelwake_context_unresolved— not implemented in this diff), and fixesops/autodrive.shto calllaunch-gate.sh 2so the launcher gate matches the brief.Tests: adds
ops/drive-assess-gate.test.mjs, which extracts the assess-gate script fromworkflows/drive.yamland covers stale vs live escalation paths (including no-git / no-main sandbox shapes).Reviewed by Cursor Bugbot for commit 89ff6ce. Bugbot is set up for automated code reviews on this repo. Configure here.