diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 924a20ca1..6513753f5 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,46 @@ +## 2026-08-30 — E-DESTRUCTIVE-PREPEND-TRUNCATES-BEFORE-READ-1 — the prepend one-liner `open(p, "w").write(entry + open(p).read())` destroyed 5876 lines of PR_ARC_INVENTORY; the pattern is now PROHIBITED + +**Status:** FINDING + PROHIBITION (operator-directed, 2026-08-30). Measured on +a real loss, restored same day (#1082, merge `82679c3a`), pattern banned in +`CLAUDE.md` § In-Session Orchestration Discipline in the same PR. +**Confidence:** Certain — the mechanism is Python evaluation order, reproduced +and named, not inferred. + +The #1081 board-hygiene commit prepended its arc entry to +`.claude/board/PR_ARC_INVENTORY.md` with + +```python +open(p, "w").write(entry + "\n" + open(p).read()) # PROHIBITED +``` + +Python evaluates `open(p, "w")` — which **truncates the file to zero bytes** — +before the argument expression's `open(p).read()` runs. The read-back returns +the empty string, and the write emits only the new entry: the append-only +ledger collapsed 5876 → 32 lines on main, leaving a dangling #1079 +self-reference as the visible wound. The same holds for every +truncate-then-read-same-path shape in any language (`>` shell redirection into +a pipeline that reads the same file, `fs.writeFileSync(p, x + fs.readFileSync(p))` +is safe only because JS evaluates arguments first — do not rely on remembering +which language does which). + +**The prohibition (P0, workspace-wide):** never open a file for +writing/truncation in the same expression, pipeline, or statement that still +needs to READ that file. Prepends are three steps: (1) read into a variable, +(2) compose in memory, (3) write — or use the `Edit` tool, which is +read-anchored by construction and the default for board files anyway. + +**The falsifier (mandatory after every ledger write):** `wc -l` the file. +An append-only file that got SHORTER is always a defect — no exceptions, no +"it probably deduplicated something". + +Cross-refs: census trap 10 +(`docs/architecture/COGNITIVE-FABRIC-CENSUS-2026-08-30.md` §8.3); restore PR +#1082; the pre-existing "Read before Write, always" P0 in `CLAUDE.md` — this +entry is its sharper edge: read-before-write applies WITHIN a single +expression's evaluation order, not just across tool calls. + +--- + ## 2026-08-30 — E-A-FLOOD-THROTTLE-IS-NOT-A-DISCRIMINATOR-1 — the census equated `hub_indegree` with a specificity test; they are opposite where it matters **Status:** FINDING (measured, review-driven). Raised as codex P2 on #1079, diff --git a/CLAUDE.md b/CLAUDE.md index 8c3541fe0..f1e8a4127 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1395,6 +1395,25 @@ instead of *built from state*. If you see this on your own commit, you just overwrote committed work. Revert with `git restore ` and use `Edit` for any genuine refinement. +**P0 Rule: the destructive-prepend one-liner is PROHIBITED** +(added 2026-08-30 after it destroyed 5876 lines of `PR_ARC_INVENTORY.md` +in #1081; restored in #1082; full entry +`E-DESTRUCTIVE-PREPEND-TRUNCATES-BEFORE-READ-1`, census trap 10): + +```python +open(p, "w").write(entry + open(p).read()) # NEVER — truncates before it reads +``` + +`open(p, "w")` truncates the file BEFORE the argument's `open(p).read()` +runs; the read-back is empty and the file's history is gone. Prohibited in +every form: any expression, statement, or shell pipeline that opens a file +for writing/truncation while it still needs to READ that same file +(`sort f > f` is the same defect). Prepend in three steps — read into a +variable, compose in memory, write — or use the `Edit` tool, which is +read-anchored by construction. **Mandatory post-check after every board / +ledger write: `wc -l` the file. An append-only file that got SHORTER is +always a defect.** + **Tool-reach reminder for deferred tools.** `AskUserQuestion`, `TodoWrite`, `WebSearch`, `WebFetch` are namechecked in the Claude Code system prompt but sit behind `ToolSearch` in Opus 4.6 — their schemas are not loaded by default.