/goal-first sessions can still be omitted from codex resume when preview metadata stays empty

Resolved 💬 5 comments Opened Jun 15, 2026 by favoyang Closed Jun 18, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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

  1. Use codex-cli 0.139.0 or a current main build.
  2. Start codex in a local project directory, for example /Users/alice/projects/codex.
  3. Make the first user interaction a /goal command. Example objective: echo 1,2,3 until 10.
  4. Let the goal complete.
  5. Quit the session. The CLI prints a direct resume hint such as codex resume <thread-id>.
  6. From the same project directory, run codex resume with 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 = cli
  • model_provider = openai
  • cwd = /Users/alice/projects/codex
  • archived = 0
  • preview = ""
  • 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/main at dfd03ea01bbec2613013b477fb82abc67534a7d7
  • rust-v0.139.0 at 7cadca4c6e8c821fa7b75b50006b1806ccebdf1c

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.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #28188

Powered by Codex Action

favoyang · 2 months ago

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 ThreadGoalUpdated event being persisted to the rollout, so metadata sync cannot recover a preview later.

etraut-openai contributor · 2 months ago

@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.

jhste102lab · 2 months ago

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:

  1. The failure also showed up through the Desktop/app-server listing path, not only the interactive CLI picker:
  • thread/read { threadId } could load the affected parent session.
  • thread/list { limit: 100 } omitted it.
  • thread/list { limit: 100, cwd: <workspace> } also omitted it.
  • Nearby sessions in the same workspace that began with a normal user message were listed normally.
  1. Besides goal-first hidden/internal context, I also saw a malformed early-turn pattern where a leading interrupted pseudo-turn hid the later real objective:

``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 = user and has_user_event = 1 was not sufficient. The session still did not appear in thread/list until the leading <turn_aborted> pseudo-user item and matching turn_aborted event were removed/normalized so the first normal user message appeared early in the rollout.

Manual recovery that made the affected parent sessions visible again:

  • normalize rollout session_meta.payload.thread_source to "user";
  • ensure an early normal response_item user message plus matching event_msg user_message exists using the goal/objective text;
  • populate empty title / preview / first_user_message in state_5.sqlite.threads;
  • verify with app-server thread/list, not only thread/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.

jhste102lab · 2 months ago

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):

  • the sessions were still on disk and resumable/readable by id;
  • they were missing from codex resume / list-based discovery;
  • state_5.sqlite.threads had has_user_event = 0, preview = "", and first_user_message = "" for the hidden threads;
  • the rollout did contain user-visible intent, but the earliest durable user item was a generic attachment wrapper message telling Codex to read attached content, while the real task text lived in the pasted-text attachment.

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.