Skip to content

fix(vibe): ship the 1.0.75 fleet lessons release - #336

Merged
shafty023 merged 6 commits into
mainfrom
fix/vibe-lessons-20261022
Oct 22, 2026
Merged

shafty023 merged 6 commits into
mainfrom
fix/vibe-lessons-20261022

Conversation

@shafty023

@shafty023 shafty023 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Purpose

Ship the open vibe lessons ledger entries from the 2026-10-21 and 2026-10-22 fleet runs as one pull request and one vibe 1.0.75 release, with a mechanical check wherever one fits. While checking the watcher's handling of unloaded threads, this also fixes a watcher bug: run as a command, liveness.mjs printed READFAIL for every failed turn.

Release metadata

  • One changelog entry: vibe v1.0.75.
  • One version bump: vibe 1.0.74 to 1.0.75 in both manifests.
  • Contract coverage for the release: plugins/vibe/tools/python/test_vibe_1075_lessons_contract.py.
  • Ledger entries already encoded by 1.0.74 or earlier (long foreground runs, touched-surface VQA findings, the orchestrator practices, the product-question research order, overnight transient recovery, the routeSpecs gap) get no new code; the ledger marks them with their release.

Affected files

  • codex-fleet-monitor: scripts/liveness.mjs, scripts/classify.sh, scripts/snapshot.sh, scripts/watch.sh, their tests, SKILL.md.
  • fleet-orchestrator: scripts/board.mjs, scripts/helpers.mjs, scripts/codex-session.mjs, scripts/review-page-check.mjs, new scripts/lessons-pr.mjs, their tests, SKILL.md, references/templates.md.
  • handoff: scripts/executed-check.mjs, scripts/rulings-check.mjs and their tests, SKILL.md; vibe: scripts/session-questions.mjs, scripts/session-plan.mjs and their tests, SKILL.md; agents: vibe-change-worker.md, vibe-ticket-worker.md.

Prevention closeouts

Watcher printed READFAIL for every failed turn

Prevention

  • Class: ran await main() at the top level above the constants it uses, so the module paused before they were initialized and classifying a failed turn threw, causing usage limits, transient failures and system errors to read as READFAIL.
  • Instances: fixed plugins/vibe/skills/codex-fleet-monitor/scripts/liveness.mjs | none found elsewhere.
  • Search: rg -n "await main\(" plugins, then a scan for top-level const, let or class below it.
  • Rung: 2 via test_no_plugin_script_declares_a_constant_below_its_top_level_await_main (scans every plugin .mjs), plus a rung 3 command-level test in liveness.test.mjs that reads a usage-limit turn as LIMIT.
  • Proof: with main's liveness.mjs restored the scan names Failure and the other constants, and moving the entry point back fails the command test.
  • Why not higher: module evaluation order is not a type state.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Finished writer unloaded by the App Server (notLoaded)

Prevention

  • Class: a finished writer whose thread the App Server unloaded could go unread if the watcher treated notLoaded as unreadable or dropped its final message.
  • Instances: none found; a read-only probe of the live App Server showed unloaded threads still return their completed turn and items, and liveness.mjs reads them IDLE with the tail.
  • Search: rg -n "notLoaded" plugins/vibe; live thread/read, thread/turns/list and thread/items/list on idle and unloaded threads.
  • Rung: 3 via a liveness.test.mjs case that reads the same finished turn as idle and as notLoaded and requires identical IDLE ... LAST lines through classify.sh.
  • Proof: skipping the item read for notLoaded threads fails the case.
  • Why not higher: App Server behavior is external; the test pins our reading of it.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Writers waited on the orchestrator while the operator was away

Prevention

  • Class: flagged NEEDS-REPLY only when an idle writer's final message ended in a question, causing a writer that asked "May I ...?" and ended on a status sentence to wait unread.
  • Instances: fixed scripts/liveness.mjs needsReply | a live idle tail showed the same shape.
  • Search: rg -n "needsReply|NEEDS-REPLY" plugins/vibe
  • Rung: 3 via liveness.test.mjs: a question or a permission, authorization or approval request on any line is NEEDS-REPLY; plain reports and URLs with query strings are not.
  • Proof: restoring the end-only pattern fails the case.
  • Why not higher: whether a message asks something is text, not a type.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Approved PRs blocked by unresolved review threads

Prevention

  • Class: read an approved PR with auto-merge armed as done, causing new unresolved review threads to block its merge for hours while the block was mistaken for a missing approval.
  • Instances: fixed codex-fleet-monitor/scripts/snapshot.sh and the monitor SKILL.md table | listed for follow-up: vibe shepherd.mjs (the writer side of polling until merged).
  • Search: rg -n "reviewThreads|isResolved|reviewDecision" plugins/vibe/skills
  • Rung: 3 via the watched PR line: threads=<n> unresolved threads, and BLOCKED(unresolved threads) on an open PR with any, apart from the review decision and checks.
  • Proof: with the flag removed, the snapshot test with two unresolved threads on an approved PR fails.
  • Why not higher: thread state lives on the code host.
  • Follow-up: have shepherd.mjs stop on a new unresolved thread so the writer acts on it.
  • Event recorded: vibe lessons ledger.

