cmux-tui: one-time cleanup of stored terminal output in old registries (takes over #23313)
Takes over #23313 (Lawrence Chen's one-time cleanup of stored terminal output in old registries). It has been open since 2026-10-21, conflicts with feat-cmux-next, and is still needed: #22937 stopped new terminal.output rows but says "Old registries keep their output rows", and nothing on base removes them. This branch carries the original commits with their authorship (a merge of feat-cmux-next-output-cleanup at 43d93e15b80). #23313 stays open for its owner to close.
What the cleanup does (from #23313)
- Phase A, in the background: an owner stages the sealed journal segments without their output rows.
- Phase B, at the next start: the owner builds a registry without the output and renames it into place. The old file is kept as
.pre-output-cleanup, and the start after a full start on the new registry removes it. - Rollback: a swapped registry that does not open, or whose owner fails to build, is rolled back under the same session locks. An owner refuses to open beside a backup it cannot put back. A killed cleanup leaves the registry usable and resumes.
This PR
-
Red first: fb7dcd622fd commits Lawrence's
tests/output_cleanup.rsalone onfeat-cmux-next. -
The cleanup: the merge of #23313 follows. It resolves the 3 conflicting hunks in
WorkspaceRegistry::openagainst base's startup timings:open_innerstarts the timings, and each open try recordsregistry.lock_and_connect. -
Fixes from four rounds of independent Codex review (gpt-6.1-sol):
- A rollback that cannot put
.pre-output-cleanupback no longer serves the cleaned registry; for example, the failed open's WAL syncer is not joined and can still hold the file (a sharing violation on Windows). A synced rollback-pending marker is written before the rename. A failed rename refuses the start, and the next start finishes the rollback before any open (finish_pending_rollback). If the marker cannot be written, the failure marker beside an unconfirmed swap is the fallback record. - A start that cannot record its confirmation (
output_cleanup_full_starts) is refused before it serves, so a later failed start can never roll back a served session. - The reviewer ruled the
open_withlock gap acceptable by design: only an owner that also fails before a full start can lose its writes.
- A rollback that cannot put
Verification
Red/green command: cargo test -p cmux-tui --test output_cleanup stored_output_is_removed_and_the_registry_swapped_at_the_next_start
-
Red at fb7dcd622fd (test only, on base): FAILED,
no log line with "output cleanup: phase A complete". Base never runs the cleanup. Fast checks run 41032592311. -
Green at 7be9433dc90: all 7
output_cleanuptests pass (swap, the two rollbacks, rollback at a later failed start, refusing beside a backup, restore preview across a pruned tail, killed cleanup resumes). Fast checks run 41033549316. -
At 62f3bfa2c6f: clippy
-D warningson cmux-tui-core and cmux-tui is clean; 2 rollback unit tests and 7output_cleanuptests pass (fast checks run 41050262654).
Observed / Not run
cargo test -p cmux-tui-core --lib workspace_registry: 275 passed, 1 failed. The failure,integrity_check_scope::open_integrity_check_skips_mutation_ledgers, is a base red: base b3699a6181c ("a cold open reads live state only") movedresource_mutationsto the background history check but did not update that test. This branch touches neither.scripts/cmux-next/prepush-gate.py: no new violations.- Not run: the
output_cleanup_benchtiming bench.
Changelog
Changed: a registry from an older build has its stored terminal output removed once, at a start, shrinking the file.
Carry, 2026-10-31 (a new owner lane after the first lane closed)
- Base merge at 8c412662b28: three conflicts where base and this branch each added a field or a call side by side (journal maintenance control and agent chat notice stop beside the output cleanup state and stop); both are kept. One compile fix in the merge: base's maintenance tick now returns a
TickOutcome. The pending-cleanup early return givesWorkRemains(ticked again after the interval), notSettled, because a settled worker would sleep until the next journal change and never seal once the cleanup ends. - A fresh full independent review (Codex gpt-6-astra, high) at 8c412662 BLOCKed on three findings, all fixed with a regression test committed first (1ac56d8a958):
- P1: Phase A trusted a segment kind map that listed only
terminal.outputand dropped the segment without inflating it. The legacy kind backfill inserts one kind row at a time, so a backfill killed partway leaves a mixed segment listed as output only, and its other records were lost. Phase A now inflates every sealed segment. - P1: a rollback ignored a
-wal/-shm/-journalit could not remove and still put the previous registry back beneath the cleaned file's WAL frames (on Windows, a reader holding the WAL without delete sharing). A sidecar that cannot go now refuses the start like a failed rename; the rollback marker stays, and the next start finishes it. - P2: the live-row count (one statement over the whole live journal) could not be stopped, so it held the owner's shutdown. Staging reads now check the owner's stop through SQLite's progress handler; whatever fails once the owner is stopping is a stop, and the next start resumes it.
- P1: Phase A trusted a segment kind map that listed only
- Panic ratchet: this branch's own lines had two
indexing_slicinghits (codec.rssplit_records, stage.rskept_segment_records); both now useget()and fail the segment with a named error (ce8ae31fdcc). - One commit on the base tip (56141824cdf): the branch carried #23313's history from its 2026-10-20 fork point, so a pull request run's shallow fetch (
--deepen=2000) reached about 2000 old base commits on that side, and the sidebar owner sweep incmux-next checksjudged them as this PR's own ("600 commit(s) change the sidebar's drawing code without a Sidebar-Look: trailer"). With full history the same sweep finds 0 over342462ca6ee..ce8ae31fdcc. The branch is now one commit onfeat-cmux-nextwhose tree is exactly the merge of ce8ae31fdcc into db183fa3bf5 (git merge-tree --write-tree), so the change is unchanged; Lawrence is credited with aCo-authored-by:trailer.
Verification (carry)
Command: cargo test -p cmux-tui-core --lib output_cleanup
- Red: with the two P1 fixes undone at the head,
a_mixed_segment_listed_as_output_only_keeps_its_other_recordsfails (left: [],right: [(2, "terminal.title")]) anda_rollback_whose_wal_cannot_be_removed_keeps_the_backup_asidefails (fast checks run 41160616158). - Green at ce8ae31fdcc: 5 passed.
cargo test -p cmux-tui --test output_cleanup: 7 passed.cargo clippy -p cmux-tui-core -p cmux-tui --all-targets -- -D warnings: clean (fast checks run 41161718926). - Green at 56141824cdf (the one-commit head): the same 5 and 7 pass and clippy is clean (fast checks run 41164784355). The local
prepush-gate.pypasses, and its sidebar sweep finds 0. a_stop_cuts_the_live_row_count_shortis a new test of the new stop path; it has no red run because the API it calls is part of the fix.- Independent review: full review BLOCK at 8c412662 (the three findings above), then SHIP at 1bb06359 (all three fixed, no new findings) SHIP at ce8ae31f (delta, the two
get()changes), and SHIP at 56141824 (the tree is exactly the merge of ce8ae31f into db183fa3, per-file changes identical, and the two base commits in these files do not interact).
Observed / Not run (carry)
- Panic ratchet (
rust_panic_ratchet.py --flags,-p cmux-tui-core --lib --bins) at ce8ae31f: no deny-lint hit on a line this branch adds. cmux-tui-core still readsindexing_slicing854 > baseline 853 andstring_slice57 > baseline 52; those hits are in files this branch does not change (e.g.agent_hooks.rs,apps/*,browser_*), so they are base. - Not run: the Windows sharing-violation path on Windows itself (the test stands in with a directory where the WAL is); the
output_cleanup_benchtiming bench.
Risk: high (an on-disk migration that removes stored data, with rollback).
Agent session: agent session
š¤ Generated with Claude Code