/goal-first sessions can still be omitted from codex resume when preview metadata stays empty
Summary
A Codex CLI session whose first interaction is /goal can still be omitted from the interactive codex resume list, even though the thread exists and can be resumed directly by ID.
This looks related to #20792, but the remaining failure is a writer-side gap: the existing fix lets resume/list metadata use a persisted ThreadGoalUpdated event as the preview, but the live app-server goal update path can notify the client without persisting that event to the rollout.
Steps to reproduce
- Use
codex-cli 0.139.0or a currentmainbuild. - Start
codexin a local project directory, for example/Users/alice/projects/codex. - Make the first user interaction a
/goalcommand. Example objective:echo 1,2,3 until 10. - Let the goal complete.
- Quit the session. The CLI prints a direct resume hint such as
codex resume <thread-id>. - From the same project directory, run
codex resumewith no explicit thread ID.
Expected behavior
The goal-first session appears in the interactive resume list for the same cwd.
Actual behavior
The session can be missing from the interactive resume list, even though the thread row exists and has the expected cwd/source/provider/archive metadata. Direct resume by thread ID can still be available.
Investigation notes
I reproduced this on codex-cli 0.139.0. The affected thread had:
source = climodel_provider = openaicwd = /Users/alice/projects/codexarchived = 0preview = ""first_user_message = ""
The resume/list query excludes rows with an empty preview, so this thread is filtered out.
The corresponding rollout contained the session metadata and hidden goal context, but no durable EventMsg::ThreadGoalUpdated. That means the metadata sync path has no goal event to use as a preview later.
I checked the official refs:
origin/mainatdfd03ea01bbec2613013b477fb82abc67534a7d7rust-v0.139.0at7cadca4c6e8c821fa7b75b50006b1806ccebdf1c
Both have the same app-server listener behavior in codex-rs/app-server/src/request_processors/thread_lifecycle.rs: ThreadListenerCommand::EmitThreadGoalUpdated sends thread/goal/updated, but does not persist EventMsg::ThreadGoalUpdated into the rollout.
The earlier fix commit f10ddc3f13 (Use goal preview metadata for goal-first threads) is present, but it appears to cover the reader/indexing side. It does not cover this path where the goal event is never written.
Suspected fix
Persist EventMsg::ThreadGoalUpdated to the live thread rollout before/with the ordered thread/goal/updated notification. Then, even if the SQLite preview update happens before the thread metadata row exists, later rollout metadata sync can recover the preview from the persisted goal objective.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
This is different from #28188: the reproduction here does not involve any usage-limit state. The affected session had an active/completed goal and an empty thread preview, but it was not usage-limited. The issue is that the goal update can be sent as a notification without a durable
ThreadGoalUpdatedevent being persisted to the rollout, so metadata sync cannot recover a preview later.@favoyang, thanks for the clean repro steps. I'm able to repro the problem, and I've traced it to a post-0.137.0 regression along a specific code path that was missed by our existing regression tests. I'll work on a fix.
Additional datapoint from #28423, which I filed before noticing this issue is probably the same root cause.
I agree this is likely covered by this issue, but there are two observations that may be useful for the fix/regression tests:
thread/read { threadId }could load the affected parent session.thread/list { limit: 100 }omitted it.thread/list { limit: 100, cwd: <workspace> }also omitted it.``
text
``session_meta
EventMsg task_started
ResponseItem role=user content=<turn_aborted>...
EventMsg turn_aborted
ResponseItem role=user content=<actual objective text>
EventMsg user_message message=<actual objective text>
In that case, updating DB metadata to
thread_source = userandhas_user_event = 1was not sufficient. The session still did not appear inthread/listuntil the leading<turn_aborted>pseudo-user item and matchingturn_abortedevent were removed/normalized so the first normal user message appeared early in the rollout.Manual recovery that made the affected parent sessions visible again:
session_meta.payload.thread_sourceto"user";response_itemuser message plus matchingevent_msguser_messageexists using the goal/objective text;title/preview/first_user_messageinstate_5.sqlite.threads;thread/list, not onlythread/read.This was observed with a running 0.139.0 app-server and local CLI updated to 0.140.0. No app-server restart was required for the manual recovery. I am closing #28423 as a likely duplicate and leaving this here as extra evidence/test coverage for the same root cause.
Additional independent repro on June 16, 2026 from a different environment. This still looks like the same underlying bug, but there is one more concrete pattern that may be worth covering in regression tests.
Observed across three separate parent sessions started within about 30 minutes of each other (one local WSL session and two remote Linux hosts):
codex resume/ list-based discovery;state_5.sqlite.threadshadhas_user_event = 0,preview = "", andfirst_user_message = ""for the hidden threads;In this state, the session behaves similarly to a goal-first hidden-context thread even when the operator-facing task clearly started from a user instruction. After normalizing the early rollout user item/event to the real task text and filling
preview/first_user_message/has_user_event, the sessions became visible in resume/list flows again.So the fix may need to cover not only
/goal-first preview persistence, but also any startup path where the first durable user-visible item is a synthetic attachment wrapper instead of the actual user instruction. From the list/index perspective, those appear to collapse into the same failure mode: a valid parent thread exists, but no durable real first user message is available for preview generation.