Goal-first parent sessions can be hidden from resume/thread lists until rollout user events are normalized

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

Summary

A parent Codex session that starts from /goal can remain hidden from both the Desktop/app-server thread list and the interactive resume picker, even though the session is still present on disk and can be opened directly by thread id.

This appears related to #20792 and #28263, but this case had an additional Desktop/app-server indexing symptom: thread/read worked for the affected parent threads, while thread/list omitted them until the rollout JSONL and SQLite metadata were manually normalized so the session looked like a normal user-started chat.

No conversation data was lost. The problem was discoverability: valid parent sessions became invisible in list-based UI/resume flows.

Environment

Redacted local environment:

Platform: Linux aarch64, Ubuntu 24.04-family environment
Codex Desktop/app-server user agent observed: codex_chatgpt_ios_remote/0.139.0
Codex CLI installed locally after update: 0.140.0
Codex home: ~/.codex
Relevant state files:
  ~/.codex/state_5.sqlite
  ~/.codex/goals_1.sqlite
  ~/.codex/sessions/**/rollout-*.jsonl
  ~/.codex/session_index.jsonl

The app-server was intentionally not restarted during recovery because active workers were running. The running app-server was still 0.139.0 while the local CLI binary had been updated to 0.140.0.

What happened

Several long-running parent goal sessions were valid and resumable by id, but did not appear in list-based discovery:

  • thread/read { threadId } returned the affected session.
  • Direct resume by id was possible for at least some affected sessions.
  • thread/list { limit: 100 } did not include the affected sessions.
  • thread/list { limit: 100, cwd: <workspace> } also omitted them.
  • Nearby sessions in the same workspace that started with a normal user message did appear.
  • Child/subagent sessions were intentionally excluded from this investigation; the issue was reproducible with parent sessions.

One control session in the same environment was visible normally. The important difference was that visible sessions had an early normal user message/user event, while the hidden goal-first parent sessions began with internal goal context or an interrupted-turn marker before the first normal user message.

Evidence from local state

The affected parent sessions existed in local state:

  • rollout JSONL files existed under ~/.codex/sessions/...;
  • state_5.sqlite.threads had rows for the affected thread ids;
  • goals_1.sqlite.thread_goals had parent goal records;
  • the sessions were not simply deleted;
  • thread/read could load them by id.

The list omission was therefore not data loss. It was a listing/indexing/classification problem.

Observed problematic rollout patterns included:

Pattern A: goal-first session starts with hidden/internal goal context

The early rollout content looked like a goal-created thread rather than a normal user-started chat. The first visible user instruction was effectively hidden behind internal goal context, e.g. a response_item user message containing internal goal context rather than a durable normal event_msg of type user_message.

The thread could have useful assistant history later, but thread/list still treated it as not listable/discoverable.

Pattern B: leading interrupted turn before the first real user message

One parent session had an early structure similar to:

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>

Even after metadata was updated to thread_source = user and has_user_event = 1, this session remained missing from thread/list. Removing the leading aborted pseudo-user message and its matching turn_aborted event so the first normal user message appeared at the start made it listable.

Manual recovery that made the sessions visible again

Backups were made first. Then the parent sessions were repaired locally as follows:

  1. Update rollout session_meta.payload.thread_source to "user" when it was missing, goal, or otherwise not classified as a normal user thread.
  2. Ensure the rollout contains an early normal user message:
  • response_item with payload.type = "message", role = "user", and the actual objective/user instruction text;
  • matching event_msg with payload.type = "user_message" and the same redacted objective text.
  1. For goal-first rollouts that began with internal goal context, replace/normalize the first user-visible item so the session has a real first user message.
  2. For rollouts that began with a synthetic <turn_aborted> user item before the real objective, remove the leading aborted pseudo-user item and its matching turn_aborted event.
  3. Update state_5.sqlite.threads for the same ids:
  • thread_source = 'user'
  • has_user_event = 1
  • populate empty title, preview, and/or first_user_message from the redacted objective text.
  1. Verify using the running app-server, not only the database:
  • thread/read { threadId } still works;
  • thread/list { limit: 100 } now includes the parent thread;
  • thread/list { limit: 100, cwd: <workspace> } now includes the parent thread.

