TUI resume main view shows partial transcript when `terminal_resize_reflow_max_rows` is capped
What version of Codex CLI is running?
0.129.0
What subscription do you have?
Pro
Which model were you using?
gpt-5.5
What platform is your computer?
Darwin 23.6.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
zsh/oh-my-zsh
What issue are you seeing?
When resuming a long TUI session, the main resumed view can show only a partial transcript, making it look like most of the session history was lost.
In my case, thread/resume returns the full session history and Ctrl+T opens the full transcript, so the data is loaded correctly; however, the normal main TUI view after codex resume only shows the first prompt/partial history.
The issue appears tied to tui.terminal_resize_reflow_max_rows. Setting tui.terminal_resize_reflow_max_rows=0 makes the full transcript appear in the main resumed view.
What steps can reproduce the bug?
- Have a long Codex session with more transcript rows than the resize-reflow row cap.
- Resume it with default TUI settings.
- Main TUI view shows only an early/partial portion of the transcript, making history appear missing.
- Press Ctrl+T and the full transcript is present.
- Resume with
-c tui.terminal_resize_reflow_max_rows=0and the full transcript appears in the main view.
What is the expected behavior?
After codex resume, the main TUI view should not make the transcript appear lost or truncated when the full transcript is available.
If row-capping is applied during initial replay, the UI should either show the most useful recent tail of the transcript, clearly indicate that older transcript rows were omitted from the main terminal scrollback buffer, or otherwise keep the main view consistent with the full transcript available through Ctrl+T.
Additional information
thread/resumereturns all turns for the session, so persistence/resume loading is correct.- The issue appears isolated to initial history replay / resize-reflow row cap in the main TUI scrollback.
6 Comments
Related: #21945 traces the root cause of why the reflow cap exists in the first place — the transcript overlay and reflow path have O(n) rendering that scales linearly with session length (full markdown re-parse per cell, no height caching, no virtualization). The partial transcript you're seeing when the cap is active is the band-aid; the underlying issue is that uncapped reflow walks and re-renders every cell from scratch.
The main resumed view should expose the render cap as a display state.
If
thread/resumeloaded the full transcript butterminal_resize_reflow_max_rowslimits visible rows, show a small status likefull transcript loaded, main view capped at N rowswith a jump/open-full-transcript action. That avoids confusing a performance guard with missing history.---
_Generated with ax._
I reproduced the same visible-transcript/model-context split on Linux with codex-cli 0.147.0.
Environment:
Trigger:
Observed:
This strongly suggests the rollout/model context is intact while the initial human-facing TUI replay is selecting or rendering the wrong/older slice. The useful invariant here is that the newest model-visible turn and newest TUI-visible turn diverge. Expected: even when replay is row-capped, the main view should include the newest completed tail, never only an older slice.
This is a useful healthy-persistence control for
codex-rescue:thread/resumehas the full history and the model retains the omitted turns, while only the main TUI replay looks truncated. Rescue should therefore avoid diagnosing session corruption from the human-facing display symptom.If you still have an affected long session, could you run:
A healthy/no-damage result is the expected useful outcome. I’m also interested in whether
sessionsidentifies the correct latest durable thread despite the TUI showing an older slice. No salvage is needed for this control case.Please share sanitized output, versions and exit codes only—no raw rollouts/SQLite, prompts, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
I reproduced a stronger variant of this on Linux with a long paginated thread: the capped main view did not merely omit older history; it settled inside an older oversized tool output and hid the actual newest semantic tail.
Environment
rust-v0.149.0tag with one scoped local change in the rollout ordinal decoder; the TUI/history hydration code is unchanged from the tagpaginatedObserved behavior
After
codex resume <thread-id>, the TUI reachedReady, but the visible main transcript consisted of an interior fragment of an older package-manager command output. That persisted command output is about 1.16 MB and belongs to an interrupted turn several turns before the true tail.The latest user/assistant turns were not visible in the main view. This made the resumed thread look as if it had reopened at an old operation, even though the backend had loaded the current history.
Healthy-persistence controls
Read-only checks showed no rollout or projection loss:
PRAGMA quick_checkreturnedok;thread/read(includeTurns: true)reconstructed 99 turns in chronological order;thread/resumealso returned all 99 compatibility turns;initialTurnsPagelikewise returned the newest turns first;So this is a human-facing initial replay/materialization error, not stale durable history and not model-context loss.
Boundary evidence
The 0.149 TUI uses bounded paginated hydration and a terminal-dependent initial replay row budget (1,000 rows in this environment). The first descending item page contained the current tail. The next page reached the older 1.16 MB
commandExecutionoutput; that single cell was sufficient to exhaust the row budget. The resulting main-view replay landed inside that tool output rather than preserving the newer semantic tail below it.This appears to be the same family as #40345, but with a large tool-output cell rather than one long user-message cell. It is also distinct from durable projection failures: both app-server resume/read paths returned the correct latest turns here.
Expected behavior
When initial replay is capped:
No thread IDs, transcript text, local paths, account data, or raw rollout contents are included here.
Fresh recurrence after a machine reboot, with a tighter control for wrong-thread selection:
thread/resumefor the intended newest thread ID. App-server logs tie that exact ID to the resume request.next_rollout_byte_offset = 16,786,181,next_rollout_ordinal = 20,161), with 966 projected items and 21 turns; both state and history databases passedPRAGMA quick_check.This rules out both a stale projection and accidentally resuming a parent/older thread in this recurrence. It is specifically: correct thread ID + correct durable EOF + incorrect initial visible transcript after resume/reboot.
The local build still differs from the 0.149.0 tag only in a scoped rollout-ordinal decoder fix; TUI pagination/reflow code is unchanged.