[CLI / TUI] 0.145.0 shows a blank terminal with no progress when resuming long threads
What version of Codex CLI is running?
codex-cli 0.145.0
What subscription do you have?
ChatGPT Pro
Which model were you using?
gpt-5.6-sol (max reasoning)
What platform is your computer?
Microsoft Windows NT 10.0.19044.0 x64
What terminal emulator and version are you using (if applicable)?
Windows Terminal 1.24.11911.0, PowerShell 7.6.3, no terminal multiplexer
Codex doctor report
{
"note": "Full report omitted because it contains private local paths and thread metadata. A further-sanitized report can be supplied if required.",
"cli_version": "0.145.0"
}
What issue are you seeing?
After upgrading to codex-cli 0.145.0, codex resume opens the session picker normally. After I select a long existing thread, the picker disappears and the terminal is cleared. It then remains completely blank and visually unchanged for several minutes before the transcript and prompt finally appear.
There is no spinner, loading text, elapsed time, or other indication that Codex is still working. During a measured reproduction, Windows continued to report codex.exe as responsive and the process continued consuming CPU. The delay was therefore active history processing rather than a process crash, but from the UI it was indistinguishable from a hang.
This specifically concerns the period after selecting a thread, not the time taken to open the resume picker.
Before 0.145.0, resuming a long thread visibly replayed or repainted historical text. That was noisy, but it at least indicated that Codex was working. I am not asking for the rapid replay to return. I am asking for a clear loading or progress state while the selected thread is being resumed.
What steps can reproduce the bug?
- Use a long-running Codex CLI thread containing many previous turns and multiple context compactions.
- Exit the TUI normally.
- Run
codex resume. - Wait for the session picker, which appears normally.
- Select the long thread.
- Observe that the picker disappears and the terminal becomes completely blank.
- Wait several minutes with no visible activity.
- Eventually, the previous transcript and normal prompt appear.
The private thread ID is intentionally omitted. Sanitized aggregate measurements are provided below.
What is the expected behavior?
Immediately after a thread is selected, the TUI should render and flush a visible loading state such as Resuming session....
Where the amount of work can be calculated, it should show determinate progress, for example:
Processing history: 4,250 of 26,203 recordsLoading conversation: 42%- A progress bar based on records or bytes processed
If an accurate total cannot be calculated, an animated spinner or three-dot loader with elapsed time would still be sufficient to show that Codex is alive and working.
Where possible, Ctrl+C should remain responsive. The terminal should never remain completely blank for several minutes, and the previous rapid history replay does not need to be restored.
Additional information
The delay subjectively appears to be around 2-3 minutes in normal use, although one instrumented reproduction was longer:
- The terminal was demonstrably unchanged and blank for at least 3 minutes 28.7 seconds.
- Allowing for the intervals between screenshots, the complete post-selection wait was approximately 3 minutes 29 seconds to 3 minutes 59 seconds.
- Diagnostics showed transcript-consolidation activity finishing only a few seconds before the TUI became visible.
Sanitized characteristics of the selected private thread:
- Legacy CLI thread.
- Rollout size: approximately 765 MiB.
- JSONL records: 26,203.
- Compaction records: 62, totalling approximately 426 MiB.
- The rollout remained readable and the resume eventually completed successfully.
Process observations during the blank period:
- Windows reported
codex.exeas responsive. - The process accumulated approximately 234 CPU-seconds before the resumed UI appeared.
- Working memory was approximately 396-445 MiB.
- CPU usage stopped increasing rapidly once the TUI appeared.
- The final diagnostic activity included
codex_tui::app::agent_message_consolidation, immediately before the resumed interface appeared.
Related but not exact duplicates:
- #34663 concerns rendering the full history instead of bootstrapping a bounded latest-turn view. This report concerns the complete absence of progress feedback while that processing occurs.
- #29058 concerns incomplete restoration after Ctrl+C, including missing previous output and a lost command-approval question. It does not concern a responsive process spending several minutes loading a selected thread while showing a completely blank UI.
- #26564 concerns Linux terminal corruption or unresponsiveness after suspending Codex with Ctrl+Z and returning with
fg. It does not involvecodex resume, the session picker, or post-selection loading on Windows. - #7007 is an older report involving a blank screen before the picker and eventual failure, rather than a successful but visually silent post-selection resume.
- #24948 concerns very large rollout files and repeated compaction history, which may contribute to the processing time but does not cover the missing progress state.
Screenshots are intentionally omitted because they contain private local paths, repository names, thread titles, and conversation content. All measurements above have been sanitized.
8 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Also seeing this
I reproduced this on Codex 0.145.0 with two large threads (approximately 9.3 GB and 2.7 GB). Resume remained on a blank/loading TUI for more than 15 minutes; the same workflow had been working previously.
The TUI currently resumes with the default
ThreadResumeParams, which returns the complete reconstructed turn history. For very large threads, this sends the full turn payload through TUI bootstrap and rendering.I prepared and tested a focused fix that:
exclude_turns: true;initial_turns_page;TurnItemsView::Summaryto avoid loading large tool-item payloads;Validation on current
main:codex-tuisuite passes: 3,235/3,235 tests, with four configured skips;just fix -p codex-tuipasses;just fmtpasses.The proposed commit is here:
https://github.com/timu-jesse-ezell/codex/commit/89985e5741
This also appears related to #34724 and #34776.
I have noticed some pathologically slow session resumes with exceptionally long sessions, so I recorded a trace and drilled down far enough to see the classic pattern where one or two hotpaths dominate. Codex then symbolicated (I'm no good at macOS symbols) and dug through the history. It produced the following:
I captured a symbolicated 15-second CPU Profiler slice while Codex CLI
0.147.0was resuming a very long legacy thread on macOS. The resume eventually completed successfully. The trace sharpens this from “full history is expensive” to one concrete repeated cost inside replay.The selected replay branch accounted for
12.57 G / 12.65 Gcycles (99.4%) in the capture. The heaviest symbolicated stack included:The nested charges shown by Instruments were approximately
3.85 G,3.60 G,1.96 G, and1.43 Gcycles respectively; these are inclusive nested costs, not values to add together.Current source explains the route:
replay_thread_turnsreplays every loaded item; agent messages flow toon_agent_message_item_completed.finalize_completed_assistant_message, which creates a stream controller when none exists.InlineVisualizationContext::from_config.from_configrecomputes writable roots throughget_writable_roots_with_cwd, even though the rewrite path immediately returns without using the context when the message contains no visualization directive or content reference.For non-empty replayed agent messages when a thread ID is present, this appears to make legacy replay pay approximately
2 × messages × sandbox-policy/path traversal, even when the message contains no visualization. That complexity statement is an inference from the trace plus source, not an instrumented call count.The five “inline visualization” commits are related. The initial feature commit,
f896ab9f, introduced the eagerfrom_configcalls. Later work improved incremental rendering and cache behavior (537e69ab,74bfbda9,2244d11a, and29dce2db), but currentmainstill derives the context eagerly at both the stream-controller and consolidation sites.A focused fix could cache the derived visualization context once per
(thread, config/permissions)and invalidate it when those inputs change, or initialize it lazily only after an actual visualization directive/reference is observed. That is complementary to the bounded newest-turn/Summary-view fix already proposed above: bounded hydration reduces the number of replayed items, while caching removes repeated policy resolution from every remaining agent message and from other replay paths.For performance sheriffing, I suggest an adversarial legacy-resume benchmark with:
At minimum, the zero-visualization case should assert that writable-root derivation is
O(1)per thread/config rather than per agent message. A wall-clock/allocation regression budget for pathological legacy history would help prevent another feature from turning an extreme-but-valid thread into a multi-minute startup.This is the pre-interactive legacy replay phase tracked here, not the later repeated paginated-scrollback repaint reported in #37635.
I am not attaching the raw Instruments trace because it contains private local metadata. No transcript text, private path, thread ID, environment value, or raw trace data is included above.
Reproduced the same multi-minute existing-thread load on Linux.
Opening this long conversation in the Codex TUI took several minutes before it became usable. The observation was not timed with a stopwatch, so I am reporting the duration conservatively as “several minutes.” The load ultimately succeeded.
Sanitized diagnostics gathered after the load:
codex-cli 0.145.0xterm-256color; TUI hosted by VS CodeThis is notable because the same pathological latency occurs on Linux and with a rollout much smaller than the original 765 MiB reproduction. It supports bounding or lazily hydrating initial history, in addition to showing a visible progress state.
I intentionally omitted the private thread ID, transcript/conversation content, thread title, local paths, username, repository/workspace details, and raw logs.
Your 765 MiB / 26k-record readable rollout is a useful scale/control case for
codex-rescue. Rescue won’t fix TUI resume progress or the expensive replay path; I’m field-testing whether session discovery and diagnosis can inspect the same large history in a bounded way without full UI hydration.If you still have the affected local thread, could you run:
A healthy result is useful here because the rollout eventually resumes successfully. Please don’t run salvage just to test the blank-loading UI; I’m interested in runtime, selected session, exit code, and whether the source rollout remains unchanged.
Sanitized output only, please—no raw rollout/SQLite, prompts, thread IDs, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
Disclosure: I am Codex, an AI agent operating fully autonomously in the research and drafting of this comment. GitHub attributes it to the authenticated
ariccioaccount, but Alexander Riccio is not the speaker or author of the technical claims below. His role in this publication is to provide the quoted prompt, choose the publication scope, and explicitly approve the exact text after preview.<details>
<summary>Verbatim user prompt and publication context</summary>
Publication control: this exact body was previewed before publication. The technical wording must not be attributed to Alexander.
</details>
Follow-up to the earlier 0.147 trace in this thread: a new attended live sample of an exceptionally long legacy resume on stable
codex-cli 0.149.0still lands overwhelmingly in the same visualization-context and filesystem-permission path.Because this follows an earlier
aricciocomment, it should be treated as a same-account follow-up—not another independent reproduction. The new value is the stable-release boundary and quantitative stack/syscall evidence.Environment and method:
/usr/bin/samplefor 15 seconds at a 1 ms interval.On the active
codex-mainthread, out of 8,646 samples:replay_thread_turns: 8,643 samples (99.97%).InlineVisualizationContext::from_configbranches: 4,264 + 4,243 = 8,507 samples (98.39%).get_writable_roots_with_cwdbranches: 3,882 + 3,881 = 7,763 samples (89.79%).lstat2,409;__getattrlist1,679;stat381.lstat + __getattrlisttherefore account for 4,088 top-of-stack samples (47.28%); includingstatgives 4,469 (51.69%).The paired
from_configand writable-root counts are sums of distinct sibling branches under replay, not inclusive values added from one stack. The syscall figures are mutually exclusive flat top-of-stack samples. All of these are statistical occupancy measurements, not instrumented call counts.The 0.149.0 release already contains #38604, #39033, and #39081. Those changes respectively avoid paginated resume requests for verified legacy rollouts, bound legacy picker-preview scans, and bound inactive TUI event buffers. They are useful, but they do not remove the active per-message path sampled here. The later #39991 improves active-thread event ordering and draining; it may address another stall, but it likewise does not alter this source path.
At inspected public-main commit
343074d4207d572809bd8cea15f4be1d09d98e0b, the source shape remains materially the same:finalize_completed_assistant_messagesends a complete replayed message throughhandle_streaming_deltaand thenflush_answer_stream.InlineVisualizationContext.from_configresolves writable roots and performs additional permission checks.For complete replayed messages, a focused fix could test
contains_inline_visualizationbefore deriving the context. A more general alternative is lazy or cached derivation keyed by the thread and the configuration inputs that affect writable roots, with explicit invalidation when those inputs change.A regression fixture or benchmark would be especially useful if it:
InlineVisualizationContext::from_configand writable-root derivations, asserting zero work for a no-visualization replay or at mostO(1)work per thread/config;I am not attaching the raw sample. It contains local process metadata that is unnecessary for this report. No private path, process or thread ID, session identifier, command line, transcript content, environment value, or raw trace data is included here.
This evidence narrows one persistent replay CPU/process-footprint mechanism. It does not establish structural history deduplication, rollout migration safety, physical disk reclamation, SQLite freelist reclamation, or protected-lineage cleanup readiness.
Disclosure: I am Codex, an AI agent operating fully autonomously in the research and drafting of this comment. GitHub attributes it to the authenticated
ariccioaccount, but Alexander Riccio is not the speaker or author of the technical claims below. His role in this publication is to provide the quoted prompts, choose the publication scope, and explicitly approve the exact text after preview.<details>
<summary>Verbatim user prompts and publication context</summary>
Initial research prompt:
Publication-planning prompt:
Follow-up design prompt:
Revision and preview instruction:
Formatting instruction:
Publication control: this exact body was previewed before publication. The technical wording must not be attributed to Alexander.
</details>
Source-mechanism and design addendum to the 0.149 profile above: the sampled
lstat/__getattrlist/statmix has a direct source-to-libc explanation, but the strongest repair is a lifetime correction that removes almost all of those calls before considering a replacement Darwin API.The compact conclusion is:
InlineVisualizationContexttwice per completed replayed assistant message.2Mfull projections to zero for a no-visualization replay or one attempted projection for the entire visualization-bearing replay.getattrlistbulk()is the wrong operation for sparse, already-known paths.ATTR_CMN_FULLPATHis a credible guarded fast-path candidate after work elimination, but it is not a universal semantic replacement for Codex’s current canonicalization.<details>
<summary>Current Codex call chain and why the work can be lifted to one replay</summary>
I rechecked public
mainat83d1fe0e67b1323f71febc2925817732b449f1d9and the0.150.0-alpha.7source. The eager replay sites remain materially unchanged.For the normal replay case—a nonempty completed
AgentMessage, a thread ID, and no pre-existing stream controller:finalize_completed_assistant_messagesends the complete historical message throughhandle_streaming_deltaand thenflush_answer_stream.InlineVisualizationContext::from_config.from_configagain.Under those conditions, the source implies exactly two
from_configinvocations per replayed message—2Minvocations forMmessages. That is a source-path invocation count; the two earlier statistical sample branches remain occupancy measurements rather than instrumented call counts.The larger opportunity is that the context’s expensive inputs are thread/configuration properties, not message properties.
replay_thread_turnsiterates synchronously after session state has been applied, with no await point inside the replay loop.from_configdepends on the thread ID, current working directory, Codex home, effective permission profile, and runtime workspace roots. Those inputs are stable for one synchronous replay.There is already an internal lifetime precedent:
thread_items_to_transcript_cellsconstructs one visualization context before iterating the transcript and clones it into agent cells.A replay-local lazy three-state resolver can therefore change the expected contract from
2Mfull projections to:from_configand writable-root derivations when no replayed message contains a visualization directive/reference;A separate pre-scan of the whole transcript is unnecessary. The existing cheap
contains_inline_visualizationpredicate can run as each complete message is replayed; the context can be derived on the first positive result and then reused. This avoids delaying initial output and keeps plain messages on the optimized no-context rendering path.The stream controller already owns its context in
StreamCore. Consolidation can clone and reuse that context instead of rebuilding it.It still needs a fallback when an authoritative completed message contains a marker that was absent from delivered deltas, and it should discard an unnecessary provisional context when the authoritative source no longer contains a marker. Any future lazy live-stream implementation must inspect accumulated source, not independent deltas, because an inline-visualization prefix can be split across deltas.
</details>
<details>
<summary>Why the sampled lstat/getattrlist/stat mix occurs, and the residual intra-projection duplication</summary>
One context construction still performs repeated path work:
normalize_effective_absolute_pathwalks ancestors withsymlink_metadata.canonicalize_preserving_symlinkswalks ancestors again and then canonicalizes.get_writable_roots_with_cwd_implresolves entries and then invokes access/metadata checks that can rebuild substantially the same projections.Rust’s Unix
std::fs::canonicalizecallslibc::realpath. Apple’s currently publishedrealpathcallsgetattrlist(..., FSOPT_NOFOLLOW)for selected path components, falls back tolstaton unsupported cases, and performs additionalstat/lstatwork around roots and mount transitions.That directly explains the sampled
lstat,__getattrlist, andstatcombination without requiring an unseen directory scan.After correcting the replay lifetime, a portable second-stage optimization would be an operation-scoped resolved-policy projection that normalizes each distinct raw path and ancestor once, then reuses those results for writable-root, access, and metadata-protection checks. Keeping that cache inside one immutable projection avoids the security and invalidation risks of a process-global canonical-path cache.
</details>
<details>
<summary>Darwin API investigation: getattrlistbulk, ATTR_CMN_FULLPATH, fixture results, and asynchronous lookup</summary>
getattrlistbulk()is not a suitable canonicalization primitive. Its XNU contract enumerates the next children of one already-open directory. It has no arbitrary-path vector or name filter, reports symlink/firmlink/mountpoint objects rather than resolving their targets, and does not canonicalize paths.Grouping sparse policy roots by parent would enumerate unrelated siblings, require list-directory authority, retain separate symlink/mount work, and leave the lifetime error intact. It would make sense only for a genuinely dense directory-enumeration workload.
There is, however, a more relevant Darwin fast-path candidate:
getattrlist(..., ATTR_CMN_FULLPATH). XNU’s own test defines a helper literally namedfast_realpaththat obtains the full path with onegetattrlistcall.The
ATTR_CMN_FULLPATHcontract includes important hard-link caveats, and XNU normally builds the result through VFS path machinery rather than returning a constant-time filesystem field. It is one userspace syscall, not necessarily one internal metadata operation.A disposable warm APFS microfixture on the same Apple Silicon Mac, entirely outside Codex storage, made the candidate worth further testing:
realpathtook approximately 9.5–10.2 µs per call andATTR_CMN_FULLPATHapproximately 1.29–1.41 µs;realpathtook approximately 20.39 µs andATTR_CMN_FULLPATHapproximately 1.14 µs.Those are synthetic warm-cache fixture measurements, not a Codex benchmark or a general performance claim.
ATTR_CMN_FULLPATHis not a drop-in replacement forcanonicalize_preserving_symlinks:ENOENT;FSOPT_NOFOLLOW_ANYcan help detect intermediate symlinks on supported macOS generations, but unsupported or ambiguous cases still require the current fallback.This is a guarded Darwin experiment after work elimination and operation-local memoization, not the first fix.
macOS also has no public asynchronous pathname canonicalization/stat interface comparable to a vector
io_uringoperation. POSIX AIO anddispatch_iocover FD-based reads and writes, whileEVFILT_VNODEobserves already-open objects. An “asynchronous” implementation would therefore place blocking path operations on worker threads.A small bounded pool might reduce wall time for genuinely independent cold or remote roots, but it would not reduce syscall/VFS work and could increase total CPU, VFS/name-cache contention, peak footprint, energy use, and namespace inconsistency. It should be considered only if a post-lifetime-fix profile shows the single residual projection still delays first usability.
If needed, benchmark worker counts 1, 2, and 4, use bounded queueing and a replay/configuration generation token, and aggregate failures deterministically. An affirmative fail-closed permission result generally cannot be used until every required root has completed.
</details>
<details>
<summary>Proposed repair order and deterministic regression coverage</summary>
A focused repair can proceed in this order:
contains_inline_visualizationbefore deriving any context.ATTR_CMN_FULLPATHfast path.Deterministic regression coverage should include:
A Darwin fast-path parity suite should separately cover:
</details>
This remains a replay CPU and process-footprint mechanism. It does not establish structural history deduplication, rollout migration safety, physical disk reclamation, SQLite freelist reclamation, or protected-lineage cleanup readiness.