A writer waited for authorization it did not need

Prevention

  • Class: asked permission to repair tests its own change broke, causing a writer to sit idle on in-scope work.
  • Instances: fixed scripts/session-questions.mjs, agents/vibe-change-worker.md, skills/vibe/SKILL.md; the watcher side is the NEEDS-REPLY change above.
  • Search: rg -n -i "authoriz|permission|NEEDS_PERSON" plugins/vibe/agents plugins/vibe/skills/vibe
  • Rung: 3 via session-questions.mjs ask, which every question to the person goes through and which refuses a permission request for a test repair, main merge, push, rerun or fix while flags, product calls, destructive actions and credentials still pass; rung 4 line in the change worker.
  • Proof: removing the refusal fails session-questions.test.mjs.
  • Why not higher: the worker's status is free text at this boundary.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Asked product questions the code could answer

Prevention

  • Class: drafted operator questions without checking the schema first, causing questions the code already answers (an owner that comes from the code host) to reach the operator.
  • Instances: fixed scripts/review-page-check.mjs, references/templates.md, fleet SKILL.md; vibe's side is session-questions.mjs ask --product --code-evidence from 1.0.74.
  • Search: rg -n "code-evidence|checked in code|D###" plugins/vibe
  • Rung: 3 via review-page-check.mjs: a D### row needs checked in code: <file>:<line>.
  • Proof: disabling the decision-row check fails review-page-check.test.mjs.
  • Why not higher: questions asked outside the review page cannot be intercepted by a script.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

PRD marked EXECUTED with open children

Prevention

  • Class: marked a PRD executed without reading its children, causing four open children to sit under an executed PRD.
  • Instances: fixed skills/handoff/scripts/executed-check.mjs, fleet SKILL.md, agents/vibe-ticket-worker.md (plans pass --kind plan).
  • Search: rg -n "executed-check|EXECUTED" plugins/vibe/skills plugins/vibe/agents
  • Rung: 1 via the check's interface: it requires --kind, and --kind prd cannot run without --children, which it refuses while any child is not DONE or CANCELED.
  • Proof: dropping the children check fails executed-check.test.mjs.
  • Why not higher: this is the interface rung.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Codex sessions started without an explicit model

Prevention

  • Class: started Codex sessions without a model or effort, causing writers on a much slower model and on the default effort.
  • Instances: fixed scripts/codex-session.mjs (a new session needs a model as well as an effort) and liveness.mjs plus classify.sh plus snapshot.sh (MODEL(...) drift against CODEX_MODEL and CODEX_EFFORT, local and remote).
  • Search: rg -n -- "--model|--effort|CODEX_MODEL|CODEX_EFFORT" plugins/vibe/skills
  • Rung: 1 via codex-session.mjs refusing a new session without a model; rung 3 via the watcher's drift word, which also catches threads started outside fleet.sh.
  • Proof: removing the model requirement fails codex-session.test.mjs; removing the drift or dropping it in classify.sh fails liveness.test.mjs.
  • Why not higher: threads started by another tool cannot be refused, only reported.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Codex usage reset rule

Prevention

  • Class: waited at a Codex usage band near the limit without telling the operator about available resets or moving work while they were away.
  • Instances: fixed fleet SKILL.md (Moving work) and the monitor's HIGH row.
  • Search: rg -n "MOVE_AT_PERCENT|resets=" plugins/vibe/skills
  • Rung: 4 via fleet SKILL.md; the usage line already carries resets=<n>.
  • Proof: advisory rung, no proof possible; the contract test pins the wording.
  • Why not higher: whether the operator is away is judgment.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Main's post-merge health went unwatched

Prevention

  • Class: stopped the worker that owned a standing duty without naming a replacement, causing merges to land with nobody watching main.
  • Instances: fixed scripts/board.mjs (duty, UNOWNED-DUTY), scripts/helpers.mjs (refuses to stop an owner), fleet SKILL.md.
  • Search: rg -n "stopped|duty|shepherd" plugins/vibe/skills/fleet-orchestrator
  • Rung: 1 via helpers.mjs stopped refusing a duty owner; rung 3 via board.mjs status.
  • Proof: removing the refusal or the duty check fails board.test.mjs.
  • Why not higher: a worker ended outside the helper ledger is reported, not prevented.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Watcher started with & again

