Skip to content

Make echo_chamber reachable (#150), plus two bugs found while verifying#156

Open
justinstimatze wants to merge 5 commits into
fiorastudio:masterfrom
justinstimatze:fix/detector-subsumption
Open

Make echo_chamber reachable (#150), plus two bugs found while verifying#156
justinstimatze wants to merge 5 commits into
fiorastudio:masterfrom
justinstimatze:fix/detector-subsumption

Conversation

@justinstimatze

Copy link
Copy Markdown
Contributor

Fixes #150.

Stacked on #151 — its commit is the base here, so this PR currently shows it too. Once #151 merges that drops out and only the four commits below remain.

The #150 fix

echo_chamber's predicate is load_bearing_vibes' predicate plus three conjuncts (user speaker, assistant-only supports, no pushback), so every echo case is also a load-bearing case. That isn't a tie to break, it's containment. Selection takes candidates[0], so the general finding won on detector order, and the per-anchor cooldown is type-agnostic, so emitting it also blocked the specific one on that anchor. echo_chamber couldn't surface at all.

SUBSUMES in types.ts names the containment and runAllDetectors drops a general finding when a more specific one fired on the same anchor. #129's thresholds are untouched — they were tuned against real session density and that work stands.

#151 pinned the echo_chamber assertion and flagged the open question. This answers it, so that assertion goes back to expecting echo_chamber. Three fixtures also turned out to be mislabelled: they seeded a user vibes claim backed by assistants with no pushback, which is a textbook echo chamber, and only read as load-bearing because of the ordering bug.

Two bugs found while verifying

Neither is related to #150 — I hit both trying to get a green suite to check the fix against.

Every rescued companion rolled identical bones when Bun is installed. bun -e '<script>' <arg> doesn't forward trailing arguments, so process.argv[1] was a constant and the userId never reached the hash. 300 userIds produced one stat vector. Measured: with Bun on PATH master fails, without Bun it passes, which is why CI would never have caught it.

resolveSessionTrace read every transcript in full to get its first line. On a machine with real history that's 8.5 GB across 646 files, and because the scan is synchronous it blocks the vitest worker so the timeout can't fire — the suite looked like it hung with no failing test to point at. Reads are now bounded to a 32 KiB head.

CI

There isn't any today, which is how #129 shipped a red suite in May and it stayed red until @InfinitePortaldev cloned the repo in July. Merge #152 and #153 first — they fix the only four remaining failures, and this red-gates every PR the moment it lands.

Verification

Ran the suite against each open PR and the combination:

failed
master 12
master + #151 7
master + #152 10
master + #153 10
master + #154 12
all four + this branch 0 — 915 passed

Each PR fixes exactly its own failures and nothing else. The four commits here touch disjoint files and are independent — happy to split them into separate PRs if that's easier to review, I kept them together to avoid adding six PRs to the queue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WFi4A9eG3CWPjDDMvdmUfy

InfinitePortaldev and others added 5 commits July 20, 2026 22:02
The tuning in fiorastudio#129 left the older detector spec tests behind — they
still assume the stricter pre-tuning thresholds, so npm test fails on a
fresh clone. This updates the fixtures and expectations to the current
values. Tests only, no behavior changes.
…ion (fiorastudio#150)

Some detectors are specializations of others. `echo_chamber`'s predicate is
`load_bearing_vibes`' predicate plus three conjuncts (user speaker,
assistant-only supports, no pushback), so every echo case is also a
load-bearing case — one situation at two resolutions, not two competing
findings.

Selection takes candidates[0] within a category, so the general finding won
purely on the order detectors run in. The per-anchor cooldown made it
self-reinforcing rather than self-correcting: it keys on anchor_claim_id
without regard to type, so emitting the general finding also blocked the
specific one on that anchor. `echo_chamber` could not surface at all.

`SUBSUMES` names the containment; `runAllDetectors` drops a general finding
when a more specific one fired on the same anchor. Thresholds are untouched —

Also fixes two defects fiorastudio#129 exposed in the same area:

- `unchallenged_chain` fired on the unchallenged *suffix* of a challenged
  chain. The walk started from every node, so once the minimum length dropped
  4→3 a suffix below the challenged pair became a candidate in its own right.
  It now starts only from maximal chains, matching its documented "anchors on
  the head" intent.
- The "below threshold" / "below minimum length" fixtures hardcoded edge
  counts that fiorastudio#129's retuning turned into *at*-threshold cases, so they went
  green while testing nothing. They derive from REASONING_CONFIG now.

Three fixtures were mislabeled: they seeded a user vibes claim backed by
assistants with no pushback — a textbook echo chamber — and read as
load-bearing only because of the ordering bug. They now use an
assistant-authored anchor so they test what they claim to.

Builds on fiorastudio#151, which fixes the same five failures by pinning the current
behaviour and explicitly defers the real fix here. The two fixture sets
conflict and should not both be merged as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFi4A9eG3CWPjDDMvdmUfy
…ntical bones

The CC-compat rescue path reproduces Claude Code's `Bun.hash()` by shelling
out to `bun -e`, passing the userId as a trailing argument and reading it back
as `process.argv[1]`. Bun does not forward trailing arguments to `-e` scripts:
inside the eval, `process.argv` is `['bun', '<cwd>/[eval]']` and nothing more.
`process.argv[1]` was therefore a constant path string, identical on every
call, and the userId never reached the hash.

Measured on master with Bun installed: 300 distinct userIds produced exactly
one distinct stat vector. Rarity, species, eye and stats were the same for
every rescued companion. Silent because the roll stayed perfectly
deterministic — which is what the determinism test checks — and because the
FNV-1a fallback path, used on machines without Bun, was always correct.

The input now travels through the environment instead of argv, preserving the
no-string-interpolation property that argv was chosen for. After: 200/200
distinct, still deterministic per userId.

Already-rescued companions keep their stored bones, so no migration is needed,
but every existing rescue was a clone of the same roll.

The existing test compared two userIds, which reads as an unlucky fixture
precisely when the bug is total. Replaced with a 40-id spread assertion.

Not covered by any open PR — fiorastudio#152 and fiorastudio#153 fix the other fresh-clone failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFi4A9eG3CWPjDDMvdmUfy
…test suite

resolveSessionTrace walks ~/.claude/projects, ~/.claude/sessions and
~/.codex/sessions to map a session id back to its cwd. It needs the first
JSONL record (Claude) or the first dozen (Codex), but read every candidate
file in full to get them, so cost scaled with total transcript volume instead
of with the header actually parsed.

On a machine with real history — 646 files, 8.5 GB — that meant reading 8.5 GB
to extract a few hundred kilobytes of headers. reasoning/session-trace.test.ts
never finished, and because vitest runs files serially it took the rest of the
suite down with it; `npm test` appeared to hang with no failing test to point
at. Fresh clones and CI runners have no transcript history, which is why this
went unnoticed.

Reads are now bounded to a 32 KiB head per file, enough for a first JSONL
record by a wide margin. Whole-file .json parsing keeps its own 256 KiB
ceiling since it is parsed as a single object. A truncated read discards its
trailing partial line rather than trying to parse half a record.

Resolution is unchanged — the real workspace still resolves to the same cwd
and source, in 4ms. The test goes from a >90s hang to 2s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFi4A9eG3CWPjDDMvdmUfy
The repo has no CI — .github/workflows contained only the stats updater — so
nothing has been running the test suite on PRs or merges. fiorastudio#129 shipped a red
suite in May and it stayed red until a contributor cloned the repo in July and
ran npm test by hand (fiorastudio#150, fiorastudio#151, fiorastudio#152, fiorastudio#153).

Node 20 (the engines floor) and 24, npm ci / build / test. `npm ci` compiles
better-sqlite3 from source when no prebuild matches the runner ABI, which is
the same path users hit on install, so a broken native build fails here too.

MERGE ORDER: this gates every PR the moment it lands, and the suite is not
green yet. fiorastudio#152 fixes the two doctor check-count failures and fiorastudio#153 fixes the
two Penguin frame-width/pose failures; those are the only four remaining, and
both PRs are open. Merge them first, or this red-gates unrelated work.

Note that CI could not have caught the bug fixed in the preceding commit: a
fresh runner has no ~/.claude transcript history, so resolveSessionTrace's
unbounded scan costs nothing there. Green CI is not evidence the suite is
sound on a real machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFi4A9eG3CWPjDDMvdmUfy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

echo_chamber can't surface anymore after the #129 threshold tuning

2 participants