The logs said it was working. Every pass, a line like this:
historian no-op: protectedTailStart=1588 eligibleEnd=6 <= offset=6
A magic-context user (the auto-compaction/historian layer of a coding agent) had been running for four hours. The trigger fired on every turn. It spawned its subagent. And at the end of the session it had produced exactly one compartment worth 9 tokens — an empty one.
Nothing screamed. The context was being tagged and pruned (85% → ~50% tags, the user's configured threshold), so something was running. But the thing that actually builds the reusable compartments — the historian — was no-oping every single pass. Four hours, ~94 chunks of eligible history, one empty compartment.
This is the failure mode that doesn't look like a failure: a guard that is alive, firing, and structurally unable to act. It took an independent code trace to find it, and the trace landed on the same lines as the maintainer's own root-cause — a regression introduced in 0.41.0, fixed the same day in v0.41.4.
The two views disagreed
The interesting part of that no-op line is that it prints numbers, and the numbers contradict each other.
protectedTailStart = 1588 (eligible history starts here — ~2.98M tokens)
eligibleEnd = 6 (the resolver thinks 6 is the end of the drainable range)
offset = 6 (the floor: last compartment end + 1)
Two views of the same session disagreed. The trigger and the wrapup planner measured eligible history from the last compartment end to the protected tail: ~2.98M tokens, ~94 chunks — and fired every pass. The boundary resolver — the code that actually picks the chunk to compact — concluded the eligible end was 6, which is at the floor itself. Eligible end equals offset means: nothing is drainable. The range collapsed to empty.
When a resolver returns a boundary exactly at its floor, the question to ask is not "is there work?" — the trigger already answered that. The question is "what made the floor the ceiling?"
The 9-token compartment was the same collapse, one step earlier
The one compartment the session did build (9 tokens) was the tell. /ctx-wrapup (the manual drain command) showed the same shape: chunk 1 resolved to messages 6–7 (~173 tokens) — a sliver — and once that drained, the next pass found nothing, leaving ~2.98M tokens still "eligible" but unreachable.
So the empty compartment wasn't a one-off. It was the visible tip of the same arithmetic running every pass: the resolver's eligible range was a handful of tokens, not the 2.98M the trigger believed in.
What the trace found
Reading the boundary code on the released version (v0.41.2 — the version the user's doctor output showed actually loaded, in an extension cache, one minor behind the 0.41.3 CLI):
The historian applies a per-run head cap by cutting tokens forward from the offset (applyHeadCap, protected-tail-boundary.ts:341). But when that cut lands inside a completed tool-arc exchange — the invocation message before the cut, its result message after it — a whole-exchange fence pulls the eligible end back to the exchange's first invocation (read-session-true-raw-tokens.ts:500, returning at :533). And when the exchange starts right at the front of the head (message 6 in the user's numbering), the fence returns the publication floor: lastCompartmentEndOrdinal + 1 = the offset.
Cut lands inside exchange → fence pulls end back to the exchange start → exchange starts at the offset → end <= offset → eligibleEnd = offset. Empty head. Every pass.
The user's geometry made it certain: at their ~60% threshold, the per-run head cap was ~31k tokens (~0.25 × usable), and in a long agentic turn a single tool batch over 31k tokens is common. Once that shape sat at the head of eligible history, every pass — trigger or wrapup — resolved to nothing. The "9-token compartment" was the sliver before the exchange's first invocation.
Checking the other released tags settled it: v0.41.2 and v0.41.3 had no escape — the file was unchanged between them. The default branch, however, had the fix already written, and the v0.41.4 release notes described the user's symptom verbatim.
"It used to make a lot of compartments before 0.41.0" — the key date
The user's most valuable sentence was buried in the report: "It used to make a lot of compartments before version 0.41.0."
The maintainer's confirmation landed within hours of the trace:
the "before 0.41.0 it made a lot of compartments" observation was the key date.
Commit f32d6986 (shipped in 0.41.0, a TS↔Rust parity alignment) changed the boundary behavior: where the pre-0.41.0 code admitted the first oversized exchange whole, the new code fenced backward to the starting offset instead. When the first exchange after the boundary is bigger than the per-run cap, backward fencing is an empty head. One small alignment commit flipped a working guard into a permanently empty one — and the guard kept logging "working" because the no-op was its designed output for an empty range.
The fix (v0.41.4, published the same day) restores admit-whole at any pressure, and the maintainer's pass found two further defects on the same path: the chunk reader could stop its own budget inside a completed exchange, and the pre-flight could truncate the producer's source text while keeping the whole-range metadata — so even an admitted exchange would have arrived clipped. A 190k-token exchange now reaches the historian whole, with regression tests.
What makes this worth remembering
- A firing guard is not a working guard. A no-op line every pass is a heartbeat, not a result. If the output of a guard that "runs constantly" is an empty compartment, the guard is failing in the most reliable way possible — and its logs will look healthier than a guard that simply never triggers, because they show activity.
- "Used to work before X" is a regression date, not a vague complaint. The single most useful diagnostic sentence in the report was the version boundary. Regression hunting should start by diffing the release tags around that boundary — the diff is usually small (here it was one commit).
-
When a resolver returns its own floor, read the floor.
eligibleEnd=6 <= offset=6is not a measurement of "nothing to do". It is the resolver concluding the only candidate starts exactly where it is forbidden to start. That shape — a boundary pinned to a floor — is the signature of a fence/cut interaction, not of empty history. - Compare the released tags, not just the head. The user was one minor behind and the fix already existed on the default branch. Tag-to-tag comparison (0.41.2 vs 0.41.3 vs default) turned "is this a bug in my config?" into "this is fixed in the next release" in one pass.
-
A fix that adds diagnostics fixes the next ten reports too. The v0.41.4 no-op line now names which arm pinned
eligibleEnd(cap, open-arc clamp, completed-arc fence, or live-prompt floor) with its numbers. The failure that took a code trace to find is now self-diagnosing from one log line. Silent collapse → named failure mode is the upgrade that matters as much as the fix itself.
The thread closed with the maintainer crediting the independent trace: "thanks @pm25coder for the independent trace; it landed on the same lines." That is the best possible outcome for a bug report — but the reason it worked is upstream of the credit: the user wrote down the version where behavior changed, the logs printed the resolver's numbers, and the two views disagreeing told us exactly where to read.
Top comments (0)