Repository navigation
tui: live message window can re-root mid-session - earlier turns unreachable, no seam summary, no lifecycle event #7142
Description
Landed setting fix — 2026-10-29
Engine main/origin commit 7b54e7f fixes the concrete automatic-off bypass. Preflight/provider overflow recovery now refuses before summary/start/replacement when compaction is disabled, retains original messages and records an actionable typed refusal through the existing audit/error owners. Both overflow paths preserve the recovery failure's actual cause; cancellation takes priority after the awaited recovery. Explicit manual compaction remains available.
Local proof: initial libtest compile and137focused compaction/error/Engine checks passed. After the final model-step cancellation guard, libtest recompiled and69affected Engine/recovery/cancellation checks passed; unchanged pure-check evidence is retained. The renamed existing emergency fixture covers disabled-but-compactable and enabled-but-unnecessary history, zero model calls/attempts, unchanged messages and one typed refusal. Format, boundary, blocking budget569sites169files, dead-code164, changelog/facts and diff checks pass. No new test functions/comments. Exact receipts: artifacts/codex-0103-resume-20261029/compaction-off-landing-verification.json. Source files are released, not held for another build.
Keep this issue and public Engine7142 open. The setting bypass is established and repaired, but it does not explain every reported Windows seam or complete the remaining lifecycle/continuity acceptance. A read-only local Mac journal inspection did not reproduce the reported multi-root shape; that does not prove no context loss or disprove the reporter's sample. No real-provider, hosted CI, Windows/installed/device or release proof is claimed.
Summary
A session's live messages window can be re-rooted partway through a conversation. When
that happens, every journal entry before the new root becomes unreachable to the model
while the on-disk journal keeps it in full. The window stays internally consistent — it
equals its own new branch exactly — so nothing looks truncated from the inside.
Three things are missing at the moment it happens:
- no summary entry at the seam. The new root is a bare
kind: "message"with
parent_id: null; the entry immediately before it is an ordinary turn on the branch
that was just abandoned. Real compaction leaves a condensed account of what it replaced.
This leaves a copy and a hole. - no marker in the interface. No banner, no notice, no error.
- no session-lifecycle event in
audit.log. In 8,792 events across five event types,
every one istool.approval.*ortool.gate.decision. Nothing announces that the model's
working context was replaced.
settings.toml has auto_compact = false and auto_compact_threshold_percent = 80.0
throughout. If this is compaction, the setting is not describing what the runtime does.
Environment
codewhale --version: 0.10.2 (ff466b2) — verified against the binary- OS: Windows 11 (10.0.26200), PowerShell 5.1
- Model:
deepseek-v4-pro(providerdeepseek) - Install: npm global;
~/.codewhale/{config,settings,permissions}.toml, no[compaction]
block and noretained_user_message_tokensinconfig.toml - Reproduces on 0.10.1 and 0.10.2
What it looks like
Dump of four consecutive documents from this host. entries = journal.entries;
window = len(messages); root = the journal index the live branch starts at.
created (UTC) window entries root coverage
2026-10-28T13:08:15 944 986 1 99.9% <- intact (benign doubled opening)
2026-10-28T15:24:44 632 1639 987 39.8% <- re-rooted
2026-10-29T07:11:22 403 473 50 89.4% <- re-rooted
2026-10-29T08:20:46 61 325 259 20.3% <- re-rooted (this one, mid-report)
The seam itself, from 2026-10-29T07:11:22:
[48] kind=message role=assistant parent_id=Y 3970 chars
[49] kind=message role=user parent_id=Y 1362 chars
[50] kind=message role=user parent_id=NONE 2054 chars <- window begins here
[51] kind=message role=assistant parent_id=Y 360 chars
Prose for scale: the journal for that session holds 473 entries; the model could see 403
of them. The boot turn, including the whole startup sequence, was gone from the window and
present on disk.
Prevalence on this host, and an onset date
87 session documents were parsed structurally. journal + leaf_id on this host arrives
between 11 and 12 Aug 2026 (before that, documents carry artifacts and no journal, so this
signature cannot exist and cannot be tested — that period is untestable, not clean).
| period | sessions | shape |
|---|---|---|
| 12 Aug – 17 Oct 2026 | 47 | one branch root, window == journal, 100% reachable |
| 18 Oct – 29 Oct 2026 | 40 | two or three roots; 15 re-rooted mid-session |
The onset boundary is narrow: …d6832ea3 (17 Oct 18:33:49Z) is clean at 113/113;
…021a7d6a (18 Oct 01:46:51Z) is the first re-root at 962 window / 1045 entries.
Across the 15 mid-session re-roots, the root lands at journal indices 8, 20, 30, 50, 67, 97,
160, 259, 295, 347, 367, 472, 733, 987. Post-re-root coverage ranges from 14.1% to
89.4%; combined unreachable entries 3,909.
In 0 of 87 sessions is the window shorter than its own branch. This is not truncation —
the window is always a complete branch. The branch is simply no longer the session.
Reproduction
No special configuration; it appears on ordinary long sessions. A read-only probe against
any session document:
import json, hashlib
doc = json.load(open(PATH, encoding="utf-8"))
msgs, entries = doc["messages"], doc["journal"]["entries"]
by_id = {e["id"]: i for i, e in enumerate(entries) if e.get("id")}
chain, seen, cur = [], set(), len(entries) - 1
while cur is not None and cur not in seen:
seen.add(cur); chain.append(cur)
cur = by_id.get(entries[cur].get("parent_id"))
root = min(chain)
print("window", len(msgs), "branch", len(chain),
"root", root, "coverage %.1f%%" % (100 * (len(entries) - root) / len(entries)))window == branchandroot <= 1→ healthy (a root at 1 is the benign doubled opening;
do not flag it — see What I got wrong, below).root > 1→ the window re-rooted mid-session.rootentries are unreachable.
To inspect a seam, print entries[root-2 : root+2] and check for anything summary-shaped.
On this host there is never one.
How this differs from the nearby open reports
- #6842 (journal has no bound; compaction retires live messages but keeps superseded
versions in RAM) names the same underlying machinery and is about memory growth. This
report is about model visibility: the earlier turns were not merely retained
redundantly, they became unreachable while the record survived. Different harm, possibly
the same call site. - Automatic Compression Context Exception #7133 (automatic compression at ~40% against an 80% setting) is the closest
behavioural neighbour and may share a cause — it reports the threshold being wrong,
where this reports a silent re-root with no summary. Worth triaging together. - Conversation history is lost. #7095 (history lost, labelled v0.10.3) is a different fault: there the record does not
survive because the last save did not flush. Here every byte survives and is correct, and
only the window is replaced.
What I am asking for
In order of value, and each is independently useful:
- Emit a session-lifecycle event.
session.opened,session.window.rerooted,
session.compacted,session.resumed— with both sizes and a reason, intoaudit.log
beside the existingtool.*events. This is the smallest change and it converts an
unexplained amnesia into a dated, attributable event. It is worth having even if the
re-root turns out to be intended, so please do not let the other two asks gate it. - Honour
auto_compact = false, or rename it. A setting that says compaction is off
while the window is replaced is worse than no setting: it is a claim the operator can
rely on and cannot verify. - If a window re-root is intended, summarise at the seam. A bounded, visible,
summarised compaction is a feature. An unannounced re-root that leaves a bare copy of an
existing message is a data-visibility defect.
What I have not established, and will not guess at
The trigger. Observed re-roots span 78 to 1639 journal entries, 11 to 1437 window
messages, and roughly 1.3 MB to 5 MB of document. That spread is inconsistent with a single
simple threshold, and two documents is not a sample. It could be turn count, cumulative
tokens, document size, wall clock, or a path that only runs on resume.
Whether it is intended. #5262 (closed, shipped v0.9.5) specifies an append-only journal
forest where branching "only moves the leaf — it never rewrites history", so multiple roots
are expected by that design and a re-root is legal in that model. What is not
design-legible is doing it silently, mid-session, with no summary, while
auto_compact = false.
What I got wrong on the way here
Recorded because it is the reason this report is careful, and because a maintainer deserves
to know which tests have already failed.
My first pass used a positional test — does messages[0] start where journal.entries[0]
starts — and produced two confident wrong answers in one morning: first "41 of 93 sessions
lost turns", then, over-correcting, "nothing was lost at all". The positional test fires on
the benign doubled opening, which is why I checked a session minutes old and never restarted
before trusting any number here. The figures above come from a membership test: walk the
branch by parent_id and ask whether the window can reach every entry its own session wrote.
If this report is wrong, it is wrong in the direction of under-claiming: 15 measured re-roots
on one host, with the mechanism named and the unknowns listed.
Reproduction and evidence kept locally. Happy to provide sanitised session documents or run
any probe upstream asks for.
Activity
fix(tui): keep compact approvals and launch navigation safe [skip ci]
fix(compaction): honor automatic-off during overflow recovery [skip ci]
Hmbown commentedon Oct 29, 2026
Thank you for this!!
vanderberghenry-oss commentedon Oct 29, 2026
You're very welcome! Codewhale is my lead engineer since May and ships like a king. Never had any issues till this happened in the last couple of weeks. My memory system is comprehensive - so it wasn't a train crash. I love working with codewhale. My compliments
Have good one!
fix(tui): name why a saved window re-rooted, and report an empty acti…
feat(session): record what a resume repaired, in the audit log