cmux-tui: one-time cleanup of stored terminal output in old registries (takes over #23313)

Merged
TE
3d ago
+4585-97

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

  1. Red first: fb7dcd622fd commits Lawrence's tests/output_cleanup.rs alone on feat-cmux-next.

  2. The cleanup: the merge of #23313 follows. It resolves the 3 conflicting hunks in WorkspaceRegistry::open against base's startup timings: open_inner starts the timings, and each open try records registry.lock_and_connect.

  3. Fixes from four rounds of independent Codex review (gpt-6.1-sol):

    • A rollback that cannot put .pre-output-cleanup back 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_with lock gap acceptable by design: only an owner that also fails before a full start can lose its writes.

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_cleanup tests 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 warnings on cmux-tui-core and cmux-tui is clean; 2 rollback unit tests and 7 output_cleanup tests 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") moved resource_mutations to 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_bench timing 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)

  1. 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 gives WorkRemains (ticked again after the interval), not Settled, because a settled worker would sleep until the next journal change and never seal once the cleanup ends.
  2. 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.output and 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/-journal it 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.
  3. Panic ratchet: this branch's own lines had two indexing_slicing hits (codec.rs split_records, stage.rs kept_segment_records); both now use get() and fail the segment with a named error (ce8ae31fdcc).
  4. 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 in cmux-next checks judged 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 over 342462ca6ee..ce8ae31fdcc. The branch is now one commit on feat-cmux-next whose tree is exactly the merge of ce8ae31fdcc into db183fa3bf5 (git merge-tree --write-tree), so the change is unchanged; Lawrence is credited with a Co-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_records fails (left: [], right: [(2, "terminal.title")]) and a_rollback_whose_wal_cannot_be_removed_keeps_the_backup_aside fails (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.py passes, and its sidebar sweep finds 0.
  • a_stop_cuts_the_live_row_count_short is 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 reads indexing_slicing 854 > baseline 853 and string_slice 57 > 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_bench timing bench.

Risk: high (an on-disk migration that removes stored data, with rollback).

Agent session: agent session

šŸ¤– Generated with Claude Code