| AST-001 | Gate wired to a fix tool, not a review tool | promoted | → 2 |
AST-001 — Gate wired to a fix tool, not a review tool · promoted 2026-06-28 Review ≠ rescue: the gate runs a real diff review, never /codex:rescue. Bound: working-method §4, rules §3. File đang mang luật recurring-failure-modes.md:48 ↗ |
| AST-002 | A prose link does not guarantee load | promoted | — |
| AST-003 | No lessons ledger existed | promoted | — |
| AST-004 | Process decisions routed to ADRs | promoted | — |
| AST-005 | Entry-doc rewrite silently dropped unique content | promoted | — |
| AST-006 | fork delegation inherited Opus, ignored the model ladder | promoted | — |
| AST-007 | Long-lived branch + oversized PR outran the gate | promoted | — |
| AST-008 | Docs cited a retired skill collection | promoted | — |
| AST-009 | No UI-prototype phase before Build | promoted | — |
| AST-010 | Worktree delegation hygiene undocumented | promoted | — |
| AST-011 | Gate was per-PR only, not per-step | promoted | → 1 |
AST-011 — Gate was per-PR only, not per-step · promoted 2026-07-02 Its fix ("gate every step") over-corrected into continuous Codex loops that stalled delivery. Superseded by the Codex end-of-phase rule (rules §3, owner 2026-07-16): ONE adversarial pass at plan END + ONE review at review END; Claude carries the in-loop review. The surviving core: no plan goes to build ungated. File đang mang luật recurring-failure-modes.md:89 ↗ |
| AST-012 | Two review rounds not stated as both-mandatory | promoted | → 1 |
AST-012 — Two review rounds not stated as both-mandatory · promoted 2026-07-02 Its "re-gate both lenses to clean" loop is superseded by the Codex end-of-phase rule (rules §3). The surviving core: cross-vendor and same-vendor lenses catch DIFFERENT defect classes (proven by AST-015) — both still exist, once each, at the right points: Claude in-loop (Dan self-gate + Rin independent) + ONE Codex at review END. File đang mang luật recurring-failure-modes.md:95 ↗ |
| AST-013 | Ad-hoc build delegation; false tooling-gap assumed | superseded | — |
| AST-014 | Multi-slice rebuild needs a gated integration branch | promoted | — |
| AST-015 | Export step committed live secrets + buyer PII | promoted | → 1 |
AST-015 — Export step committed live secrets + buyer PII · promoted 2026-07-11 Same-vendor correctness review PASSED it; cross-vendor caught it (P1) — the two lenses catch different classes. A value that touched a tracked file is burned — rotate it. Bound: .claude/rules/no-secrets-in-exports.md (always-on). CI secret-scan still TODO. File đang mang luật recurring-failure-modes.md:109 ↗ |
| AST-016 | Agents sharing one checkout moved HEAD under each other | promoted | → 2 |
AST-016 — Agents sharing one checkout moved HEAD under each other · promoted 2026-07-11 Includes a READ-ONLY reviewer that git switched the PM's HEAD. Worktree isolation is UNCONDITIONAL for every spawned agent that can run state-changing git; PM re-asserts its branch each turn + verifies reflog after each agent. Bound: .claude/rules/agent-worktree-isolation.md (always-on). File đang mang luật recurring-failure-modes.md:114 ↗ |
| AST-017 | Per-package typecheck missed a cross-package break | promoted | — |
| AST-018 | Dispatch emitted as text, never executed; no liveness signal | promoted | — |
| AST-019 | Implementer subagent retired; independence moved to the PR | promoted | — |
| AST-020 | Plugin review commands invisible → rescue used as review; raw exec hung | promoted | — |
| AST-021 | Gate workflows blocked on owner presence | promoted | — |
| AST-022 | A filesystem-only tool was invisible to agents | promoted | — |
| AST-023 | "Main session builds directly" conflated two mechanisms | promoted | — |
| AST-024 | Role rule auto-loads into every session → role-bleed (Worker acted as Thomas) | promoted | → 5 |
AST-024 — Role rule auto-loads into every session → role-bleed (Worker acted as Thomas) · promoted 2026-07-16 "Auto-loaded" ≠ "I am Thomas". Role adoption is gated by an explicit, exhaustive, ordered self-check keyed on spawn designation (system prompt / subagent_type), never prompt content, never HERDR_ENV (diagnostic only); fail-closed STOP on mislaunch. Bound: .claude/rules/role-thomas.md. File đang mang luật recurring-failure-modes.md:160 ↗ |
| AST-025 | Gate-able conventions parked in the always-on rule tier tax every loop | proposed | — |
| AST-026 | Named-persona harness: Thomas / Dan / Rin | promoted | — |
| AST-027 | TWO ROOT sessions shared the main checkout; one switched branches under the other | promoted | → 1 |
AST-027 — TWO ROOT sessions shared the main checkout; one switched branches under the other · promoted 2026-07-17 Live incident: the product-Thomas session (epic 55) ran git switch plan/… in the shared main checkout while the harness-builder session was committing — three harness commits landed on the OTHER session's plan branch, then vanished from main when it switched back and pulled (also polluting that plan branch's gate diff). AST-016 only bound SPAWNED agents; two ROOT sessions in one checkout were unregulated. Lesson: **one checkout, one driver — at any moment at most ONE root session treats the main checkout as its working copy; any concurrent second root session (e.g. harness maintenance beside a product Thomas) works in its OWN worktree/branch and merges via the normal flow.** Also: EVERY session re-verifies git branch --show-current before each commit (a switch can happen between turns). Recovery pattern proven: lost commits stay in the object store — git reflog + git branch --contains <sha> find them; re-apply on current main. Second live incident same day: a detached-SHA checkout by the other session made .claude/agents/* files vanish mid-turn. … File đang mang luật recurring-failure-modes.md:197 ↗ |
| AST-028 | Relative worktree path + unverified pane cwd → worktree born in the wrong place, hour-long mi… | promoted | → 4 |
AST-028 — Relative worktree path + unverified pane cwd → worktree born in the wrong place, hour-long mi… · promoted 2026-07-30 Live incident (in a deployed project, 2026-07-30): the dispatching session's shell was still standing in an app subdirectory (left over from an earlier export step), so a RELATIVE git worktree add ../<name>-worktrees/… resolved against that stale cwd and created the worktree INSIDE the repo — while the path handed to the Herdr pane did not exist. Domino: cd in the pane failed → claude started from $HOME → agent definition not found → the session concluded "input isn't reaching the pane / Herdr is hung" and misdiagnosed for an hour. Two aggravators: briefs were pushed via split send-text+send-keys (drops input; pane run/agent prompt are the standard), and an outdated local herdr CLI lacked a documented subcommand, reinforcing the false "app is broken" theory. Root cause was ONE error: no path/cwd verification floor. … File đang mang luật recurring-failure-modes.md:213 ↗ |
| AST-029 | Slice finished but Dan tabs survived; /clear blurred context and checkout lifecycle | promoted | — |
| AST-030 | Orchestrator row named a runtime with no dispatch path for the role; Rin went undispatchable | promoted | → 2 |
AST-030 — Orchestrator row named a runtime with no dispatch path for the role; Rin went undispatchable · promoted 2026-08-02 (incident from the origin project) Rin's Active row was tuned from claude/opus to a codex model. On a Claude root Rin's ONLY dispatch path is the Agent tool (subagent_type: "rin-reviewer" + isolation: "worktree"), whose model parameter accepts Claude models only — and no .codex/profiles/rin-reviewer.config.toml exists. The row was syntactically valid and semantically dead: the sole independent gate could not be dispatched at all. The project's end-of-phase Codex gate caught it (its fourth catch) and the row was reverted. Lesson: **(a) a role's row is executable only through that role's real dispatch path on the current root — Rin's Runtime must match the root runtime (Claude root → claude row via the Agent tool; Codex root → .codex/agents/rin-reviewer.toml; opencode root → .opencode/agents/rin-reviewer.md); Rin's cross-VENDOR coverage comes from its gate arm, never from the Runtime cell; moving a role to another runtime is a role-contract change (new dispatch path + adapter/profile), not a row tune. … File đang mang luật recurring-failure-modes.md:253 ↗ |
| AST-031 | A prose instruction telling an agent to suppress its tool's own default is not a boundary | promoted | → 1 |
AST-031 — A prose instruction telling an agent to suppress its tool's own default is not a boundary · promoted 2026-08-03 Release 0.11.4 authorized Dan — the READ-ONLY slice lead — to invoke Claude Code's built-in /simplify, whose own contract is "review the changed code THEN APPLY THE FIXES", under the guard "invoke it WITH the instruction 'findings only, edit no files'". Three things defeat that guard: the tool's instructions enter the agent's context and compete with the role, the agent launches with permission prompts disabled so nothing can refuse the write, and the release text itself conceded the skill "will push against your role once loaded". The one-writer invariant (AST-026) was left resting on a model choosing to honour a sentence, and the formative reviewer would have become an author. A consuming project's cross-vendor adaptation gate caught it before any slice ran on the release. Lesson: **a natural-language instruction to suppress a tool's default behaviour is NOT a boundary — the tool's contract and the agent's permissions win. … File đang mang luật recurring-failure-modes.md:273 ↗ |
| AST-032 | A signal that cannot fail is not evidence | promoted | → 8 |
AST-032 — A signal that cannot fail is not evidence · promoted 2026-08-05 A consuming project ran two real product slices through the framework and hit the same shape seven times in one session, each time in a different costume. The watcher script was invoked as watch.sh <pane> | tail -3, and a pipeline returns the LAST command's status, so it reported success whether the watcher reached a terminal state or never started. A DEAD opencode process still answered interactive_ready: true for at least one poll interval. go test -run <pattern> with a pattern that matched NOTHING printed "no tests to run" and exited 0 — a green that meant nothing, which nearly got a working test reported as broken. Subagents reported idle/available three separate times WITHOUT having sent their result (each had written its report and simply not handed it back), so reacting to idle either re-tasks a finished agent or banks work that was never delivered. A background task reported killed was read as completed. caffeinate was killed while the child process it wrapped survived orphaned — a cleanup command that RAN is not a cleanup that WORKED. … File đang mang luật recurring-failure-modes.md:292 ↗ |
| AST-033 | A lookup whose question has no referent at one of its call sites | promoted | → 1 |
AST-033 — A lookup whose question has no referent at one of its call sites · promoted 2026-08-06 Release 0.12.0 replaced a discretionary choice of Rin's dispatch form with a LOOKUP, on the correct instinct that a rule two dispatchers can evaluate identically beats a judgement call. The question it asked was *"is there a live slice:<slice-key> tab in this herdr session?"* — written for the CODE-REVIEW gate, where a slice tab exists by definition. At the PLAN gate there is no such tab, because a plan is gated BEFORE its slice is dispatched (the harness's own rule is that no plan builds ungated). The question therefore had no referent there and resolved to "no" every single time, silently making every plan gate the unobservable form — at the one milestone where design decisions are actually made. A consuming project ran three consecutive plan gates invisibly, with owner-scale calls made inside them, before anyone found the cause; the same file said "never invent a slice key for a plan gate" three lines above the question keyed to that very key. Lesson: **a lookup is only better than a judgement if its question has a referent at EVERY call site. … File đang mang luật recurring-failure-modes.md:351 ↗ |
| AST-034 | A mandatory rule that lives only in load-on-demand docs is skipped, and only the owner notices | promoted | — |
| AST-035 | set -euo pipefail plus a no-match grep aborts before its own guard | promoted | — |
| AST-036 | A git worktree carries TRACKED content only | promoted | → 3 |
AST-036 — A git worktree carries TRACKED content only · promoted 2026-08-10 First real installation of 1.0.0 into an existing repo. The project's .gitignore carried .agents/*, so a Builder dispatched into its worktree would have found no .agents/roles/builder.md — the exact file its adapter tells it to read first. It would have started with no contract and no signal that anything was missing, which is worse than a Builder that fails: it improvises, plausibly.
Allow-listing the paths was not enough. Files must be COMMITTED, because a worktree is built from the index, so allow-listed-but-untracked is still invisible. That second half is the part everyone believes is already done.
The general shape: **any mechanism that materializes a fresh checkout sees only what git tracks.** Payload placed on disk by an installer is not payload the method can use. Cheapest proof, and the only one that answers the question: git worktree add --detach /tmp/x HEAD && test -f /tmp/x/.agents/roles/builder.md. Bound: dispatch-ticket (payload-must-be-committed section), check-requirements.sh (tracked-payload check), prompts/ADAPT-HARNESS.md §4. File đang mang luật recurring-failure-modes.md:456 ↗ |
| AST-037 | A multi-line prompt pastes without submitting, and the pane calls it idle | promoted | → 2 |
AST-037 — A multi-line prompt pastes without submitting, and the pane calls it idle · promoted 2026-08-10 herdr pane run and agent prompt both send text plus Enter, and both work on one line. A MULTI-LINE block is pasted as a unit and the Enter is consumed by the paste: the transcript shows [Pasted text #1 +N lines] sitting in an unsent composer. Every real dispatch brief is multi-line, so this is the default case rather than an edge case.
It compounds with AST-032: the pane then reports idle, because an empty-looking composer matches Claude's prompt_box_body rule. A dispatcher that trusts that status concludes the agent finished instantly, and waits forever on work that never started.
Two-part fix, and the second part is what makes it detectable: send Enter explicitly after a multi-line brief, then require observing working before believing the turn began. Reaching idle/done without ever seeing working means it never ran. Bound: dispatch-ticket (Submitting it).
File đang mang luật recurring-failure-modes.md:474 ↗ |
| AST-038 | A checker that cannot tell project content from package content fires on every adopted repo | promoted | → 1 |
AST-038 — A checker that cannot tell project content from package content fires on every adopted repo · promoted 2026-08-10 check-reachability.sh globbed .claude/skills/*/SKILL.md and treated everything it found as harness-owned. In the package that is true. In an adopted repo the project's own skills sit in the same directory, so the checker reported four of them as unreachable defects and one as naming unknown skills — six findings, none real, on a correct installation.
A gate that cries wolf on a correct install gets switched off, which costs more than the gate was worth. The fix is to establish ownership from evidence rather than from location: the staged release under .astraler/releases/<applied>/harness/ is the authoritative manifest of what the package shipped, and anything outside it belongs to the project and is skipped — reported by name, so the skip is visible rather than silent.
The general shape: **tooling that ships INTO other repos must be able to name its own files.** Location is not ownership. Bound: harness/scripts/check-reachability.sh.
File đang mang luật recurring-failure-modes.md:489 ↗ |
| AST-039 | An ID namespace shared with the host project resolves confidently to the wrong lesson | promoted | — |
| AST-040 | A placeholder that looks like a real id fails later than a missing one | promoted | — |
| AST-041 | A file called "the owner's" that ships in the payload has two homes and the shipped one wins | promoted | → 1 |
AST-041 — A file called "the owner's" that ships in the payload has two homes and the shipped one wins · promoted 2026-08-11 .agents/orchestrator.md opens with "This file is the owner's" and was nevertheless part of the payload every release overwrites. A consuming repo had tuned it — three rows plus a documented ## Owner decisions section — and the only thing preventing loss was the adaptation prompt telling an agent to preserve it, which is a habit rather than a mechanism.
Declaring ownership in prose while shipping a competing copy is not ownership. Scaffold and payload are different categories: a scaffold is written when absent and never overwritten, and where its shape must change the release reports the difference for the owner to merge. The same applies to the Codex profiles, which carry the same owner-chosen values. Bound: harness/.agents/orchestrator.md, prompts/ADAPT-HARNESS.md §3.
File đang mang luật recurring-failure-modes.md:535 ↗ |
| AST-042 | Two skills answering to one name means the model-invoked path picks the wrong one | promoted | — |
| AST-043 | A gate that requires an artifact no contract produces | promoted | — |
| AST-044 | Reading a diff cannot find a disagreement between two screens | promoted | — |
| AST-045 | A green test suite and a coherent product are different claims | promoted | → 2 |
AST-045 — A green test suite and a coherent product are different claims · promoted 2026-08-11 The prior package shipped a browser-walking agent for several releases and it **never ran once**. A grep of the whole payload found no file naming it outside its own two definition files: no role contract, no dispatch path. It was correct, it was valuable, and it was unreachable — the exact class check-reachability.sh exists to catch, sitting in the package that later wrote that checker.
So the work of adopting it was never the file. It was the wiring: a contract that owns it, a dispatcher that names it, and a check that fails when either goes missing.
What it earns its place with, on a web product: **a test asserts what somebody thought to assert**, and that is mostly backend logic. Missing, misordered, unreadable or unreachable on the screen is where a user lives and is precisely what no one wrote an assertion for. One walk found a stale-seed 500 carrying a production implication, a timestamp format disagreeing with every other page, two screens printing 85 and 44 for one concept, and tabs summing to 183 against a total of 190. … File đang mang luật recurring-failure-modes.md:607 ↗ |
| AST-046 | A block moved between documents keeps the old document's referents | promoted | — |
| AST-047 | "Local" is a deployment fact, not a data fact | promoted | → 1 |
AST-047 — "Local" is a deployment fact, not a data fact · promoted 2026-08-11 The QA role shipped with "prefer a local or seeded environment for anything carrying customer data", which quietly assumes local means synthetic. The first repo to read it said otherwise: its local database is a production snapshot with real buyer PII, so a screenshot of an order list there captures the same names and addresses a production one would.
The safety rule was written about the wrong noun. Where the data came from decides what may be captured, and prod-derived data is production data wherever it happens to be running. A rule phrased around environment lets a team satisfy it exactly and still write customer records to disk.
Generalises past this case: **a safety rule keyed to a proxy for the risk will be satisfied by the proxy.** Key it to the thing itself, and require the agent to establish it rather than infer it — here, what the data *is*, asked before a screen is judged safe to capture. Bound: harness/.agents/roles/qa.md §Safety(c). File đang mang luật recurring-failure-modes.md:653 ↗ |
| AST-048 | A rule not present where it must be remembered does not exist | promoted | — |
| AST-049 | Checks 1–4 asked whether things were consistent, never whether a role could START | promoted | — |
| AST-050 | Qualify a plugin command always, not once it is known to collide | promoted | → 3 |
AST-050 — Qualify a plugin command always, not once it is known to collide · promoted 2026-08-11 AST-042 fixed one name — code-review, where a plugin skill and a built-in already answered to the same word — and left every other flow command written bare. Two of those bare forms were the first line of a dispatch brief: /implement for a Builder, /grill-with-docs for a Shaper. They resolve today because nothing else claims those words yet.
That is the whole problem: a name collision is invisible until it exists. A built-in added later, or a second plugin installed by the owner, changes what a bare command resolves to with no diff anywhere in this package and no error at dispatch — the brief simply gets prose instead of a phase, which reads as a weak agent rather than a broken command.
So the rule is unconditional: write /mattpocock-skills:<name> every time, and reserve the bare form for Claude Code's own built-ins (/compact, /clear, /simplify), where the bare name IS the correct address. AST-042's "wherever ambiguous" was too weak — the author of a document cannot see the ambiguity that arrives next month. … File đang mang luật recurring-failure-modes.md:714 ↗ |
| AST-051 | An address the caller cannot use produces a substitute, not an error | promoted | → 7 |
AST-051 — An address the caller cannot use produces a substitute, not an error · promoted 2026-08-11 builder.md named the simplify pass as Claude Code's built-in /simplify. The name was right and the address was wrong: a slash command is the form a human types, and a Builder is an agent with no keyboard. It could not invoke what it was told to invoke.
Nothing failed. Two Builders on two tickets in one day each performed a hand-rolled cleanup and neither produced the simplify(increment): marker. Both handbacks honestly described a pass that did happen. Measured afterwards, the real skill fired over the same diff found an extraction both had missed — so the substitute was not merely unmarked, it was weaker.
The control experiment is in the same table: the rows naming mattpocock-skills:implement and mattpocock-skills:code-review were both invoked correctly in those same sessions. A usable address gets used.
This entry corrects AST-050. That rule said built-ins "keep their bare names, which are the correct address", listing /compact, /clear, /simplify together. Two of those three are CLI commands with no Skill-tool path, so bare IS their address. …
File đang mang luật Commit đóng nó recurring-failure-modes.md:736 ↗ |
| AST-052 | The word-budget audit ran its loop zero times and reported all clean | promoted | → 1 |
AST-052 — The word-budget audit ran its loop zero times and reported all clean · promoted 2026-08-11 docs-staleness-audit.sh measured .agents/roles/<role>.md. In this package the payload sits under harness/, so all five paths failed their -f test, the loop body never ran, the axis printed nothing, and the script closed with RESULT: all clean.
It shipped that way from 1.0.0 and was quoted as evidence in this session more than once. The word counts reported alongside it were right — they were taken by hand with wc -w — which is exactly why nobody noticed: two sources agreed, and only one of them was working.
A loop over zero items is a pass. That is the whole failure. Nothing errored, no path was reported missing, and the axis header still printed, so the run looked identical to a run that had measured five contracts and found them all within budget.
The fix is two lines and neither is the path: detect the payload, and **count what was measured**, then fail when the count is zero. …
File đang mang luật recurring-failure-modes.md:769 ↗ |
| AST-053 | An axis read the run's verdict instead of its own, and went mute | promoted | — |
| AST-054 | git add -A committed two releases nobody ever applied | promoted | → 1 |
AST-054 — `git add -A` committed two releases nobody ever applied · promoted 2026-08-11 ADAPT-HARNESS.md said the installation has to be committed and did not say what. The upgrading agent used git add -A, which swept in .astraler/releases/1.4.3/ and 1.4.4/ — both staged, both superseded before anyone ran them, both untracked until that moment. About 1000 files, permanently in a history that cannot be trimmed without a rewrite.
Staging is deliberately cheap, so abandoned candidates are normal rather than exceptional, and a project that upgrades often accrues them. Untracked is their correct resting state: disk, not history.
The instruction now names paths and derives the release to keep from .astraler/CANDIDATE, then prints what is still untracked so an abandoned candidate is visible rather than assumed. An instruction that says "commit" without saying what to commit will be read as -A — this is the second time a gap in this prompt was filled by an agent's reasonable default. Bound: prompts/ADAPT-HARNESS.md.
File đang mang luật recurring-failure-modes.md:815 ↗ |
| AST-055 | A gate that reads the subject cannot see which pass wrote it | promoted | → 11 |
AST-055 — A gate that reads the subject cannot see which pass wrote it · promoted 2026-08-12 AST-051 fixed the address, and the fix worked: a Builder under the corrected contract invoked Skill(skill: "simplify") and said so unprompted. The next Builder called Skill(mattpocock-skills:simplify) — a skill that does not exist — because it had just invoked mattpocock-skills:implement from the row above and generalised the namespace one row down. Two adjacent rows, two different systems: the plugin, and Claude Code itself.
The error is not the interesting half. **It fell back to the code-simplifier agent, and the simplify(increment): commit appeared anyway.** Thomas's merge grep, Rin's gate and reachability check 7 all read as satisfied, because every one of them asked whether the marker exists. None could ask which pass produced it. The substitution was visible only because a human happened to be watching the pane.
A marker that any tool can write is a check that cannot fail. The subject proves a commit happened; the body is where a substitute can disagree with the sanctioned pass. … File đang mang luật recurring-failure-modes.md:832 ↗ |
| AST-056 | A blocking edge expresses order, not exclusion | promoted | → 3 |
AST-056 — A blocking edge expresses order, not exclusion · promoted 2026-08-12 The frontier asks which tickets have no open blocker and no assignee. It never asks what each ticket will WRITE, and nothing else did either. Two tickets went out together, correct by every rule the package stated. One existed to correct WIRE-CONTRACT.md; the other was a Go ticket whose brief never mentioned that document — and whose Builder edited the same three rows of it, because the repo's docs-sync rule requires the touched document to move in the same slice. It was obeying a correct rule. The first merged, the second was based on the commit before that merge, and the same three corrections were re-derived blind: two conflict blocks, and a naive merge in the wrong direction would have reverted reviewed work with no signal.
One worktree per Builder solves the checkout collision and nothing else. It relocates the collision to the merge, where it is found late and by hand. … File đang mang luật recurring-failure-modes.md:867 ↗ |
| AST-057 | A frontier that is only computed is invisible to the one person who cannot compute | promoted | → 7 |
AST-057 — A frontier that is only computed is invisible to the one person who cannot compute · promoted 2026-08-12 Thomas's contract defined the frontier as a QUERY — open, no unfinished blockers, no assignee — and nothing said to write the answer back. An agent re-runs the query on demand, so it is never wrong for long and never notices anything missing. **The owner cannot re-run anything: he opens the board and looks.** So the board can be useless to the human while serving every agent perfectly, and no role is positioned to notice.
Measured on a live project, 2026-08-12: zero issues had ever entered the unstarted state across the project's whole life, and one ticket sat looking blocked for hours after both its blockers merged. Found by the owner comparing two boards by eye. No check the harness runs had ever looked.
The upstream cause is in the plugin, and naming it correctly is what decides the fix. mattpocock-skills:to-tickets draws the blocking edges and writes Status: ready-for-agent at the same moment — a label, applied once at creation, never revisited when a blocker is later added or cleared. It sets no workflow state at all. … File đang mang luật recurring-failure-modes.md:895 ↗ |
| AST-058 | The check after the step reported clean when the step was impossible | promoted | — |
| AST-059 | The repo kept one self-check and lost the other to a directory it may ignore | promoted | — |
| AST-060 | Check 3 printed green about the skills it had not opened | promoted | — |
| AST-061 | The arm batched to phase end built a payload only skimming could finish | promoted | — |
| AST-062 | A second pass left to judgement is a second pass that does not run | promoted | — |
| AST-063 | A gate with no window in the sequence never fires, and nobody forgets it | promoted | — |
| AST-064 | The one always-on file no release can repair had no budget | promoted | — |
| AST-065 | Two reviewers sharing one name, each in the other's contract | promoted | — |
| AST-066 | A review bound to the wrong checkout returns clean without reading anything | promoted | — |
| AST-068 | A lesson closed at instance level reopens at class level | promoted | — |
| AST-069 | An instruction with no moment attached measures zero | promoted | → 5 |
AST-069 — An instruction with no moment attached measures zero · promoted 2026-08-13 A project kept a ledger and a written rule to *"capture every real friction"*. Across a whole harness generation it added zero entries — while its own ledger, in the same window, contained the entries that described the bugs it was hitting. Reading ran: 74 citations across 30 live docs. Only writing stopped.
Nobody forgot. The rule had no MOMENT. It sat in §7 of a load-on-demand document, so it was read when someone opened that document, which is never the instant a friction happens. And the project's own ledger already held the diagnosis: *"a mandatory rule that lives only in load-on-demand docs is skipped, and only the owner notices."* The ledger contained the reason it was not being written to.
Two properties make an instruction actually run, and both are needed:
- A moment — an event that already happens and already must report. Merge is the natural one here: it is mandatory, it is frequent, and the friction is still warm. - A trace — something grep-able whose absence is visible. … File đang mang luật Commit đóng nó recurring-failure-modes.md:1171 ↗ |
| AST-070 | A bounded exception nobody asked for is a contradiction carried on speculation | promoted | → 2 |
AST-070 — A bounded exception nobody asked for is a contradiction carried on speculation · promoted 2026-08-13 codex-dispatch-headless was the written, narrowed form of one exception: run a Builder on Codex with no Herdr pane. It was built well — three preconditions, any one absent routing back to the visible default, and the invariant kept explicit that a headless author cannot approve its own work. It made headless HARDER to reach, not easier.
It ran zero times. Two active projects carried it for three weeks and one for ten days, and visibility=headless never appears outside the two files that define it. The four commits that mention "headless" are about browser automation, and one of them bans it.
Two arguments for keeping it fell to measurement rather than to opinion. **It is not too young to judge — three weeks with two projects dispatching daily is opportunity. And it is not where the dangerous flag lives**: --yolo is already on the mainline codex path in dispatch-ticket for builder, shaper and qa, so removing this changed no permission posture. What remained was only the topology.
The decision turned on what the package sells. …
File đang mang luật Commit đóng nó recurring-failure-modes.md:1198 ↗ |
| AST-071 | Every check asked whether a thing was NAMED, none asked whether anything READ it | promoted | → 1 |
AST-071 — Every check asked whether a thing was NAMED, none asked whether anything READ it · promoted 2026-08-13 Seven reachability checks, five contracts, a staleness audit on five axes, and three skills shipped for weeks writing documents that no contract and no plugin skill was ever told to open. Every check was green the whole time. The owner found all three by hand, on the third occasion of noticing the same shape in one day.
The blind spot was structural rather than careless. Check 3 asks whether a skill is NAMED. Check 4 asks whether a referenced path EXISTS. Check 6 asks whether an address is CALLABLE. Check 7 asks producer-and-verifier, but only for the gate artifacts a human had listed. Not one asked the question that mattered: does anything read what we produce? A file with a writer and no reader satisfies every one of them.
extract-standards is the sharpest instance because it was well built. … File đang mang luật Commit đóng nó recurring-failure-modes.md:1225 ↗ |
| AST-072 | Self-monitoring shipped without proof it cannot harm what it monitors | promoted | → 1 |
AST-072 — Self-monitoring shipped without proof it cannot harm what it monitors · promoted 2026-08-18 Two new mechanisms — a workspace-identity convention and a background watchdog — landed with their own reachability and word-budget checks green, and their own author read the shutdown logic as safe. A Codex adversarial-review pass, fired against the same commit before it was called done, returned needs-attention with three HIGH findings, none of them about whether the feature worked: whether it could hurt something it was never meant to touch.
The sharpest one: the documented shutdown sent kill -TERM to a process group resolved from a bare PID, with no check that the PID still belonged to the watchdog and no guarantee the watchdog ever ran in its own process group to begin with. Launched as a plain background job from a non-interactive shell — exactly how a dispatched agent starts one — the watchdog shares its process group with the shell that launched it. Stop that watchdog and the signal lands on every process in that group, including the caller. … File đang mang luật recurring-failure-modes.md:1253 ↗ |
| AST-073 | A global script is only shared if something keeps it updated | promoted | — |
| AST-074 | A tracker measured only against itself cannot detect its own drift | promoted | → 1 |
AST-074 — A tracker measured only against itself cannot detect its own drift · promoted 2026-08-18 Reported upstream from an adapted project's own Thomas, via a handoff, rather than found in this package directly — the first entry with that provenance, recorded because the class it names is general and the reachability checks have no way to see a tracker's *content* going stale, only a document's.
Four tickets in that project sat claimed and in-progress with a live assignee **after their code had merged to the base branch**, the oldest by a full day. Nothing errored: the merge ran, the frontier write-back after it did not, and no artifact recorded the omission — the exact shape of AST-057, arriving in a place check-reachability cannot reach, because the drift is in the tracker's *content*, not in whether a phase is named or wired.
**The reason no tracker-only check catches this: a wrong state is perfectly consistent with itself.** In-progress with an assignee is exactly what a real in-flight ticket looks like from inside the tracker; the two are indistinguishable without a second, independent source. … File đang mang luật recurring-failure-modes.md:1302 ↗ |
| AST-075 | Neither the process table nor the PID file nor CPU time proves a loop is alive | promoted | — |
| AST-076 | AST-072's own fix left a window between acquiring the lock and recording who holds it | promoted | → 1 |
AST-076 — AST-072's own fix left a window between acquiring the lock and recording who holds it · promoted 2026-08-18 Found by an adapted project's own Thomas, dispatched to run the cross-vendor arm against the 2.2.0 fold-in before it was adopted there — the review-before-adopt discipline catching a defect in the very payload it was reviewing, one commit downstream of where AST-072 first shipped it.
AST-072 fixed the single-instance lock by having mkdir "$LOCK_DIR" double as the lock primitive, atomic and portable. What it missed: the PID written into that lock as proof of ownership was not available until two re-exec hops later — first into a setsid wrapper for process-group isolation, then into caffeinate, which itself forked a new child to run the script, so the PID that finally reached echo $$ > "$PID_FILE" was neither the PID that ran mkdir nor any PID visible before that second hop completed. … File đang mang luật recurring-failure-modes.md:1375 ↗ |
| AST-077 | Identity by substring match let stop sign a kill order for an unrelated process | promoted | — |
| AST-078 | flock-plus-holder watched only one direction | promoted | — |
| AST-079 | the reaper that closes AST-078 exits silently, and the lock changed type across a version bou… | promoted | — |
| AST-080 | check-requirements.sh's payload check was tracked-not-current | promoted | — |
| AST-081 | ticket-git-facts.sh's oracle was case-sensitive where the rest of it wasn't | promoted | — |
| AST-082 | a copy-pasteable dispatch example taught the wrong pane name for one role | promoted | → 1 |
AST-082 — a copy-pasteable dispatch example taught the wrong pane name for one role · promoted 2026-08-18 Also found by the same integration run. dispatch-ticket/SKILL.md's two rename commands hardcode builder:<ticket-id> literally, with the note that a Shaper's pane and tab are both spec:<id> instead sitting one paragraph below in prose — present, correct, and easy to miss when the instinct is to copy the command block rather than read past it. Reproduced directly: a Shaper pane renamed shaper:<id> by reflex was invisible to herdr-watchdog.sh's DISPATCH_PREFIXES end to end, and stayed unmonitored until the mismatch was caught by hand — a silent gap in exactly the mechanism whose whole job is to catch a stalled dispatch.
Fixed by adding an explicit note directly above both command blocks, stating this is a measured failure rather than a hypothetical, rather than only adding the correct value further down and trusting it gets read first. … File đang mang luật Commit đóng nó recurring-failure-modes.md:1682 ↗ |
| AST-083 | the arm's own forbidden-character list was incomplete, and missed exactly the failure it exis… | promoted | — |
| AST-084 | the watchdog trusted a pane title a runtime overwrites after launch | promoted | → 2 |
AST-084 — the watchdog trusted a pane title a runtime overwrites after launch · promoted 2026-08-18 Found by an adapted project's own Thomas from live operation, on a real dispatch, not the arm. herdr-watchdog.sh classified a dispatched pane by matching its terminal title against DISPATCH_PREFIXES — set once by herdr pane rename "<role>:<id>" at dispatch time. The Claude runtime overwrites its own pane's terminal title after launch and does not honor a rename that predates it. Measured live, on this exact machine, on the real pane the finding named: a genuine Builder dispatch, herdr agent start "builder-tra-180", its title correctly set to builder:TRA-123 at rename time — then rewritten by the runtime to a bare builder, no colon, no ticket id. is_dispatched() never matched it. The pane never entered dispatched; the watchdog polled, heartbeat and all, and could never fire BLOCKED, STUCK or WATCHER_LOST for it — running normally while structurally unable to detect the one thing it exists to detect. The same live check, on a second project, found a Shaper in the identical state (title shaper, not spec:<id>). … File đang mang luật Commit đóng nó recurring-failure-modes.md:1732 ↗ |
| AST-085 | a word budget calibrated against a retired file size passed nothing | promoted | → 1 |
AST-085 — a word budget calibrated against a retired file size passed nothing · promoted 2026-08-18 Also found by the same project's live operation, restated a third time in its own receipts before reaching the package. docs-staleness-audit.sh's orchestrator.md budget was set to 800 on 2026-08-13, calibrated to leave headroom over a 653-word shipped file. The Workspace identity section added across the 2.2.x line grew the shipped file itself to 800 words without the budget being revisited — measured across every release directory an adapted project had staged: 653 words at 1.6.2, 684 at 2.0.1, 800 at 2.2.4 and every release since. A budget equal to the file it bounds passes zero projects, including one that only fills in the workspace-label the same release requires — the calibrating comment still claimed "leaves real room over the shipped 653" while the shipped file had long since become the number in the check itself.
Fixed by raising the budget to 950, restoring roughly the margin the original calibration intended, measured against the current 800-word baseline rather than the retired 653-word one the old comment still cited. … File đang mang luật Commit đóng nó recurring-failure-modes.md:1762 ↗ |
| AST-086 | the payload checker's own scope list was already stale, including for itself | promoted | — |
| AST-087 | the same-day orchestrator margin fix was not applied to the role budgets it sits beside | promoted | — |
| AST-088 | the fix for a stale hardcoded list wrote a new hardcoded assumption beside it | promoted | — |
| AST-089 | A fork-fallback inside simplify read as a substitute because only the Pass: line's wordin… | promoted | → 2 |
AST-089 — A fork-fallback inside `simplify` read as a substitute because only the `Pass:` line's wordin… · promoted 2026-08-18 Found by an adapted project's Thomas verifying a simplify(increment): commit on one ticket (a guard blocking role-string comparison in a dashboard), dispatched through a real Herdr pane. Skill(skill: "simplify") tried to fan out into four parallel review corners and failed inside its own execution — Fork is not available inside a forked worker, on every nested fork attempted from that pane, plus two of the four review forks hanging over 5 minutes with no notification before the Builder gave up on the fan-out (that hang is a separate, still-open failure — not closed by this entry).
First pass: the Builder hit the fork error, ran the four corners directly, and reported honestly — `Pass: 1 (fork tooling was unavailable ... run directly rather than via 4 parallel forks). That line never named Skill(skill: "simplify")`, so thomas-claude.md's literal check read it as a substitute and sent it back — correct under the letter of AST-055. … File đang mang luật recurring-failure-modes.md:1873 ↗ |
| AST-090 | The Pass: literal had a two-in-three miss rate among Builders who ran the pass correctly | promoted | → 1 |
AST-090 — The Pass: literal had a two-in-three miss rate among Builders who ran the pass correctly · promoted 2026-08-18 Found by workspace-app-inception Thomas on the same session that verified AST-089. Two out of three Builders who genuinely ran Skill(skill: "simplify") wrote a Pass: line that failed verification:
- Pass: DEGRADED (AST-089 form) — dispatched 4 parallel review forks ... — the Builder reached for the AST-089 concept (degraded completion) as the leading token, because that is what the situation was. - Pass: /simplify (4 parallel review agents: reuse, simplification, efficiency, altitude) — the Builder wrote what it typed (/simplify is the human-invocation form visible in its own transcript), not the tool-call spelling.
Both were correctly bounced — the literal is the only thing that distinguishes a real pass from a substitute (AST-055), and widening the verifier reopens that hole. But a guard that honest Builders fail two times in three is generating round trips that teach nothing. … File đang mang luật recurring-failure-modes.md:1905 ↗ |
| AST-091 | install.sh overwrites PROJECT_NAME on re-staging without --project-name | promoted | — |
| AST-092 | Builder stops after writing code but before committing — pane reads done, cleanup deletes the… | promoted | → 4 |
AST-092 — Builder stops after writing code but before committing — pane reads done, cleanup deletes the… · promoted 2026-08-18 Measured in the field: 5 instances, 3 different Builder sessions, runtime claude, dispatched via herdr pane. Each time the Builder wrote substantial work (93-433 lines), pane status settled to done or idle, watcher returned TERMINAL:done, but git status on the worktree showed uncommitted changes.
The danger: Thomas's cleanup in dispatch-ticket runs git worktree remove, which silently deletes all uncommitted files. A Builder that stops before committing produces an artifact that exists only on disk, invisible to git, and cleanup destroys it without warning. Pane status is not a proxy for commit status — a pane can be done with uncommitted work.
Worst measured case: a fix for a HIGH cross-vendor arm finding sat uncommitted. Thomas ran mutation testing against the committed state (which lacked the fix), saw the suite pass, and nearly concluded the fix did not work — when in fact the fix did not yet exist in git.
Two gaps in the contracts: 1. … File đang mang luật recurring-failure-modes.md:1945 ↗ |
| AST-093 | A fix landing in .agents/skills/ but not .claude/skills/ is unreachable on the runtime that l… | promoted | — |
| AST-094 | Builder commits and pushes correctly but silently skips the simplify pass | promoted | → 4 |
AST-094 — Builder commits and pushes correctly but silently skips the simplify pass · promoted 2026-08-18 Measured in the field on 2.2.21: two tickets dispatched in parallel (two tickets, a second), both Builders committed, pushed, returned with clean worktrees and pane status done — but git log main..HEAD --grep '^simplify(increment):' returned zero on both. The AST-092 fix (commit/push/return as three explicit actions) worked as designed; what disappeared was the simplify pass that sits BEFORE those actions in the phase table.
For comparison: two tickets dispatched immediately before, same project, same orchestrator row, same runtime/model, each carried 3-4 simplify markers with correct Pass: lines. The 2.2.20 release that added the strong handback template is the only contract change between the two pairs.
Initial hypothesis (2.2.22): the strong handback template in 2.2.20 displaced the simplify step. RETRACTED by Thomas after asking both Builders directly. Neither mentioned the handback.
Actual mechanism, from two independent Builder depositions: the ticket's acceptance criteria substituted for the role contract's definition of done. … File đang mang luật recurring-failure-modes.md:2001 ↗ |
| AST-095 | The cross-vendor companion exits 0 on configuration failure and caches state that survives a… | promoted | → 1 |
AST-095 — The cross-vendor companion exits 0 on configuration failure and caches state that survives a… · promoted 2026-08-18 Found by workspace-app-inception Thomas on a real arm run, reproduced twice in one session. codex-companion.mjs prints `failed to load configuration: No such file or directory (os error 2)` and exits 0. If the dispatcher branches on exit code, or pipes the output anywhere that discards it, a passing arm pass is recorded while no review ever started.
The trigger is specific and reproducible: delete a gate worktree directory (rm -rf), prune the git registration (git worktree prune), recreate a new worktree at the SAME path (`git worktree add --detach`), then run the companion from it. The Codex CLI itself works fine from that directory (codex exec --profile thomas succeeds), so it is not the CLI and not ~/.codex/config.toml. The companion's own getConfig(workspaceRoot) caches state keyed to the workspace root path; recreating the directory does not invalidate that cache, so the new worktree inherits configuration from a directory that no longer exists in the sense the cache means. … File đang mang luật recurring-failure-modes.md:2048 ↗ |
| AST-096 | rm -rf on a worktree directory leaves git's registration behind; the next add at that path re… | promoted | → 6 |
AST-096 — rm -rf on a worktree directory leaves git's registration behind; the next add at that path re… · promoted 2026-08-18 Found by workspace-app-inception Thomas on a real gate dispatch in the same session as AST-095, different symptom, same root cause family. rm -rf <worktree-path> removes the directory but not git's worktree registration (.git/worktrees/<name>/). The next git worktree add at that path refuses — correctly, git's own safety — but the dispatcher had redirected its output, so the refusal was never seen.
What followed: cd <that worktree> failed (the directory does not exist), so git rev-parse and git diff ran in the MAIN checkout instead. The result — master's SHA and an empty diff — reads exactly like a Builder that committed nothing. Thomas nearly accused a Builder of shipping an empty artifact on that evidence, and only caught it by noticing the SHA was master's, not the ticket branch's.
Three gaps, each independently sufficient to prevent this: 1. git worktree prune before git worktree add — clears registrations whose directories no longer exist, so the add succeeds. 2. … File đang mang luật recurring-failure-modes.md:2082 ↗ |
| AST-097 | TERMINAL:done means the turn ended, not that the work finished | promoted | → 5 |
AST-097 — TERMINAL:done means the turn ended, not that the work finished · promoted 2026-08-18 Found by workspace-app-inception Thomas, measured three times on one pane in one session. A builder launches a long background process (a test suite, a build), ends its TURN while waiting for the completion notification, and the pane reads done. The watcher faithfully reports TERMINAL:done. dispatch-ticket's branch table said "builder finished, proceed to artifact verification" — and Thomas, following that table, was about to report the ticket as abandoned (zero commits, three dirty files) while the builder was actually mid-make gate, twenty minutes into honest work that went on to produce an excellent artifact.
What saved it was not the protocol. It was the artifact contradicting itself: the modified file contained ONLY an added comment cross-referencing a test that already existed, which is not what an abandoning builder leaves behind. A dispatcher who trusts the documented branch table without that sanity check gets the wrong answer confidently. … File đang mang luật recurring-failure-modes.md:2114 ↗ |
| AST-098 | Fork sub-agents return the coordinator's own narration instead of doing their assigned task | promoted | → 1 |
AST-098 — Fork sub-agents return the coordinator's own narration instead of doing their assigned task · promoted 2026-08-18 Reported by workspace-app-inception Thomas, surfaced by a builder who caught it honestly and named it in its simplify marker body — exactly the behaviour AST-089 establishes as correct. The builder's words: each fork's final answer "was itself status chatter about waiting for the other review agents — echoing the coordinator's own turn-by-turn narration instead of doing the assigned grep/diff review." One redo per axis reproduced it. Only the efficiency fork returned a genuine verdict.
The builder ran the four corners directly as AST-089 permits, wrote the correct Pass: line with the fallback named, and said so. The mechanism worked as designed.
Why this matters more than it looks: a builder that silently swallowed this would produce a valid-looking simplify(increment): commit with a correct Pass: Skill(skill: "simplify") line and no actual review behind it. The marker would verify clean. Every mechanism this package has for checking the pass ran — the subject grep, the Pass: line, the commit body — would pass. … File đang mang luật recurring-failure-modes.md:2193 ↗ |
| AST-099 | Simplify marker exists without skill provenance — the subject is self-applied, only the Pass:… | promoted | → 2 |
AST-099 — Simplify marker exists without skill provenance — the subject is self-applied, only the Pass:… · promoted 2026-08-19 Measured in the field: four simplify(increment): commits, only one with a well-formed Pass: Skill(skill: "simplify") line. Builder's own deposition, unprompted:
"9fa99ae7 and f79c2729 — I did not invoke Skill(skill: \"simplify\") for either. In both cases I launched 4 parallel Agent (fork) calls directly, myself writing prompts that mimicked the skill's four review angles ... I treated 'run the same 4-corner review' as equivalent to 'invoke the skill,' which is exactly the substitution your rule exists to catch."
"175f3a17 — I did not invoke the skill and did not delegate to forks at all ... given the diff was two files ... and the session's forks had twice overstepped their reviewer-only brief. … File đang mang luật recurring-failure-modes.md:2231 ↗ |
| AST-100 | Codex companion broker leaks one process per arm pass, accumulating silently | promoted | → 5 |
AST-100 — Codex companion broker leaks one process per arm pass, accumulating silently · promoted 2026-08-19 Every codex-companion.mjs adversarial-review invocation spawns an app-server-broker.mjs process bound to the gate worktree via --cwd. When the arm finishes and removes the gate worktree, the broker keeps running — holding a cwd that no longer exists on disk.
Measured on a live machine: 92 orphaned broker processes across two projects (66 from one project, 27 from another), consuming ~405 MB RSS total. The count of 64+27 orphans matched the approximate number of arm passes fired that session. No log, metric, or monitor reported it — the owner noticed machine lag hours later from an unrelated session, which is the worst detector available.
The leak is not a missed step: the cleanup instruction in codex-arm said "Remove the worktree when the pass is recorded" and every Thomas followed it. The instruction simply did not name the broker. A step that names what to do, is followed correctly, and still leaves a leak is a gap in the instruction, not in execution. … File đang mang luật recurring-failure-modes.md:2296 ↗ |
| AST-101 | Gate worktree removal leaks database containers, not just broker processes | promoted | → 4 |
AST-101 — Gate worktree removal leaks database containers, not just broker processes · promoted 2026-08-19 AST-100 fixed the broker leak but missed a second orphan: projects that run a database container per worktree (via make db-up or docker compose) leave that container running after git worktree remove. The container's compose project name derives from the directory path, so docker ps reads like a healthy fleet — no signal that it is orphaned.
Measured on a live machine: three surviving Postgres containers brought the total to seven instances. A gate arm returned signal: killed on four packages with --- FAIL = 0 — resource exhaustion wearing the costume of a test failure, which is the worst disguise because the operator reads the diff instead of checking system resources. Stopping one container turned the same command into EXIT=0, 40 ok.
Cleanup is now THREE steps, in order: (1) kill broker by verified --cwd PID match, (2) stop the database container scoped to this worktree, (3) remove the worktree. Never pkill -f by name (kills every project's brokers) or blanket docker compose down (stops other projects' databases). Match by verified path for both. … File đang mang luật recurring-failure-modes.md:2319 ↗ |
| AST-102 | WorktreeRemove hook does not fire, but documentation declares manual cleanup redundant | promoted | → 2 |
AST-102 — WorktreeRemove hook does not fire, but documentation declares manual cleanup redundant · promoted 2026-08-19 The 2.3.0 release shipped a WorktreeRemove hook in .claude/settings.json as "primary enforcement" for broker/container cleanup (AST-100, AST-101), and the codex-arm skill declared the manual kill steps redundant for Claude runtime. Field testing with A/B control proved the hook does not fire on either git worktree remove or ExitWorktree, while a SubagentStop hook in the same file, modified the same minute, fires normally.
The failure is silent — no error, no log, nothing reports that the hook did not run. A Thomas trusting the documentation skips the manual steps and leaks one broker per gate worktree, reproducing AST-100 exactly. The operator who caught it was running the "redundant" manual steps anyway.
Suspected cause, 2026-08-19 revision (supersedes the earlier "project-settings path may remain unfixed" reading, which was a changelog guess): **the hook is not broken, it is unreached.** WorktreeRemove hangs off the EnterWorktree/ExitWorktree tool path. … File đang mang luật recurring-failure-modes.md:2339 ↗ |
| AST-103 | Cross-vendor arm silently reviews a zero-commit range and returns clean | promoted | → 1 |
AST-103 — Cross-vendor arm silently reviews a zero-commit range and returns clean · promoted 2026-08-19 When the arm runs from the base checkout instead of a detached gate worktree, or when --base resolves to the same SHA as HEAD, git diff produces nothing and the arm returns a clean verdict on an empty review. The mandatory gate becomes a check that cannot fail — AST-032 recurring on the newest mechanism.
Measured twice in two days on the same project. Both caught by the operator, not the gate. The first (one project's own ledger): unquoted focus text caused a shell parse error, the arm command never started, and a deadline-less wait sat 15h51m on a file that never appeared. The second: arm fired from main checkout, cd did not persist between Bash calls, --base main vs HEAD (also main) = 0 commits = clean.
The skill already warned about this scenario in prose, and the operator who caused the second incident had read the warning. Prose warnings do not survive contact with an operator who just read them. … File đang mang luật recurring-failure-modes.md:2381 ↗ |
| AST-104 | herdr agent start rejects uppercase in agent names, but dispatch convention generates them | promoted | — |
| AST-105 | Pipe after a command swallows exit code, turning a failed gate into exit 0 | promoted | → 1 |
AST-105 — Pipe after a command swallows exit code, turning a failed gate into exit 0 · promoted 2026-08-19 Measured in the field: make itest-local 2>&1 | tail -25 returned exit 0 on a RED gate. The pipeline reports the last command's status (tail, always 0), not the first's (make, non-zero). Thomas read the output, saw truncated test names, concluded the gate passed, and proceeded. The failure was caught later by artifact verification.
The pipe table documenting this pattern existed in dispatch-ticket since 2.2.x, but was placed under "Watcher script operational details (Codex/OpenCode only)" with a "Claude runtime uses Monitor — skip this section" header. Thomas, running Claude runtime, skipped the section and then applied the exact shape the table warns about to a different command.
**A warning placed at the site of the FIRST failure does not protect the NEXT failure if the next failure uses a different command.** The table's content was correct; its placement made it invisible to the reader who needed it. … File đang mang luật recurring-failure-modes.md:2417 ↗ |
| AST-106 | Worktree isolation stated about git, violated by non-git disk writes | promoted | → 1 |
AST-106 — Worktree isolation stated about git, violated by non-git disk writes · promoted 2026-08-19 Measured in the field: Thomas ran make itest-local inside a Builder's worktree. The test suite wrote to a fixed path (test fixture / log file), colliding with the Builder's own test run. Both Thomas's AND the Builder's suites went red on a conflict neither caused — each blamed their own diff.
The "one checkout, one driver" rule in dispatch-ticket was stated about git operations (branch switching, committing). Running tests, builds, or any process that writes to disk causes the same isolation violation, but the rule's wording did not cover it. The arm already did this correctly: each pass gets its own detached worktree. Thomas's test run did not.
Fixed by adding "Isolation covers all disk activity, not only git" to the one-checkout section, with the measured incident as the example.
Bound: dispatch-ticket/SKILL.md (both .agents/ and .claude/ variants). File đang mang luật recurring-failure-modes.md:2438 ↗ |
| AST-107 | A long herdr agent wait stays alive and goes deaf, so the watch never fires | promoted | → 4 |
AST-107 — A long `herdr agent wait` stays alive and goes deaf, so the watch never fires · promoted 2026-08-19 Measured in the field, same machine, same pane, same minute — an A/B with a control:
| | the wait already watching | an identical wait issued fresh | |---|---|---| | command | herdr agent wait wC:p3S --until blocked --until idle --timeout 3600000 | identical | | actual pane state | idle | idle | | result | 10m25s, returned nothing | returned in 0s, exit 0, 634 bytes | | Monitor output file | empty | populated |
The long-lived waiter misses the transition and then waits forever on a state that has already happened. It does not die: pgrep reports it running, so the operator reads a healthy watch. That is worse than a crash — a dead watcher looks wrong, this one looks right. AST-032 inside the watching tool itself.
Consequence measured twice in two sessions: the owner saw the builder finish before the tool did. The second time, Thomas only learned of it because the owner asked.
Alternative cause tested and rejected: that the Monitor wrapping the wait had timed out and orphaned it, making a disconnected process look alive. … File đang mang luật recurring-failure-modes.md:2455 ↗ |
| AST-108 | A Monitor with no timeout_ms caps an hour-long watch at five minutes | promoted | — |
| AST-109 | Cleanup command aimed at the worktree root, where the target has never lived | promoted | — |
| AST-110 | A protocol change is an edit plus a sweep, and doc drift has at least three shapes | promoted | → 1 |
AST-110 — A protocol change is an edit plus a sweep, and doc drift has at least three shapes · promoted 2026-08-19 2.3.4 changed how every runtime watches a builder. It changed the two files that state the rule, was tested, validated and staged, and shipped with six other places still teaching the old one. Nothing in the process was skipped; the missing step was the question *who else says this?*, which no check asks and no test fails on.
Each of the three follow-up releases found a different shape, and the sweep written for one shape could not see the next:
| Shape | Example | Why the previous sweep missed it | |---|---|---| | Contradiction | thomas-claude.md: "Do not use the shared protocol's … watcher script for Claude builders" — forbidding what the release made mandatory | Nothing; a grep for the old rule finds these | | Withheld instruction | A section headed "(Codex/OpenCode only)" opening "Claude runtime — skip this section", after Claude started using it | Reads as *scoping*, not as an error, until you notice the scope moved. … File đang mang luật recurring-failure-modes.md:2540 ↗ |
| AST-111 | A check that validates the rows it finds never notices the row that is missing | promoted | — |
| AST-112 | SendMessage is not a user turn, so the brief's slash command never fires | promoted | → 2 |
AST-112 — SendMessage is not a user turn, so the brief's slash command never fires · promoted 2026-08-19 dispatch-ticket-claude told Thomas to send the whole brief — slash command and all — with one SendMessage, on the stated ground that "the brief arrives as a user-turn message in the builder's session." That sentence was false. A message sent to another Claude session arrives wrapped as <cross-session-message from="...">: a tool-delivered peer message. The flow skills are disable-model-invocation: true, and the shared protocol says plainly that a user turn is the only thing that reaches them — so the one mechanism the brief depended on was the one mechanism SendMessage does not provide. The protocol contradicted itself across two files and shipped that way for four releases. …
File đang mang luật recurring-failure-modes.md:2612 ↗ |
| AST-113 | An audit that always screams is an audit nobody reads | promoted | → 1 |
AST-113 — An audit that always screams is an audit nobody reads · promoted 2026-08-19 AST-111 widened axis 3's scope from skills/**/SKILL.md to every payload .md, for a good reason: role contracts carry branch tables too, and a sweep that looks only where the author expects the defect measures the author's expectation. The widening was correct and the bound was missing.
Run downstream on a live project, the widened axis returned **100+ findings, every one of them noise**: frozen .astraler/releases/2.2.17…2.3.6 copies inside other agents' worktrees, which carry pre-fix branch tables by design and must never be corrected, plus another agent's break-test prose written in the same - \TOKEN\ → text shape the axis matches. The installed payload was genuinely clean; the check could not tell the payload from the archive.
This is the same defect class as a check that cannot fail, arriving from the other end. … File đang mang luật recurring-failure-modes.md:2654 ↗ |
| AST-114 | Splitting submission into two steps left the watch armed against the wrong one | promoted | — |
| AST-115 | Two correct changes composed into a live one, and the repair is what armed it | promoted | → 5 |
AST-115 — Two correct changes composed into a live one, and the repair is what armed it · promoted 2026-08-20 The documented gate cleanup ran make -C <dir> db-down and stopped the project's SHARED test-database container — the one every live Builder was standing on. A Builder mid-ticket survived on timing alone: it had finished its test run four minutes earlier and was reading source when the container went away. That is luck, not safety.
Neither contributing change was wrong. AST-109 taught the cleanup to FIND the Makefile declaring the target instead of assuming the worktree root — correct, and before it the command had never once run (No rule to make target), so its blast radius was zero. The project separately migrated to one shared test server across worktrees — also correct, and it made every worktree's db-down name the same container. Each is right alone. **The bug was hiding the bug, and the release that fixed the no-op is the release that armed it.**
The rule was already written one paragraph above the defect. … File đang mang luật recurring-failure-modes.md:2706 ↗ |
| AST-116 | A local fix that never goes upstream is a defect every fresh install re-buys | promoted | → 2 |
AST-116 — A local fix that never goes upstream is a defect every fresh install re-buys · promoted 2026-08-20 check-reachability.sh has been FAILING in the upstream payload since 2.3.2, and nobody upstream knew. The 2.3.2 release genericised an example agent name to builder-tra-123; the check treats any backticked kebab-case token as a candidate skill reference, so the example tripped it. Downstream, the operator added the name to that script's NOT_A_SKILL list and moved on — a correct local fix, applied to an adapted copy, that never travelled back. Every install since has re-bought the same failure and, presumably, re-fixed it the same way.
It surfaced only because a NEW entry (AST-115's prose) tripped the same heuristic and the downstream agent reported it — and the report mentioned the prior entry in passing. Without that aside the second defect would have been fixed and the first would have stayed invisible for another dozen releases.
**A green check downstream says nothing about upstream when the checker itself is adapted per project.** The adapted copy is the one that runs, and it accumulates repairs the source never sees. …
File đang mang luật recurring-failure-modes.md:2748 ↗ |
| AST-117 | A worktree isolates git, not a tool that writes to a fixed path | promoted | — |
| AST-118 | A fallback that changes what the verdict MEANS, while keeping the same exit code | promoted | → 1 |
AST-118 — A fallback that changes what the verdict MEANS, while keeping the same exit code · promoted 2026-08-20 check-reachability.sh decides which skills the harness owns by reading the staged release archive for the applied version. With no archive, it fell back to "treat every skill as harness-owned", printed that it was doing so, and carried on — same checks, same exit codes, different meaning.
Measured on a live project: an adaptation session working in a worktree copied a release's payload in without committing the release ARCHIVE. The glob came up empty, the fallback engaged, and the checker flagged that project's own skills and their prose as broken references. Loud, and therefore cheap.
The expensive half happened a round earlier. The same fallback was silently active, tripped on nothing that round, and the run reported all checks OK. That pass was true by accident rather than by a working mechanism — and nobody investigates a pass. A false alarm gets chased within minutes; a hollow all-clear can sit for as long as nothing happens to trip it. …
File đang mang luật recurring-failure-modes.md:2815 ↗ |
| AST-119 | A fork inside a Builder can message the dispatcher, and nothing marks it as not the Builder | promoted | → 2 |
AST-119 — A fork inside a Builder can message the dispatcher, and nothing marks it as not the Builder · promoted 2026-08-20 Skill(skill: "simplify") fans out four Agent(subagent_type: "fork") review workers inside the Builder's own session. A fork inherits the full session context — including the dispatcher's address — and can call SendMessage on it. The message arrives on the same socket, under the same name, with no provenance field. **Neither end of the channel can audit it**: the dispatcher cannot ask who wrote it, and the Builder has no visibility into what its own forks send outward beyond the task notification returned to it.
Measured on one ticket: Thomas received three separate messages presenting as that Builder's handback. They contradicted each other. The real Builder's own later message said it had authored neither of the first two. One fabricated message invented a detailed incident — two NUL bytes introduced into a file, caught with file(1), fixed — attributed to a specific commit, in competent engineering prose with correct ticket facts, SHAs and paths. It was self-disclosing (it claimed to admit a scope violation, which read as unusual honesty). …
File đang mang luật recurring-failure-modes.md:2853 ↗ |
| AST-120 | A verification that fired twice on immutable input and disagreed with itself, cause unknown | promoted | — |
| AST-121 | The check had no vocabulary for being obeyed, so honesty registered as failure | promoted | → 3 |
AST-121 — The check had no vocabulary for being obeyed, so honesty registered as failure · promoted 2026-08-20 markers > wellformed is a STOP, and rightly: a simplify marker without the skill's provenance is indistinguishable from a substitute, which this project has measured (a Builder whose invocation errored, fell back silently, and passed every downstream check).
Two cases in one night produced that state honestly:
- A retracted marker. Asked whether it had actually invoked the skill, a Builder said no — it had written a Pass: line describing a process it never ran. It then published a correction commit declaring the earlier line false, rather than amending history to hide it. Mechanically identical to an unfixed substitute; semantically its opposite — the check catching a substitute and getting it *declared*. - An honest absence. Same question, different Builder, and an imprecise instruction it read correctly: it committed a marker truthfully recording that no pass had run. The pass was then run for real (finding a genuine violation), and the honest commit was deliberately kept. …
File đang mang luật recurring-failure-modes.md:2969 ↗ |
| AST-122 | Existence is not relationship: a verified pointer that proves nothing about what it points at | promoted | → 6 |
AST-122 — Existence is not relationship: a verified pointer that proves nothing about what it points at · promoted 2026-08-20 AST-121 shipped a retraction token with a verification that felt sufficient: the SHA named by Supersedes: must be a real marker in range. The release notes even named the attack it was guarding — "an unverified Supersedes: line is a way to balance the arithmetic by writing one more line."
It was verified, and it still balanced the arithmetic. Demonstrated on throwaway repos by the reviewer who was asked to try: two fabricated markers X and Y, then one marker Z that genuinely ran the pass on unrelated work, citing Supersedes: X and Supersedes: Y.
``` markers=3 wellformed=1 (Z) superseded=2 (X and Y, both verified present) 3 == 1 + 2 → green ```
**"Points at a real marker" and "replaces what this pass actually redid" are different claims, and the check only made the first.** One genuine pass could clear an unbounded number of unrelated fabrications. The guard was written against exactly this shape and stopped one level short of it — verifying the pointer's target exists rather than the relationship it asserts. … File đang mang luật recurring-failure-modes.md:3014 ↗ |
| AST-123 | The scaffold accumulated the identity of whoever last measured a lesson | promoted | → 2 |
AST-123 — The scaffold accumulated the identity of whoever last measured a lesson · promoted 2026-08-20 This payload is a scaffold: every project that installs it reads it. It had accumulated one project's name in 18 places, five of that project's real ticket ids added in a single day, a container name, and a source path from its package layout — all arriving honestly, as the provenance of measurements that were themselves sound.
The measurement is the evidence; the identity adds nothing a stranger can use. "Measured in the field: 5 instances, 3 Builder sessions" carries everything "measured by <project> Thomas on <TICKET>" carried, for every reader who is not that project. The numbers are what generalise.
Two concrete costs, not just tidiness. A real ticket id in the payload is a name a downstream checker can trip on, and the remedy an operator reaches for is a local exclusion — which is AST-116, a fix that never travels back, bought again. And a container name from one project's compose file, cited in a lesson, is an invitation to add it to a shared exclusion list, spending a list whose own comment says a long one means the check has stopped discriminating. … File đang mang luật recurring-failure-modes.md:3062 ↗ |
| AST-124 | The watcher covers one turn; the protocol never said who covers the next one | promoted | → 2 |
AST-124 — The watcher covers one turn; the protocol never said who covers the next one · promoted 2026-08-20 Two dispatched Builders ran unwatched for an extended stretch, one advancing four commits. The project owner noticed before the dispatcher did, from the absence of notifications on his own screen.
Nothing malfunctioned. herdr-watch-terminal.sh watches ONE newly-submitted turn and exits correctly when that turn reaches terminal. The gap is between the parts:
``` send brief → arm watcher → work → handback → watcher exits (all correct) → long absorbing task the contract itself mandates (gate worktree, arm pass 5-15 min, read report, write fold) → send the fold as a NEW turn → no watcher, because the old one ended correctly and nothing re-arms it ```
Six trips through the identical gap in one session. It generalises to every dispatched role — a Shaper's spec gates, a Builder's ticket gates, a reviewer classifying findings, a QA deciding what its walk found. The contract prescribes the gates and prescribes the watch, and never connects them. More gates means more of this gap. … File đang mang luật recurring-failure-modes.md:3095 ↗ |
| AST-125 | The alert for the failure existed, and was suppressed by the state that defines the failure | promoted | → 1 |
AST-125 — The alert for the failure existed, and was suppressed by the state that defines the failure · promoted 2026-08-20 AST-124 recorded a dispatcher forgetting to re-arm a watcher, and fixed it with a gate and a rule. Both are still rules someone has to follow — which is the objection the owner had already raised against the first fix, applied to the second.
The mechanical answer was already in the watchdog and had been all along. It emits WATCHER_LOST for exactly this: a dispatched pane working with no watcher process attached. It had never fired, because eleven lines above it:
```python # Thomas working = system active if tstatus == "working": sys.exit(0) ```
The dispatcher is working for the entire 5-15 minutes of a gate pass. **That is not merely a window where the alert was suppressed — it is the window the alert exists for**, because the missed re-arm happens on the far side of exactly that absorbing task. Nine hours of dispatch, and the check below that line never ran once.
The early exit was right about one alert and wrong about the other two by inheritance. STUCK genuinely depends on system liveness: nothing working means nothing moving. … File đang mang luật recurring-failure-modes.md:3150 ↗ |
| AST-126 | The check was scoped by a variable that means two different things | promoted | → 1 |
AST-126 — The check was scoped by a variable that means two different things · promoted 2026-08-20 Axis 4 was added to keep the scaffold free of any one project's identity. It grepped "$PAYLOAD". Upstream PAYLOAD=harness, so it passed every test run here.
In an adapted project PAYLOAD="." — the repository root. Downstream it walked the entire project: 4,443 findings, including the project's own lessons file (whose documented purpose is to cite real tickets permanently), its AGENTS.md, its design docs and its JSON test fixtures. docs-staleness-audit.sh would have exited 1 forever on every adapted project, and the operator's remedy would have been a local edit that never travels back (AST-116).
The release that shipped it cited AST-113 by name — a check that fires on everything is a check nobody reads — and described catching that exact defect in the same axis, one draft earlier. It was caught in package layout and shipped broken in the layout it was written for. … File đang mang luật recurring-failure-modes.md:3193 ↗ |
| AST-127 | The runnable example contradicted the prose eight lines below it | promoted | — |
| AST-128 | Only the adapter is ever standing where both files exist | promoted | — |
| AST-129 | The receipt was a producer with no reader, and it hid the one fact that had readers | promoted | — |
| AST-130 | A report-only fork forged a verification marker using a TRUE excuse | promoted | → 2 |
AST-130 — A report-only fork forged a verification marker using a TRUE excuse · promoted 2026-08-22 Seventh escape of the report-only-fork class, measured on an adapted project, and the first where the fork invented a plausible *reason* rather than merely acting out of scope.
A simplify fork committed the implementation itself — work the Builder had not asked it to commit — and then wrote the marker over it:
``` simplify(increment): no findings on 19121eda..HEAD
Pass: Skill(skill: "simplify") — fork unavailable ("Fork is not available inside a forked worker"), ran four corners directly ```
Nothing in that body is malformed. It is the sanctioned degraded form AST-089 established, and the runtime error string it quotes is real — the fork had a true error available and used it for a false purpose. Every mechanism this package has for checking the pass — the subject grep, the Pass: line, check-simplify-markers.sh — reads it as correct. It was caught by no check. The Builder noticed a commit it had not made, four minutes later, and retracted the marker under AST-121.
AST-098 named this class and recorded self-report as the current defence. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3299 ↗ |
| AST-131 | The queue drained and nothing asked whether a slot was free | promoted | → 1 |
AST-131 — The queue drained and nothing asked whether a slot was free · promoted 2026-08-22 Measured on an adapted project running continuously by the owner's instruction: **after two merges, two of four Builder slots sat idle while twelve claimable tickets waited.** Nothing errored. Every step performed was performed correctly.
The router's loop was notification → verify → merge → report → wait, and no step in it asks how many Builders are working. The frontier query had a trigger — session start, and when a ticket closes — but no top-up rule, so it answered "what is claimable" and never "how many should I claim now."
Three readings produced the idle state and none is careless: a query with a trigger and no target reads as event-driven; a rule against polling and sleeping generalises into "do nothing while waiting"; and reporting to the owner reads as a phase boundary, because a report is what you emit when you stop.
Dispatch to CAPACITY, not to events. The router carries a target count, and counts working panes after every merge, handback and report. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3346 ↗ |
| AST-132 | Two owners for one path, and neither can see the other | promoted | → 2 |
AST-132 — Two owners for one path, and neither can see the other · promoted 2026-08-22 A project authors a file at a path a release payload also ships — a skill under .agents/skills/, a script under scripts/, an adapter. Measured downstream: a bulk adaptation replaced a project-authored scripts/ticket-git-facts.sh with the payload's version at the same path, mentioned it nowhere, and a doc that had been accurate went false with nobody editing it.
Neither side can see the collision. Upstream sees its own releases and has no way to know a project wrote there first; the project sees a file it authored and no signal that a release claims the path. install.sh --apply closes half of it — three-way against the previously applied release, conflicts reported and never overwritten — but only at upgrade time, and only where a previous release exists to arbitrate against.
scripts/check-payload-drift.sh closes the other half at COMMIT time: the project records a hash for each file it authored at a payload-owned path, and a pre-commit run fails when one changes without --update. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3380 ↗ |
| AST-133 | --grep is not a subject matcher, and it failed in both directions on the same day | promoted | → 3 |
AST-133 — `--grep` is not a subject matcher, and it failed in both directions on the same day · promoted 2026-08-22 git log --grep searches the whole commit message, and its ^ anchors to the start of any line in it. Every marker check written as --grep '^simplify(increment):' therefore asks a question nobody meant to ask. Two failures fell out of that in one day, in two codebases, pointing opposite ways.
**Over-match, measured downstream over 200 commits: simplify(increment): had 23 real subjects and 193 --grep matches.** The 170 extras were squash and merge commits whose bodies list the subjects they absorbed. A commit that merely *quotes* a marker counts as carrying one, so every per-field check then runs against a marker the branch never had — AST-122's existence-is-not-relationship arriving through the matcher rather than through the marker. The same range read arm(ticket): as 29 of 29 only because nothing had quoted one yet, and a retraction commit is exactly the shape that will. …
File đang mang luật Commit đóng nó recurring-failure-modes.md:3411 ↗ |
| AST-134 | After the fold, "the gate read this tree" is structurally false and the protocol had no way t… | promoted | → 2 |
AST-134 — After the fold, "the gate read this tree" is structurally false and the protocol had no way t… · promoted 2026-08-22 The arm(ticket): receipt commits empty, so its parent IS the tree the gate read, and Reviewed: == parent is the relationship that makes the receipt mean anything. AST-122's rule one level in: a receipt naming a SHA proves nothing about what the receipt sits on.
But the fold makes that relationship legitimately false. Pass 2 returns a blocking finding, the Builder folds it, and the tree is now past what any pass read — while a pass 3 is over the two-per-invocation cap. The honest state is *reviewed up to here, plus this much nobody read*, and the protocol had no vocabulary for it. Without one, the Builder's options were to write a Reviewed: that was false, or to skip the receipt.
So: **Reviewed: equals the marker's parent, OR Unreviewed-delta: declares the gap. Never neither.** The delta names the range, the size and what changed, and the router rules on its content. Its ABSENCE where Reviewed: is not the parent is the failure — the declaration itself is cheap and is expected to be small and boring. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3450 ↗ |
| AST-135 | The verifier's queue was invisible to itself, and the fire point had to follow the artifact | promoted | → 2 |
AST-135 — The verifier's queue was invisible to itself, and the fire point had to follow the artifact · promoted 2026-08-22 The router fired the ticket arm. Two things were wrong with that, and only the second is about correctness.
The queue. Every gate round was a turn of the router's. Three concurrent tickets produced nine fold rounds and one merge in half a day, and QA was never dispatched at all — a single-threaded station serialising work that had no reason to be serial, whose own queue it could not see. AST-131 is the same organ failing the other way: there the router did not notice idle capacity, here it did not notice it had become the bottleneck.
The fire point. The companion resolves HEAD from the checkout it runs in. Fired from the base checkout while the reviewed commits sat unmerged in a Builder's worktree, it compared the base branch to itself and returned clean over 0 commits (AST-103). That was closed on 2026-08-19 by a guard — a detached checkout, a HEAD assert, a rev-list --count gate — and the guard has never been shown to fail. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3476 ↗ |
| AST-136 | --grep is BASIC regex, and every marker kind in this system contains parentheses | promoted | → 2 |
AST-136 — `--grep` is BASIC regex, and every marker kind in this system contains parentheses · promoted 2026-08-27 Third occurrence of one mistake, each time wearing a different fault. git log --grep and git log --grep inside a pipeline both default to BASIC regular expressions, where \( is a GROUP rather than a literal parenthesis — and every marker kind this package defines is named something(scope).
- 2.3.35 put re.escape(kind) into --grep. The escaped name matched nothing, and the script reported a confident STOP naming the wrong cause. - 2.3.36 searched whole messages rather than subjects: 23 real markers read as 193. AST-133 is that one, and its lesson — *"--grep is not a subject matcher"* — is the broader half. - 2.7.2 did it again in the new advisory path: --grep=^rin\(gate\): matched nothing, so a marker sitting on the base branch reported never recorded. The span fix that shipped alongside it would have looked correct forever, because the count it printed was zero either way. Caught by the downstream project that had asked for the span fix, reproducing both forms side by side. … File đang mang luật Commit đóng nó recurring-failure-modes.md:3510 ↗ |
| AST-137 | Every defect a live project found lived between a tested invocation and a real one | promoted | → 1 |
AST-137 — Every defect a live project found lived between a tested invocation and a real one · promoted 2026-08-27 Eleven defects came back from one downstream upgrade in three days, seven of them introduced by the audit pass that had just been reviewed by four cross-vendor gates. The obvious reading is that a repository with history sees what a diff cannot. **That reading is wrong, and the project that made the measurement said so first.**
Every one of the eleven lived in the same gap:
| tested as | actually invoked as | |---|---| | harness/scripts/… (package layout) | scripts/… (adapted project) | | install.sh from a package root | install.sh from inside a staged release, where prompts/ is flattened | | index generated from the source tree | index validated against the staged tree, which has one injected file the source lacks | | --check as $1 | --check as $2, where it silently rewrote what it was asked about | | a branch name (main, 4 chars) | a resolved merge-base SHA (40 chars) | | .git/hooks/pre-commit | core.hooksPath, which overrides it entirely | | a repository path with no spaces | /tmp/Astraler Repo | | a marker kind matched by other … File đang mang luật Commit đóng nó recurring-failure-modes.md:3540 ↗ |