Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,13 @@ went red, and no workflow reads that label back. So the Console finds both by as
narrate each. Respect the ledger gate: never `--no-verify`, never a rename workaround.
- A long commit message can fail to parse. The harness reported a 1015-byte ceiling when it refused
one on 2026-09-02; that number is not recorded anywhere in this repository, so treat it as a
measurement rather than a contract. Write the message to a file inside the project
and use `git commit -F <file>`.
measurement rather than a contract. Write the message to a uniquely-named file **inside your own
worktree**, use `git commit -F <file>`, and delete it. Not the per-worktree git dir: it sits under
the primary checkout's path, so `worktree_gate.ps1` refuses a `Write` there.
**Never the harness scratchpad, whatever its system prompt says about isolation.** That directory
is shared with every subagent and background task the session spawns, so a sibling writing the same
generic name between your write and your `commit -F` silently substitutes its message for yours --
measured 2026-09-03, BACKLOG #1440. Same rule for any file whose content is later fed to a command.
- **Every seat pushes its own branch and opens its own PR, without asking.** Owner ruling 2026-08-29,
anchored at `refs/liaison/owner-ruling-20260829-push` (`987705dfb`), in their words: *"Sessions
push their own."*
Expand Down
91 changes: 91 additions & 0 deletions docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22560,6 +22560,7 @@ So `test_the_script_prefers_its_own_repo_over_an_earlier_path_entry` supplies th

**Adjacent and NOT fixed here, named rather than numbered.** `docs/WEBCONSOLE-PACKAGE.md`'s seam-refresh procedure is stale in three steps left behind by #1220: it says to bump `ENGINE_UI_SEAM` by hand (`1` to `2`) when the value is a derived digest, it says to update curated lists in this script that #1220 retired, and its step 5 prescribes `python scripts/webconsole_seam_snapshot.py > tests/golden/...`, the shell redirect this script's own docstring forbids because PowerShell's `>` writes UTF-16LE with a BOM into a file the test reads as UTF-8. That is doc drift with its own cause and it wants its own item; folding a documentation rewrite into a `sys.path` fix would make both harder to review.


## 1434. the tooling-partition gate names the file it rejects and not the remedy, so the Builder who trips it has already exited

> 🚧 **Filed 2026-09-03. The failure message is fixed on this branch. Five open pull requests still carry unregistered test files, and only their own branches can fix them -- they are named below.** `tests/test_tooling_partition.py::test_every_non_engine_test_is_classified` fails when a file matching `tests/test_*.py` sits in neither `tests/tooling_manifest.txt` nor `_STAYS_WITHOUT_IMPORTING`. It is not deselected by `-m 'not tooling'`, so it reds **all three required `test` legs at once**. PR 774 hit exactly that, and its Builder's process had exited before any leg reported.
Expand Down Expand Up @@ -22627,3 +22628,93 @@ All 137 listed entries were swept for a repo-rooted read of `messagefoundry/**`.

1. **The five pull requests above.** A Builder must not push to another seat's branch, so each is fixed by whoever next touches it: append `tests/<name>.py` to `tests/tooling_manifest.txt`, keeping the list alphabetical. **The gate is passable and two pull requests that night passed it correctly -- nothing here asks for it to be weakened.**
2. **Three copies of the manifest parser, pinned against nothing.** This file, `_tooling_basenames` in `tests/conftest.py` (the copy that actually applies the marker), and `_manifest_paths` in `tests/test_ci_tooling_gate.py` all implement the same rule. Extracting one `tests/_tooling_manifest.py` is the real fix. Related: `test_every_manifest_entry_trips_its_own_gate` in `tests/test_ci_tooling_gate.py` feeds each manifest entry back as its own changed path, so on the manifest arm every entry matches itself unconditionally.

## 1440. Commit messages and command-fed files must not live in the shared scratchpad

> 🔢 **Filed 2026-09-03 (dazzling-jang-df503f) -- not started.** A session wrote a commit message to `<scratchpad>/msg1.txt` and then ran `git commit -F <scratchpad>/msg1.txt`. Between those two steps another agent overwrote that file with **its own** commit message, for a different backlog item. The scratchpad the harness advertises as *"session-specific, isolated from the user's project"* is shared by a session and **every subagent and background task it spawns** -- all of them are handed the identical path, and all of them are told the same sentence about isolation. **The substitution is invisible in `git commit -F` output.** What stopped the commit was the ledger claim gate, which exists for an unrelated reason and fired only because the substituted subject happened to cite a number this worktree did not hold.
>
> **Scored 2026-09-03 -> P2.** Value **6/10** · Difficulty **2/10** · _quick win_. The remedy is one convention line plus a one-line habit change, and the hazard is live for every manager seat running concurrent subagents. Value is held at 6 rather than higher because the failure corrupts the repository's own record and never reaches shipped code; it is held above the low band because the one control that caught it is incidental and fails open on the commonest shape -- a substituted message citing **no** backlog number, or citing one the worktree does hold, passes the gate and lands. Difficulty 2: `CLAUDE.md` sec. 5 already tells a Builder to use `git commit -F <file>` and already says *"inside the project"*, so the edit narrows an existing sentence rather than adding a rule.
> Verdict: build
> Research: none
> Closing-act: code

