RUL-024 — "pre-existing" is a diagnosis, not a disposition
Ruling
"new rule, create a hook, when you output the word prexisting failure print a message that tells you to fix it" (verbatim, 2026-07-28).
Proving that a failing test, warning, leak or defect predates the current change is useful evidence about cause. It is not a decision about what happens next, and it is not permission to leave the thing red.
On labelling any defect "pre-existing" / "already failing" / "unrelated" / "fails on baseline" / "not introduced by this change", exactly one of these must follow in the same turn:
- Fix it — root-cause, fix, prove green, say so; or
- Surface it as a named blocker — if the fix carries a real behaviour or scope decision that is the user's to make, state the decision explicitly with a recommendation. Not buried in a status line; or
- Show it is genuinely unreachable — a third-party binary that cannot be rebuilt, hardware, an upstream repo not under our control — stating why it is unreachable and what was done to establish that.
Restating the label and moving on is the violation. This is the specific shape
that project memory feedback-fix-everything-own-everything
forbids: authorship and provenance are irrelevant to whether a broken window
gets fixed. It sharpens RUL-006 — fix-on-sight
already covers "I saw an error"; this covers the narrower dodge of classifying
the error so it looks handled.
Evidence
2026-07-28, during the codebase-memory-mcp fork work, three failures were
reported across many turns as "pre-existing", each time with correct evidence and
each time with no action:
subprocess_quiet_timeout_kills_ignoring_tree— correctly proven pre-existing by stashing the whole diff, rebuilding baseline, and reproducing 3/3. The proof was real; the defect was still left red for the entire session, and the racy 500 ms probe that causes it was diagnosed but never fixed.- 2×
daemon_runtime— asserted three separate times to be caused by a full/tmp, on the strength of reading the test source. That inference was never verified against a re-run, and was stated as settled fact until challenged with "why didn't you run it?".
The pattern: the label ended the investigation instead of starting the fix, and in the second case the label was not even established — it was assumed.
Enforcement
- hook .claude/hooks/no-preexisting-excuse.sh
Verification
.claude/hooks/no-preexisting-excuse.sh is a Stop hook registered in
.claude/settings.json. It reads the last assistant turn from the transcript
(thinking and output text) and emits {"decision":"block","reason":...}
when it matches any of:
pre-?existingalready (failing|red|broken)(unrelated|not caused by|not mine|not ours) … (failure|test|bug|error|defect)fails on baselinenot introduced by (this|my) (change|diff|work)
The block returns the three permitted dispositions above, plus up to three
context snippets showing what triggered it, so the reminder is specific rather
than generic. stop_hook_active short-circuits the hook, so it fires at most
once per turn and cannot loop.
Tested on three paths at creation: positive (blocks with reason), negative
(clean turn → silent), loop-guard (stop_hook_active: true → silent).