After this normalization, the affected parent sessions appeared in thread/list and in workspace-scoped listing without restarting the app-server.

Expected behavior

A valid parent session should remain discoverable in resume/thread lists even when the first user action is /goal, or when a previous turn was interrupted before the real objective was written.

At minimum, list/index code should be able to derive a safe preview from one of:

  • the goal objective;
  • the first real user message after hidden goal context;
  • a persisted ThreadGoalUpdated/goal metadata event;
  • a fallback title for otherwise valid, readable parent sessions.

A leading interrupted pseudo-turn should not permanently hide a later valid parent session from list-based discovery.

Actual behavior

The sessions were present and readable by id, but omitted from list-based discovery until local state was manually rewritten to make the rollout begin like a normal user-started chat.

Why this matters

From the user perspective this looks like lost conversation history, especially for long-running goal/orchestrator work. In reality the data remains on disk, but the normal discovery path hides it. Users can start duplicate work, lose orchestration context, or assume a long-running parent session was deleted.

Suggested fixes

  • Persist a durable normal preview/title/user-message equivalent when /goal is the first interaction.
  • Persist goal updates to rollout before or together with app-server goal notifications, so later metadata rebuilds can recover the preview.
  • Make thread/list tolerant of goal-first sessions whose first event is internal goal context.
  • Make thread/list tolerant of a leading interrupted/aborted pseudo-turn followed by a real user message.
  • Add a local repair/reindex path that rebuilds listable metadata from ~/.codex/sessions/**/*.jsonl and goals_1.sqlite.
  • Add diagnostics for why a thread/read-able session is excluded from thread/list.

Related issues

  • #20792 — /goal-first sessions are missing from resume lists
  • #28263 — /goal-first sessions can still be omitted from codex resume when preview metadata stays empty
  • #25463 — Desktop project threads disappear from project views/search while session JSONL remains readable
  • #25084 — Desktop hides active project chat history while local threads remain on disk

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.

  • #28263

Powered by Codex Action

jhste102lab · 2 months ago

Closing this as a likely duplicate of #28263.

The core failure is the same: a /goal-first parent session remains readable/resumable by id, but is omitted from list-based discovery because the durable preview/first-user-message metadata is missing or malformed.

I posted the extra observations from this report on #28263, specifically:

  • the Desktop/app-server path also showed the mismatch (thread/read worked while thread/list omitted the thread);
  • a leading <turn_aborted> pseudo-user item before the real objective can also keep a parent session hidden until the early rollout user events are normalized.
jhste102lab · 2 months ago

Closing as a duplicate of #28263; additional observations from this issue have been posted there.

jhste102lab · 2 months ago

One more concrete datapoint from the same investigation, in case someone lands here from search before reading #28263.

I reproduced the hidden-session symptom again on June 16, 2026 across three recent parent sessions, and the additional pattern was:

  • has_user_event = 0
  • preview = ""
  • first_user_message = ""
  • the rollout's earliest user-visible item was a synthetic attachment-wrapper message telling Codex to read attached content rather than the real task text, which lived in the pasted-text attachment.

That makes this look even closer to the same root cause as #28263: list/resume discoverability breaks whenever the thread never gets a durable real first user message / preview source, regardless of whether the initiating path was /goal hidden context or attachment-wrapped startup.

Leaving this note here for searchability, but I still think the active fix should be tracked on #28263.

boombx403-byte · 9 days ago

Hi @jhste102lab, this subagent persistence/history issue aligns with some boundary anomalies observed in multi-agent rollouts. Codex Rescue Alpha5 provides read-only lifecycle and subagent boundary diagnostics, cleanly separating historical start markers from current live execution state without altering the source rollout.

If you have access to the local session, you can run a non-destructive check:

pip install codex-rescue==0.1.0a5
codex-rescue doctor --latest

No raw session data is required, and please redact private paths if you share any output.