**Cluster:** fleet conventions / commit hygiene. **Priority:** P2. **Verdict:** build.
**Severity:** no engine effect, no PHI axis, and **no deployment axis (sec. 0)** -- nothing here reaches shipped code. What it corrupts is this repository's own commit record, so the cost is a commit whose message describes work it does not contain.

### The failure, as reported

Measured 2026-09-03 in another seat's worktree while building #1395. **Reported to this author rather than witnessed by it, and marked as such deliberately:** the session wrote #1395's commit message to `<scratchpad>/msg1.txt`, ran `git commit -F` against that path, and the commit-msg claim gate (`scripts/coord/claim.ps1`) refused with `BACKLOG #1414 is NOT CLAIMED` while the session believed it was committing #1395.

**The artifact was still on disk and is quoted here from a direct read.** `msg1.txt` holds a complete, well-formed commit message whose subject is:

```
feat(coord): count stranded allocations, and stop a correct refusal burning a number (BACKLOG #1414)
```

A sibling `msg2.txt` in the same directory holds a second message from the same work stream (`docs(ledger): record the measured recoveries, and correct three claims in #1414`). Neither mentions #1395. So the reported substitution is confirmed by the residue, not only by the report.

### The framing in the original finding is wrong, and the correction matters for the remedy

The finding was filed as *"the session-UUID segment does not partition; concurrent sessions in the same project resolve to one directory."* **That is not what happens.** The UUID segment partitions top-level sessions correctly. What it does not partition is a session from its own agents.

**Measurement A -- direct, and it cannot return an ambiguous answer.** This author wrote `probe_marker.txt` into its own scratchpad, then spawned one subagent whose entire task was to quote back the scratchpad path from **its own** system prompt and list that directory. It returned this session's path verbatim, UUID segment included:

```
<local-temp>\claude\<working-directory-slug>\<session-uuid>\scratchpad
```

Its `ls -la` showed `probe_marker.txt` already present, it read the parent's marker back verbatim, and its own write landed beside it. **The positive control is built into the shape of the test:** had the two directories differed, the subagent's listing could not have contained a file only the parent wrote.

**Measurement B -- the control that must fire in the opposite direction.** If the UUID segment did not discriminate sessions, every project slug under the temp root would carry exactly one UUID child. Counted on this machine: the busiest slug carries **209**, the next **76**, and at least 18 further slugs for this repository carry between 9 and 25. The UUID does discriminate.

**Measurement C -- structural, and it closes the peer-session question.** The slug is a function of the working directory, with each path separator rewritten to a dash. Every fleet seat runs in its own worktree, so two top-level seats have different slugs and cannot collide by construction. Two sessions could only share a directory by sharing **both** a working directory and a session UUID.

**So the sharing boundary is the top-level session, and the writers inside it are the session, its subagents, its background tasks and its hooks.** The observed directory is consistent with exactly that: the `3a8d4cbd` scratchpad sits beside a `tasks/` directory holding both subagent-shaped (`a` plus 16 hex) and background-task-shaped output files, and it accumulated four unrelated work streams inside a six-minute window (`board.py` 17:49, `gd1379_rows.py` 17:51, `strand_probe.py` 17:52, `final-sweep.txt` 17:53, `msg1.txt` 17:55). The seat is a manager (`Manager 2-3`), which is the seat shape that runs concurrent subagent Builders.

**AND THE ISOLATION IS ASYMMETRIC, WHICH IS WHY THE MISTAKE IS EASY.** A subagent running under worktree isolation gets its **own git worktree, its own branch and its own `.venv`** -- `git worktree list` here registers **76** of them, named `agent-` plus the same 16-hex id that names its output file in the session's `tasks/` directory (`agent-ab736d15cb3676e08`, on branch `b-1375-install-gate-allowlist-merge`, is one). So an agent that checks whether it is isolated gets **yes** from git, from its branch, and from its interpreter, and the harness then tells it the scratchpad is *"session-specific, isolated from the user's project"* as well. Every signal it can cheaply consult agrees, and the one directory that is actually shared is the one it is told is private.

**A SECOND, INDEPENDENT OBSERVATION AT n=5, BY A ROUTE THAT NEEDS NO EXPERIMENT.** A peer manager seat reviewing this row reported the same sharing from the other side. It ran nothing: it listed its own scratchpad after four hours of ordinary work and found **22 files, exactly one of which it had written**. The rest were deposited by **at least five distinct agents it had spawned** -- four builders and a reviewer -- spanning about two hours by file mtime. **The peer separated its measured half from its inferred half and that split is preserved here:** the listing and the mtimes are measured; which agent wrote which file is inferred from filenames matching what each agent reported doing.

