The test suite writes temp-dir project entries into the operator's real ~/.claude.json on every run, and the guard built to stop that passes anyway #646

Open
opened 2026-10-02 04:15:46 +02:00 by ltms · 1 comment
Owner

What happens

mvn -o test adds temp-dir project entries to the operator's real ~/.claude.json — the live Claude Code config on the host — every time it runs. 65 had already accumulated there before today.

There is a guard against exactly this, ClaudeCodeLauncherTest.noFixtureSeededTheDefaultClaudeJson, and its own message names the hazard precisely:

a fixture in this class seeded the DEFAULT .claude.json (the operator's real file when user.home is not redirected) with N temp-dir project entry/entries … Give that fixture's profile a @TempDir configDir

The guard passed while three entries were added. So the writing is real and the guard does not see it.

Measured on 2026-10-01/02

Two full runs in a throwaway detached worktree. I only ever read ~/.claude.json, and only counted keys under the JVM temp dirs — nothing in the operator's file was printed, copied or written back by me.

Run main temp-dir keys before → after ClaudeCodeLauncherTest
baseline, no new test 68397f7 65 → 68 passed
with PR #645 applied 68397f7 + 1 test file 68 → 71 passed

Both runs: Tests run: 1894 / 1896, Failures: 0, BUILD SUCCESS.

Two conclusions, and they are separate:

  1. +3 entries per suite run, reproducible, and nothing fails. The deltas are identical with and without the new test, so this is pre-existing and unrelated to #612 Shape A.
  2. The guard is scoped too narrowly to catch it. It is bracketed around its own class — it captures tempProjectKeysBefore, runs that class's fixtures, then diffs. The suite runs serially in one JVM fork (fleetd/pom.xml sets no forkCount, reuseForks, and there is no junit-platform.properties parallel config), so entries written by any other test class land outside its bracket and are invisible to it. The guard answers "did my fixtures do this", while the thing worth knowing is "did the suite do this".

That is why 65 could accumulate under a guard whose whole purpose is to prevent accumulation.

Why it matters

This writes to a real file outside the build tree, on the machine running the tests. Under the lead's own home directory that file is the live Claude Code configuration. The entries are harmless-looking stale project keys, but the property being violated is "a test run does not modify the operator's environment", and 65 of them is the evidence that nobody has been told.

It also makes the guard actively misleading. A green noFixtureSeededTheDefaultClaudeJson reads as "the suite left your config alone". It does not mean that.

How it surfaced

A #612 Shape A implementer reported, honestly and unprompted, that one of its four mutation cycles saw noFixtureSeededTheDefaultClaudeJson fail and the suite total move between cycles. It flagged this as "the other failure" rather than quietly dropping it. Chasing that report is what produced the measurement above. The implementer's own test turned out to be innocent — but the anomaly it reported was a real defect in something else.

So the intermittency is the remaining open question: the guard fired for that worker, and passed in both of my runs. That means at least one seeding fixture sometimes lands inside ClaudeCodeLauncherTest's bracket and sometimes outside it. I have not identified which fixture writes the three entries, and I am not going to name one without measuring it.

Suggested direction

  • Find the three writers first. The decisive instrument is a before/after diff of the key names around individual test classes, not around one class. The three added keys name their own temp dirs, so they point straight at the fixtures that created them.
  • The real fix is redirecting user.home (or giving every claude-code profile fixture a @TempDir configDir, which is what the guard's message already advises) so no test can reach the real file at all. A guard is the wrong primary defence here; not having the path is the right one.
  • If the guard is kept, widen its bracket to the whole suite and say in its message that a green result covers only the classes inside that bracket. A check whose scope is narrower than its claim is the shape this repo keeps paying for — see #642 for the same defect in a different guard, and the "a filter you omit is a filter not applied" pattern.

Related

  • #645 / #612 — where the anomaly was reported. PR #645 is measured innocent above and was merged on that basis.
  • #642 — a guard whose assertion works and whose scope is wrong. Same class of bug, different file.
## What happens `mvn -o test` adds temp-dir project entries to the **operator's real `~/.claude.json`** — the live Claude Code config on the host — every time it runs. 65 had already accumulated there before today. There is a guard against exactly this, `ClaudeCodeLauncherTest.noFixtureSeededTheDefaultClaudeJson`, and its own message names the hazard precisely: > a fixture in this class seeded the DEFAULT .claude.json (the operator's real file when user.home is not redirected) with N temp-dir project entry/entries … Give that fixture's profile a `@TempDir configDir` **The guard passed while three entries were added.** So the writing is real and the guard does not see it. ## Measured on 2026-10-01/02 Two full runs in a throwaway detached worktree. I only ever *read* `~/.claude.json`, and only counted keys under the JVM temp dirs — nothing in the operator's file was printed, copied or written back by me. | Run | `main` | temp-dir keys before → after | `ClaudeCodeLauncherTest` | |---|---|---|---| | baseline, no new test | `68397f7` | **65 → 68** | passed | | with PR #645 applied | `68397f7` + 1 test file | **68 → 71** | passed | Both runs: `Tests run: 1894` / `1896`, `Failures: 0`, `BUILD SUCCESS`. Two conclusions, and they are separate: 1. **+3 entries per suite run, reproducible, and nothing fails.** The deltas are identical with and without the new test, so this is pre-existing and unrelated to #612 Shape A. 2. **The guard is scoped too narrowly to catch it.** It is bracketed around *its own class* — it captures `tempProjectKeysBefore`, runs that class's fixtures, then diffs. The suite runs serially in one JVM fork (`fleetd/pom.xml` sets no `forkCount`, `reuseForks`, and there is no `junit-platform.properties` parallel config), so entries written by **any other test class** land outside its bracket and are invisible to it. The guard answers "did *my* fixtures do this", while the thing worth knowing is "did *the suite* do this". That is why 65 could accumulate under a guard whose whole purpose is to prevent accumulation. ## Why it matters This writes to a real file outside the build tree, on the machine running the tests. Under the lead's own home directory that file is the live Claude Code configuration. The entries are harmless-looking stale project keys, but the property being violated is "a test run does not modify the operator's environment", and 65 of them is the evidence that nobody has been told. It also makes the guard actively misleading. A green `noFixtureSeededTheDefaultClaudeJson` reads as "the suite left your config alone". It does not mean that. ## How it surfaced A #612 Shape A implementer reported, honestly and unprompted, that one of its four mutation cycles saw `noFixtureSeededTheDefaultClaudeJson` fail and the suite total move between cycles. It flagged this as "the other failure" rather than quietly dropping it. Chasing that report is what produced the measurement above. The implementer's own test turned out to be innocent — but the anomaly it reported was a real defect in something else. So the intermittency is the remaining open question: the guard fired for that worker, and passed in both of my runs. That means at least one seeding fixture sometimes lands **inside** `ClaudeCodeLauncherTest`'s bracket and sometimes outside it. I have not identified which fixture writes the three entries, and I am not going to name one without measuring it. ## Suggested direction - **Find the three writers first.** The decisive instrument is a before/after diff of the key *names* around individual test classes, not around one class. The three added keys name their own temp dirs, so they point straight at the fixtures that created them. - **The real fix is redirecting `user.home`** (or giving every claude-code profile fixture a `@TempDir configDir`, which is what the guard's message already advises) so no test can reach the real file at all. A guard is the wrong primary defence here; not having the path is the right one. - **If the guard is kept, widen its bracket to the whole suite** and say in its message that a green result covers only the classes inside that bracket. A check whose scope is narrower than its claim is the shape this repo keeps paying for — see #642 for the same defect in a different guard, and the "a filter you omit is a filter not applied" pattern. ## Related - #645 / #612 — where the anomaly was reported. PR #645 is measured innocent above and was merged on that basis. - #642 — a guard whose assertion works and whose scope is wrong. Same class of bug, different file.
Author
Owner

Still growing — 83 temp-dir keys now, up from 65 when this was filed

Lead note, 2026-10-02 ~04:40 CEST. Adding a datapoint, not a cause.

Measured twice, a few seconds apart, with identical results both times:

total project keys: 101
temp-dir keys:      83      (prefix /private/tmp, /tmp or /var/folders)
bridged-worktree keys: 1
file size:          85214 bytes

Read-only: I parsed the JSON, counted keys by prefix, and printed nothing but counts. I never read or printed any key's value, and never wrote the file.

What this does and does not establish

Establishes: the accumulation is ongoing, and it is well past where this ticket found it. The filing measured temp-dir keys at 65 → 68, then 68 → 71 with PR #645 applied. It is now 83, in an 85 KB file.

Does not establish: which runs added the 12 keys between 71 and 83. Three members were running Maven concurrently while I measured, plus one single-class run of my own, and I did not bracket any of them. So I can state the level and the direction, and I deliberately name no cause for the delta. The earlier +3 per run figure came from a properly bracketed before/after; this one did not, and the two should not be read as the same kind of number.

My two readings agreeing is weak evidence of stability, not strong — they were seconds apart and no suite completed between them. It rules out the file being rewritten under me at that instant, nothing more.

Why the level is worth recording anyway

The guard this ticket is about, ClaudeCodeLauncherTest.noFixtureSeededTheDefaultClaudeJson, exists to stop exactly this accumulation, and it keeps passing. 65 was already evidence that it cannot see the writes; 83 is the same evidence with a steeper slope. The diagnosis in the original filing — the guard brackets only its own class, the suite is serial in one fork, so another class's writes land outside its window — predicts unbounded growth, and growth is what the file shows.

One consequence worth stating plainly for whoever picks this up: every full-suite run on this host, by the lead or by any member, makes this worse. We ran several today in the course of normal Shape A verification work. That is not an argument for running fewer suites — the suite is the instrument this whole ticket family depends on — but it does mean the number will keep climbing while this is open, and that a future measurement disagreeing with 83 is expected rather than suspicious.

Still unattributed: which fixtures do the writing. Unchanged from the filing, and still the thing a fix needs first.

## Still growing — 83 temp-dir keys now, up from 65 when this was filed Lead note, 2026-10-02 ~04:40 CEST. Adding a datapoint, not a cause. Measured twice, a few seconds apart, with identical results both times: ``` total project keys: 101 temp-dir keys: 83 (prefix /private/tmp, /tmp or /var/folders) bridged-worktree keys: 1 file size: 85214 bytes ``` Read-only: I parsed the JSON, counted keys by prefix, and printed nothing but counts. I never read or printed any key's value, and never wrote the file. ### What this does and does not establish **Establishes:** the accumulation is ongoing, and it is well past where this ticket found it. The filing measured temp-dir keys at `65 → 68`, then `68 → 71` with PR #645 applied. It is now **83**, in an 85 KB file. **Does not establish:** which runs added the 12 keys between 71 and 83. Three members were running Maven concurrently while I measured, plus one single-class run of my own, and **I did not bracket any of them.** So I can state the level and the direction, and I deliberately name no cause for the delta. The earlier `+3 per run` figure came from a properly bracketed before/after; this one did not, and the two should not be read as the same kind of number. My two readings agreeing is weak evidence of stability, not strong — they were seconds apart and no suite completed between them. It rules out the file being rewritten under me at that instant, nothing more. ### Why the level is worth recording anyway The guard this ticket is about, `ClaudeCodeLauncherTest.noFixtureSeededTheDefaultClaudeJson`, exists to stop exactly this accumulation, and it keeps passing. 65 was already evidence that it cannot see the writes; 83 is the same evidence with a steeper slope. The diagnosis in the original filing — the guard brackets only its own class, the suite is serial in one fork, so another class's writes land outside its window — predicts unbounded growth, and growth is what the file shows. One consequence worth stating plainly for whoever picks this up: **every full-suite run on this host, by the lead or by any member, makes this worse.** We ran several today in the course of normal Shape A verification work. That is not an argument for running fewer suites — the suite is the instrument this whole ticket family depends on — but it does mean the number will keep climbing while this is open, and that a future measurement disagreeing with 83 is expected rather than suspicious. Still unattributed: which fixtures do the writing. Unchanged from the filing, and still the thing a fix needs first.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#646