Prevention

  • Class: started watch.sh with & in a foreground shell, causing a stray second watcher (a repeat).
  • Instances: fixed scripts/watch.sh, monitor and fleet SKILL.md.
  • Search: rg -n "watch.sh" plugins/vibe/skills
  • Rung: 1 via a per-work-dir lock: a second copy exits 3 and says how to re-arm; a dead holder's lock is taken over.
  • Proof: removing the lock fails the second-copy case in watcher.test.mjs (run on a second machine).
  • Why not higher: this is the architecture rung for a script.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

A struggling Codex writer handed to Claude too late

Prevention

  • Class: left a Codex writer on judgment-heavy review work for 35 minutes without a push, causing review threads to sit while a stronger writer was available.
  • Instances: fixed scripts/board.mjs (--runtime, head time, SLOW from unresolved human threads via gh api graphql or --threads), fleet SKILL.md.
  • Search: rg -n "takeover|harness switch|SLOW" plugins/vibe/skills/fleet-orchestrator
  • Rung: 3 via board.mjs status.
  • Proof: disabling the slow-row check fails board.test.mjs.
  • Why not higher: the handoff itself is a judgment the orchestrator makes.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

An agent-approved plan's exclusion treated as a human ruling

Prevention

  • Class: treated an uncited Out-of-scope line in a plan vibe itself had set to APPROVED (no approver) as a ruling, causing a writer to skip two tickets it was told to build; a later scope audit found every program plan had no approver.
  • Instances: fixed skills/vibe/scripts/session-plan.mjs (every rendered PLN starts with a Self-approved line), agents/vibe-ticket-worker.md (sets APPROVED only with a human approver or that line, and runs the plan check first), skills/handoff/scripts/rulings-check.mjs (--plan), skills/handoff/SKILL.md (every linked PLN), fleet SKILL.md (the operator's scope-source ruling in principle 1; the check at dispatch and QA prep), references/templates.md (takeover and resume prompts trace inherited exclusions), scripts/session-questions.mjs (a question about dropping or narrowing scope goes through). Vibe already said session-written text is never a source, so this moves that rule up a rung.
  • Search: rg -n -i "out-of-scope|approverId|APPROVED|obsolete" plugins/vibe/agents plugins/vibe/skills plugins/closedloop-core/skills/plan-structure
  • Rung: 1 via session-plan.mjs, which renders the self-approved line on every plan vibe publishes; rung 3 via rulings-check.mjs --plan: an APPROVED or EXECUTED plan with no approver fails without that line, and any plan with no approver fails on each out-of-scope, non-goal, excluded, deferred, descoped, obsolete, not-covered or GAP item (labeled lines, bullets under such headings, and such clauses in tables or notes) that cites no ticket AC, PRD or operator ruling; rung 4 lines in fleet SKILL.md and the takeover and resume prompts.
  • Proof: on a fixture shaped like the plan (APPROVED, approverId null, an uncited Out-of-scope: ISS-9381 ... line) the check reports 7 problems; trusting the APPROVED status, accepting one citation for a whole line, reading labeled lines only, dropping the marker requirement, or rendering without the marker each fails its test.
  • Why not higher: the approver lives in the ticket service; vibe keeps APPROVED as its built marker and labels it instead.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Stacked lessons PRs

Prevention

  • Class: opened a new lessons PR while one was open, causing a stack that conflicted with main (a rule existed since ISS-12135: fix(vibe): encode the open vibe lessons ledger #317).
  • Instances: fixed fleet SKILL.md Lessons loop and new scripts/lessons-pr.mjs.
  • Search: rg -n "lessons PR|Batch lesson" plugins/vibe
  • Rung: 3 via lessons-pr.mjs, required before opening one; it names the open PR to push onto and fails when gh cannot answer.
  • Proof: making it report nothing fails lessons-pr.test.mjs.
  • Why not higher: no plugin script opens the PR, so it cannot be blocked at creation.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Bug fixes grouped into few tickets and PRs

Prevention

  • Class: filed one ticket and PR per finding or per PRD, causing extra CI runs and review overhead (operator ruling).
  • Instances: fixed fleet SKILL.md (QA finding filing).
  • Search: rg -n "file a ticket|follow-up" plugins/vibe/skills/fleet-orchestrator
  • Rung: 4 via fleet SKILL.md.
  • Proof: advisory rung, no proof possible; the contract test pins the wording.
  • Why not higher: how to group findings by code area is judgment.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Work outside the program goes to its owner

Prevention

  • Class: took an out-of-scope security or infrastructure finding into the program's queue, causing work its owner should do (operator ruling).
  • Instances: fixed fleet SKILL.md Pushback.
  • Search: rg -n "other teams|scope" plugins/vibe/skills/fleet-orchestrator/SKILL.md
  • Rung: 4 via fleet SKILL.md.
  • Proof: advisory rung, no proof possible; the contract test pins the wording.
  • Why not higher: scope is judgment.
  • Follow-up: none.
  • Event recorded: vibe lessons ledger.

Testing

Run on a second machine in this branch's own worktree: uv run ruff check ., uv run pyright, uv run pytest plugins/, node --test over every plugin test file (80 files), npm ci && npm run typecheck && npm test in each tools/* package, and npm run build in each bundling tool with no change to the committed dist bundles. Each new check was shown failing with its fix removed; the watcher lock proof ran on the second machine.

Risks

A new Codex session started through fleet.sh without CODEX_MODEL now refuses to start, and executed-check.mjs now requires --kind; the only shipped caller passes it. The first publish after upgrading versions each open session's PLN once to add the self-approved line, and a vibe plan with an uncited exclusion goes back to its writer before it is set APPROVED.

- liveness.mjs awaited main() above the constants that classify turn
  errors, so the watcher printed READFAIL for every failed turn; the
  entry point is last and a contract test refuses the shape anywhere.
- NEEDS-REPLY catches a question or a permission, authorization or
  approval request on any line of an idle writer's final message.
- A notLoaded thread whose turn finished reads IDLE with its message,
  proven with a test against the same App Server reads.
- The watcher flags MODEL(...) drift from CODEX_MODEL and CODEX_EFFORT;
  a new Codex session needs a model as well as an effort.
- watch.sh refuses a second copy per work dir.
- board.mjs: standing duties with owners (UNOWNED-DUTY; helpers.mjs
  refuses to stop an owner) and SLOW Codex writers with unresolved
  human threads and no push for 30 minutes.
- executed-check.mjs --kind prd refuses open PRODUCES children;
  review-page-check.mjs needs code evidence on decisions;
  session-questions.mjs refuses permission for in-scope work;
  lessons-pr.mjs finds the open lessons PR.
- Short prose for the usage reset rule, grouped bug fixes and work
  outside the program; vibe 1.0.75 and its changelog.

Testing: node --test on each changed suite; each new check shown
failing with its fix removed; full suites run on a second machine.
Risks: a new Codex session without CODEX_MODEL now refuses to start.
- The second-copy test starts the refused copy detached and kills its
  group afterwards, so a regression that lets it run leaves no sleep.

Testing: watcher.test.mjs on a second machine, with and without the
lock in watch.sh.
Risks: None identified.
- A writer skipped two tickets on an uncited Out-of-scope line of a
  plan vibe had set to APPROVED with no approver, against a direct
  instruction to build them.
- rulings-check.mjs --plan reads a PLN's get-document JSON; with no
  approverId, each out-of-scope, non-goal, excluded, deferred,
  descoped, not-covered or GAP item must cite a ticket AC, PRD or
  operator ruling.
- The ticket worker runs it before setting a PLN APPROVED, handoff on
  every linked PLN, fleet-orchestrator at dispatch and QA prep; the
  takeover and resume prompts make the incoming writer trace every
  inherited exclusion to a human source.

Testing: rulings-check tests on a fixture shaped like the plan, failing
when APPROVED is trusted, when a line-level citation covers every item,
and when only labeled lines are read.
Risks: vibe plans with uncited exclusions now return NEEDS_CHANGE at
the APPROVED transition until each one is cited or brought in scope.
- Every PLN session-plan.mjs renders starts with a Self-approved line,
  since vibe sets a PLN's status but never its approver; the ticket
  worker sets APPROVED only with a human approver or that line.
- rulings-check.mjs --plan fails an APPROVED or EXECUTED plan with no
  approver that lacks the line, and reads obsolete calls as exclusions.
  The fixture is shaped like the plan a writer used to skip two tickets.
- fleet-orchestrator principle 1 states the operator's ruling that a
  scope exclusion, deferral or obsolete call needs a human source.
- session-questions lets a question about dropping or narrowing scope
  through; the change worker waits for that answer.

Testing: rulings-check, session-plan and session-questions tests; each
check shown failing with its part removed.
Risks: the first publish after upgrading versions every open session's
PLN once, adding the Self-approved line.
- snapshot.sh reads each watched PR's unresolved review threads and
  prints threads=<n>; an open PR with any ends BLOCKED(unresolved
  threads), apart from its review decision and checks. Two approved
  PRs with auto-merge armed sat blocked for hours while their writers
  waited and the block was read as a missing approval.

Testing: snapshot.test.mjs with a fake gh, failing with the flag
removed.
Risks: one more gh call per watched PR per tick.
@shafty023
shafty023 merged commit 17c3625 into main Oct 22, 2026
10 checks passed
@shafty023
shafty023 deleted the fix/vibe-lessons-20261022 branch October 22, 2026 14:40
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