**That adds two things.** First, those builders each ran **in their own git worktree on their own branch** and still wrote into the parent's scratchpad -- the asymmetry above, observed from the parent side rather than inferred from worktree names, and at n=5 rather than n=1. Second, **a near miss on a generic filename that is not a commit message**: exactly one `fullsuite.log` existed while at least two builders reported running full suites. **Whether one overwrote the other is NOT established, and the peer explicitly declined to claim it.** What is established is that the collision surface is ordinary -- two agents naming a log the obvious thing -- which is the evidence that the class is wider than commit messages.

**AND IT IS THE REPRODUCTION MOST READERS SHOULD USE.** Measurement A needs a subagent spawned on purpose. This needs `ls`. Any seat that has been running agents for a few hours can list its own scratchpad and see the answer without setting anything up.

**The hazard is unchanged by the correction, and arguably worse.** Peer seats were never going to collide. Sibling subagents of one manager collide by default, they all believe the directory is private to them, and there are more of them than there are seats.

### What caught it was a gate built for something else, which is the argument for keeping it

`scripts/coord/claim.ps1` exists to stop a seat committing under a backlog number it does not hold. It caught a clobbered message file -- a defect nobody designed it for -- because the substituted message cited `#1414` and this worktree held `#1395`.

**Nothing else in the chain could have caught it.** `git commit -F` reads the file and prints a summary of the commit it made, not a diff against what the author intended; a message swapped between the write and the read produces no warning anywhere. The gate is the only reader that compares the message's *content* against something the session independently knows about itself.

**It is incidental cover, not a control, and must not be recorded as one.** It fires only when the substituted message cites a backlog number **and** that number is not claimed by the committing worktree. A substituted message citing no number, or citing a number the worktree does hold, lands silently. That is the common case, not the exotic one.

### Remedy

**Write any file whose content is later fed to a command into the session's own git worktree, under a name that cannot collide.** Commit messages and PR bodies are the concrete instances; the class is wider.

**A uniquely-named file in the worktree root, removed after the commit, is the whole remedy.** The worktree is already per-session, the harness already treats it as the place you may write, and a name carrying the item number cannot collide with a sibling's.

**THE ATTRACTIVE ALTERNATIVE DOES NOT WORK, AND THIS WAS FOUND BY TRYING IT RATHER THAN BY REASONING.** The per-worktree git directory looks like the ideal home -- unique per worktree, never a commit candidate, `git rev-parse --git-dir` names it:

```
git rev-parse --git-dir
<primary-checkout>/.git/worktrees/<worktree-name>
```

**`worktree_gate.ps1` refuses a `Write` there.** The gate matches on the primary checkout's path prefix, and `.git/worktrees/<name>/` is under it, so the private per-worktree directory is indistinguishable from the shared primary tree as far as the gate is concerned. The refusal is correct at its own rule and the path is simply unavailable to the tools an agent has. **A first draft of this item recommended it, and of `CLAUDE.md`'s amendment too;** both were corrected only because the author tried the write instead of trusting the reasoning.

**`CLAUDE.md` sec. 5 should carry the reason, because the existing sentence is right and still lost this commit.** It already says to write a long message to *"a file inside the project"* and use `git commit -F <file>`. The scratchpad is not inside the project, so the rule was not followed -- but the rule does not say **why** the location matters, and the harness spends a paragraph telling every agent the scratchpad is isolated and is the place for *"any file that would otherwise go to `/tmp`"*. Between an unexplained repository convention and an emphatic harness instruction, agents take the harness. The amendment names the scratchpad as the wrong place and gives the one-line reason.

**Do not change the scratchpad mechanism.** It is harness behaviour, not repository behaviour, and the convention fix is sufficient and cheap.

### What is not resolved

**Which agent wrote the substituted message is not established, and is not worth establishing.** What is established is that the writer shared that exact directory, and the only writers that can are the session itself, its subagents, its background tasks and its hooks. The remedy does not depend on which.

**Whether a subagent can be told a different scratchpad path is unknown.** No option for it was looked for, and if one exists it belongs to the harness rather than here.

**Whether a resumed or restarted session reuses its UUID was not measured**, and it is the one remaining way two top-level sessions could land on one directory. It cannot produce the failure above -- a session is not concurrent with itself -- but it would leave a previous run's files under a name a later run treats as its own. Raised by a peer seat reviewing this row; recorded as a candidate rather than answered.

**A NOTE ON REPRODUCING THIS, because the first framing did not reproduce and that nearly buried it.** A reader in their own worktree who lists their own project directory will find exactly one session UUID and correctly conclude that nothing is shared with other seats. That result is right and it is not a refutation: the collision is inside one session, so the reproduction is to write a marker into the scratchpad and then spawn a subagent to list that same directory. **The two colliding paths are deliberately not published here** -- they carry a host account name and worktree slugs, which the forbidden-content gate blocks and which its own message says must be removed rather than allowlisted. The schematic form above is the publishable version.

Loading