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

Resolved 💬 4 comments Opened Jun 16, 2026 by jhste102lab Closed Jun 16, 2026

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 ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