Skip to content

fix(vibe): Stop blocking handoff on checks already red on main - #303

Merged
shafty023 merged 2 commits into
mainfrom
fix/vibe-main-only-failures
Oct 16, 2026
Merged

shafty023 merged 2 commits into
mainfrom
fix/vibe-main-only-failures

Conversation

@shafty023

@shafty023 shafty023 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Purpose

Operator ruling, 2026-10-16. When a handoff validation check fails, handoff runs the same check on the main the session captured (mainSha). If it fails there too, the session didn't cause it. It is recorded on the live ticket's Engineering checklist as a known main failure and doesn't block publishing or assignment. Only a failure that passes on the captured main and fails on the session's commit blocks. This applies in both harnesses.

In two days, three handoffs stopped on checks that were already red on main: the API path filter, the e2e volume guard and a capability-parity fixture. Each session ended with "a needed fix stops the handoff".

What changed

  • Classification (tools/vibe-workers/src/main-sync-main-comparison.ts, used by executeChecks in main-sync-checks.ts). Only at handoff, and only for a check that failed: the executor makes one disposable detached checkout of the captured mainSha and bootstraps it once with the repo's own .closedloop-ai/loops-setup.sh (the same pristine bootstrap input readiness uses). The checkout is shared by every failing check and removed in a finally (git worktree remove --force). If the cleanup can't be proven, the run refuses. Diff-relative selectors (typecheck:affected, test:affected, the pinned turbo --filter=...[sha]) run the same task over the packages the session selected, so main gets the same question. A Biome check over a file that main doesn't have can't run on main.
    • Fails on main too: main-preexisting. Publication proceeds and the result lists it under mainPreexistingFailures.
    • Passes on main: one rerun on the session commit. If it fails again it is session-caused and blocks. If it passes it is flaky, counted as passed and listed under flakyChecks.
    • Can't run on main (no bootstrap, a failed or timed-out bootstrap, a time limit, a missing file, report admission refused): session-caused. That's the conservative choice, and it blocks.
  • Bounds. Every new run (bootstrap, main run, session rerun) goes through scripts/run-bounded.mjs, with that check's own time limit (15 minutes for the bootstrap). Checks that passed are never rerun. There is at most one main run and one session rerun per failing check. The handoff recipe binds a comparison reserve (one bootstrap plus 2x each command budget) into its identity, so the Claude validation envelope covers it. validationEnvelopeMs refuses a reserve that wasn't derived from the commands. Build-loop deploys are unchanged: a failing quick check still blocks.
  • Receipt. On success, each compared command keeps its comparison: the classification, mainSha, the session run or runs and the main run, each with its log and sha256. The command receipt now admits a non-zero exit only with main-preexisting evidence. A blocked validation records failedChecks, and the next success clears it. validationMatches re-verifies every evidence log digest at push and request time.
  • Prose. quality-loop.md gains "Failures already on main", the one statement of the rule. guardrails.md ("Plugins and tooling") and the handoff skill now say a main-only failure is not a needed fix. Handoff step 7 continues on mainPreexistingFailures and passes them to step 8. The change worker returns them unchanged. The environment worker pushes as usual. The ticket template and ticket worker place each one once at the end of the Engineering checklist as - [ ] Known main failure: <check>; fails on captured main <sha>; evidence: .... It never also goes on the Pre-existing failures line and is never routed to the person. environment.md and the README describe the comparison.
  • Release. vibe 1.0.51 to 1.0.52 (both manifests; 1.0.51 went to feat(vibe): Require traced decisions for new data directions and API writes #302, merged in normally), CHANGELOG entry, rebuilt dist bundles.

Commands run

cd tools/vibe-workers && npm run typecheck && npm run build    # clean; dist rebuilt
npx vitest run src/main-sync-main-comparison.test.ts           # 7 pass; 4 fail on the 1.0.50 bundle
uv run pytest plugins/vibe -q                                  # 273 passed
node --test 'plugins/vibe/**/*.test.mjs'                       # 306 pass
uv run ruff check . && uv run pyright                          # clean
cd tools/plugin-manifests && npm test                          # 6 pass

main-sync-main-comparison.test.ts drives the real CLI bundle through the fixture. It covers:

  • a check failing on both, including typecheck:affected pinned on main to turbo typecheck --filter=app, followed by a successful push;
  • a check failing only on the session commit, which blocks with all three runs recorded;
  • a flaky check;
  • a check whose main bootstrap fails, which blocks with no rerun;
  • a build failure, which still blocks with no comparison.

Every case asserts that the main checkout is gone. On the 1.0.50 bundle the four classification cases fail. test_vibe_main_only_failures_contract.py: 6 of 6 fail on 1.0.50.

The full vitest suite ran on this laptop at a load average of 20 to 70 from other sessions. Some long fixture cases in main-sync.test.ts, main-sync-input-boundary.test.ts, main-sync-ci.test.ts and main-sync-input-references.test.ts hit their 20s and 30s timeouts. Three of them were rerun at the same load on an unchanged 1.0.50 checkout and timed out the same way there. main-sync-ci, main-sync-budget and main-sync-budget-ref-read pass when run alone. CI's TypeScript (vibe-workers) job is the authority for the full suite.

Evals, one run each, tag main-failures:

  • Claude (claude plugin eval, Sonnet): 2 of 2 here. On 1.0.50 the main-only case scored 0.67: it continued but wrote a generic "pre-existing failure" line, not a Known main failure.
  • Codex (evals/codex/run.py): 2 of 2 here. On 1.0.50 the main-only case fails on the same grader.
  • The session-caused case passes on 1.0.50 too, as expected, because 1.0.50 blocks every failure.

Operator ruling, 2026-10-16: a handoff check that also fails on the main
the session captured is not the session's failure. Three handoffs in two
days stopped on such checks (the API path filter, the e2e volume guard,
a capability-parity fixture).

- main-sync-validate reruns each failing handoff check on the captured
  mainSha in one disposable checkout bootstrapped with loops-setup.sh,
  pinning diff-relative selectors to the session's selected packages.
- Fails there too: main-preexisting, publication proceeds and the result
  lists it. Passes there: one session rerun, session-caused if it fails
  again, flaky if it passes. Cannot run on main: session-caused.
- Runs are bounded by run-bounded.mjs; passing checks are never rerun;
  the recipe binds a comparison reserve for the Claude envelope. The
  receipt keeps every run's log and digest. Build checks are unchanged.
- Handoff, quality-loop, guardrails, the ticket template and the change,
  environment and ticket workers record each main-preexisting failure
  once on the Engineering checklist as a Known main failure.
- Release vibe 1.0.51 with rebuilt bundles and both manifests.

Testing: vitest main-sync-main-comparison (7), contract test (6), vibe
pytest, node:test suites, two main-failures evals on Claude and Codex

Risks: A handoff now spends one bootstrap of the captured main when a
check fails; a check main cannot run still blocks
@shafty023
shafty023 merged commit 6f17faf into main Oct 16, 2026
10 checks passed
@shafty023
shafty023 deleted the fix/vibe-main-only-failures branch October 16, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant