/fork leaves parent thread with active writer, preventing resume in another terminal

Open 💬 6 comments Opened Aug 12, 2026 by pkhafizov
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.147.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.6-sol

What platform is your computer?

Linux 5.15.167.4-microsoft-standard-WSL2 x86_64 x86_64

What terminal emulator and version are you using (if applicable)?

WSL

Codex doctor report

{
  "schemaVersion": 1,
  "generatedAt": "1786518424s since unix epoch",
  "overallStatus": "warning",
  "codexVersion": "0.147.0",
  "checks": {
    "app_server.status": {
      "id": "app_server.status",
      "category": "app-server",
      "status": "ok",
      "summary": "background server is not running",
      "details": {
        "control socket": "/home/[REDACTED_USER]/.codex/app-server-control/app-server-control.sock",
        "daemon state dir": "/home/[REDACTED_USER]/.codex/app-server-daemon",
        "mode": "ephemeral",
        "pid file": "/home/[REDACTED_USER]/.codex/app-server-daemon/app-server.pid (missing)",
        "settings": "/home/[REDACTED_USER]/.codex/app-server-daemon/settings.json (missing)",
        "status": "not running",
        "update-loop pid file": "/home/[REDACTED_USER]/.codex/app-server-daemon/app-server-updater.pid (missing)"
      },
      "remediation": null,
      "durationMs": 0
    },
    "auth.credentials": {
      "id": "auth.credentials",
      "category": "auth",
      "status": "ok",
      "summary": "auth is configured",
      "details": {
        "auth env vars present": "OPENAI_API_KEY",
        "auth file": "/home/[REDACTED_USER]/.codex/auth.json",
        "auth storage mode": "File",
        "stored API key": "false",
        "stored ChatGPT tokens": "true",
        "stored agent identity": "[REDACTED: identity metadata]",
        "stored auth mode": "chatgpt"
      },
      "remediation": null,
      "durationMs": 0
    },
    "config.load": {
      "id": "config.load",
      "category": "config",
      "status": "ok",
      "summary": "config loaded",
      "details": {
        "CODEX_HOME": "/home/[REDACTED_USER]/.codex",
        "config.toml": "/home/[REDACTED_USER]/.codex/config.toml",
        "config.toml parse": "ok",
        "cwd": "/home/[REDACTED_USER]/code/ai-content-pipeline",
        "enabled feature flags": "shell_tool, view_image, unified_exec, shell_snapshot, code_mode_host, terminal_resize_reflow, sqlite, memories, hooks, enable_request_compression, network_proxy, multi_agent, apps, tool_search_always_defer_mcp_tools, tool_suggest, plugins, in_app_browser, in_app_updates, browser_use, browser_use_full_cdp_access, browser_use_external, computer_use, remote_plugin, plugin_sharing, image_generation, resize_all_images, item_ids, skill_mcp_dependency_install, skill_search, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, auth_elicitation, personality, fast_mode, tui_app_server, remote_compaction_v2, workspace_dependencies",
        "feature flag overrides": "memories=true, network_proxy=true",
        "feature flags enabled": "41",
        "log dir": "/home/[REDACTED_USER]/.codex/log",
        "mcp servers": "[REDACTED: MCP server names]",
        "model": "gpt-5.6-sol",
        "model provider": "openai",
        "sqlite home": "/home/[REDACTED_USER]/.codex"
      },
      "remediation": null,
      "durationMs": 0
    },
    "git.environment": {
      "id": "git.environment",
      "category": "git",
      "status": "ok",
      "summary": "git version 2.43.0",
      "details": {
        ".git entry": "[REDACTED: repository metadata]",
        "PATH git #1": "/usr/bin/git",
        "PATH git #2": "/bin/git",
        "PATH git entries": "2",
        "git branch": "[REDACTED: branch name]",
        "git build options": "git version 2.43.0; cpu: x86_64; no commit associated with this build; sizeof-long: 8; sizeof-size_t: 8; shell-path: /bin/sh",
        "git exec path": "/usr/lib/git-core",
        "git version": "git version 2.43.0",
        "repo detected": "true",
        "repo root": "[REDACTED: repository path]",
        "selected git": "/usr/bin/git"
      },
      "remediation": null,
      "durationMs": 79
    },
    "installation": {
      "id": "installation",
      "category": "install",
      "status": "ok",
      "summary": "installation looks consistent",
      "details": {
        "PATH codex #1": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #2": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #3": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #4": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #5": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #6": "/home/[REDACTED_USER]/.local/bin/codex",
        "PATH codex #7": "/mnt/c/nvm4w/nodejs/codex",
        "PATH codex #8": "/mnt/c/nvm4w/nodejs/codex",
        "PATH codex entries": "8",
        "current executable": "/home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/bin/codex",
        "install context": "standalone (unix, package /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl, bin /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/bin, resources /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/codex-resources, path /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/codex-path)",
        "managed by bun": "false",
        "managed by npm": "false",
        "managed by pnpm": "false",
        "managed package root": "not set"
      },
      "remediation": null,
      "durationMs": 23
    },
    "mcp.config": {
      "id": "mcp.config",
      "category": "mcp",
      "status": "ok",
      "summary": "MCP configuration is locally consistent",
      "details": {
        "configured servers": "[REDACTED: MCP server names]",
        "disabled servers": "[REDACTED: MCP server names]",
        "stdio servers": "[REDACTED: MCP server names]",
        "streamable_http servers": "[REDACTED: MCP server names]"
      },
      "remediation": null,
      "durationMs": 3481
    },
    "network.env": {
      "id": "network.env",
      "category": "network",
      "status": "ok",
      "summary": "network-related environment looks readable",
      "details": {
        "proxy env vars": "[REDACTED: proxy configuration]"
      },
      "remediation": null,
      "durationMs": 0
    },
    "network.provider_reachability": {
      "id": "network.provider_reachability",
      "category": "reachability",
      "status": "ok",
      "summary": "active provider endpoints are reachable over HTTP",
      "details": {
        "openai API base URL": "https://api.openai.com/v1 reachable (HTTP 404)",
        "openai API route probe": "https://api.openai.com/v1/<redacted> route exists (HTTP 401)",
        "reachability mode": "API key auth"
      },
      "remediation": null,
      "durationMs": 682
    },
    "network.websocket_reachability": {
      "id": "network.websocket_reachability",
      "category": "websocket",
      "status": "ok",
      "summary": "Responses WebSocket handshake succeeded",
      "details": {
        "DNS": "[REDACTED: DNS result]",
        "auth mode": "chatgpt",
        "connect timeout": "15000 ms",
        "endpoint": "wss://chatgpt.com/backend-api/<redacted>",
        "handshake result": "HTTP 101 Switching Protocols",
        "model provider": "openai",
        "models etag present": "true",
        "provider name": "OpenAI",
        "proxy env vars": "[REDACTED: proxy configuration]",
        "reasoning header": "false",
        "server model present": "false",
        "supports websockets": "true",
        "wire API": "responses"
      },
      "remediation": null,
      "durationMs": 1620
    },
    "runtime.provenance": {
      "id": "runtime.provenance",
      "category": "runtime",
      "status": "ok",
      "summary": "running standalone on linux-x86_64",
      "details": {
        "commit": "unknown",
        "current executable": "/home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/bin/codex",
        "install method": "standalone (unix, package /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl, bin /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/bin, resources /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/codex-resources, path /home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/codex-path)",
        "platform": "linux-x86_64",
        "version": "0.147.0"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.search": {
      "id": "runtime.search",
      "category": "search",
      "status": "ok",
      "summary": "search is OK (bundled)",
      "details": {
        "search command": "/home/[REDACTED_USER]/.codex/packages/standalone/releases/0.147.0-x86_64-unknown-linux-musl/codex-path/rg",
        "search command readiness": "file exists",
        "search provider": "bundled"
      },
      "remediation": null,
      "durationMs": 0
    },
    "sandbox.helpers": {
      "id": "sandbox.helpers",
      "category": "sandbox",
      "status": "ok",
      "summary": "sandbox configuration is readable",
      "details": {
        "approval policy": "OnRequest",
        "codex-linux-sandbox helper": "/home/[REDACTED_USER]/.codex/tmp/[REDACTED]",
        "execve wrapper helper": "/home/[REDACTED_USER]/.codex/tmp/[REDACTED]",
        "filesystem sandbox": "restricted",
        "network sandbox": "restricted"
      },
      "remediation": null,
      "durationMs": 0
    },
    "state.paths": {
      "id": "state.paths",
      "category": "state",
      "status": "ok",
      "summary": "state paths and databases are inspectable",
      "details": {
        "CODEX_HOME": "/home/[REDACTED_USER]/.codex (dir)",
        "active rollout files": "1404 files, 1578995851 total bytes, 1124640 average bytes",
        "archived rollout files": "0 files, 0 total bytes, 0 average bytes",
        "goals DB": "/home/[REDACTED_USER]/.codex/goals_1.sqlite (file)",
        "goals DB integrity": "ok",
        "log DB": "/home/[REDACTED_USER]/.codex/logs_2.sqlite (file)",
        "log DB integrity": "ok",
        "log dir": "/home/[REDACTED_USER]/.codex/log (dir)",
        "memories DB": "/home/[REDACTED_USER]/.codex/memories_1.sqlite (file)",
        "memories DB integrity": "ok",
        "queue DB": "/home/[REDACTED_USER]/.codex/queue_1.sqlite (file)",
        "queue DB integrity": "ok",
        "sqlite home": "/home/[REDACTED_USER]/.codex (dir)",
        "standalone release cache": "3 entries in /home/[REDACTED_USER]/.codex/packages/standalone/releases",
        "state DB": "/home/[REDACTED_USER]/.codex/state_5.sqlite (file)",
        "state DB integrity": "ok",
        "thread history DB": "/home/[REDACTED_USER]/.codex/thread_history_1.sqlite (file)",
        "thread history DB integrity": "ok"
      },
      "remediation": null,
      "durationMs": 4889
    },
    "state.rollout_db_parity": {
      "id": "state.rollout_db_parity",
      "category": "threads",
      "status": "warning",
      "summary": "rollout files and state DB thread inventory differ",
      "details": {
        "default model provider": "openai",
        "rollout DB active files": "1400",
        "rollout DB active rows": "1395",
        "rollout DB archive mismatches": "0",
        "rollout DB archived files": "0",
        "rollout DB archived rows": "0",
        "rollout DB duplicate DB paths": "0",
        "rollout DB duplicate rollout thread ids": "0",
        "rollout DB malformed file names": "0",
        "rollout DB missing active rows": "5",
        "rollout DB missing active sample": [
          "[REDACTED: rollout path and identifier]"
        ],
        "rollout DB missing archived rows": "0",
        "rollout DB model providers": "openai=1395",
        "rollout DB rows": "1395",
        "rollout DB scan cap reached": "false",
        "rollout DB scan error sample": [
          "[REDACTED: rollout path and identifier]"
        ],
        "rollout DB scan errors": "4",
        "rollout DB sources": "cli=448, subagent:thread_spawn=407, vscode=340, subagent:review=188, exec=12",
        "rollout DB stale rows": "0"
      },
      "issues": [
        {
          "severity": "warning",
          "cause": "rollout files are missing from the state DB",
          "measured": "5 active, 0 archived",
          "expected": "every rollout file has a matching threads row",
          "remedy": null,
          "fields": []
        },
        {
          "severity": "warning",
          "cause": "rollout scan was incomplete or found bad files",
          "measured": "4 scan errors, 0 malformed names, scan cap reached: false",
          "expected": "rollout directories are fully scannable",
          "remedy": "Check file permissions and unexpected files under CODEX_HOME sessions.",
          "fields": []
        }
      ],
      "remediation": null,
      "durationMs": 988
    },
    "system.environment": {
      "id": "system.environment",
      "category": "system",
      "status": "ok",
      "summary": "OS language C",
      "details": {
        "EDITOR": "not set",
        "LANG": "C.UTF-8",
        "VISUAL": "not set",
        "os": "Ubuntu 24.4.0 (noble) [64-bit]",
        "os language": "C",
        "os type": "Ubuntu",
        "os version": "24.4.0"
      },
      "remediation": null,
      "durationMs": 4
    },
    "terminal.env": {
      "id": "terminal.env",
      "category": "terminal",
      "status": "ok",
      "summary": "terminal metadata was detected",
      "details": {
        "COLORTERM": "truecolor",
        "DISPLAY": "[REDACTED: local display endpoint]",
        "TERM_PROGRAM": "vscode",
        "VSCODE_IPC_HOOK_CLI": "[REDACTED: local IPC path]",
        "WAYLAND_DISPLAY": "[REDACTED: local display endpoint]",
        "WSL_DISTRO_NAME": "present",
        "WSL_INTEROP": "[REDACTED: local IPC path]",
        "color output": "enabled",
        "effective locale": "C.UTF-8",
        "stderr is terminal": "true",
        "stdin is terminal": "true",
        "stdout is terminal": "true",
        "terminal": "VS Code",
        "terminal size": "306x37",
        "terminal version": "1.132.1"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.title": {
      "id": "terminal.title",
      "category": "title",
      "status": "ok",
      "summary": "terminal title default",
      "details": {
        "redacted": "[REDACTED: terminal project metadata]"
      },
      "remediation": null,
      "durationMs": 0
    },
    "updates.status": {
      "id": "updates.status",
      "category": "updates",
      "status": "ok",
      "summary": "update configuration is locally consistent",
      "details": {
        "cached latest version": "0.147.0",
        "check for update on startup": "true",
        "last checked at": "2026-08-12T06:19:22.738664369Z",
        "latest version": "0.147.0",
        "latest version status": "current version is not older",
        "update action": "standalone installer",
        "version cache": "/home/[REDACTED_USER]/.codex/version.json"
      },
      "remediation": null,
      "durationMs": 783
    }
  }
}

What issue are you seeing?

/fork leaves the parent session with an active writer, so the parent session cannot be resumed in another terminal.

I am using Codex CLI 0.147.0.

Steps to reproduce:

  1. Start working in a Codex CLI session.
  2. Run:
/fork
  1. Codex switches to a new forked session.
  2. Run:
/status

The result confirms that the current TUI is attached to the new forked session:

Session:              019ff4b6-6708-7830-8f43-11ce2495c89f
Forked from:          019ff4a0-1d4d-79a3-a456-ab7187a79d02

After /fork, Codex also prints:

To continue this session, run codex resume 019ff4a0-1d4d-79a3-a456-ab7187a79d02
  1. In another terminal, run exactly that command:
codex resume 019ff4a0-1d4d-79a3-a456-ab7187a79d02

Instead of resuming the parent session, Codex fails with:

Error: Failed to resume session from /home/pavel/.codex/sessions/2026/08/12/rollout-2026-08-12T11-19-21-019ff4a0-1d4d-79a3-a456-ab7187a79d02.jsonl: thread/resume failed during TUI bootstrap: thread/resume failed: thread 019ff4a0-1d4d-79a3-a456-ab7187a79d02 already has an active writer (code -32600)

What steps can reproduce the bug?

Uploaded thread: 019ff4b6-6708-7830-8f43-11ce2495c89f

What is the expected behavior?

_No response_

Additional information

_No response_

View original on GitHub ↗

6 Comments

github-actions[bot] contributor · 16 days ago

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

  • #37450
  • #37403

Powered by Codex Action

pkhafizov · 16 days ago
Potential duplicates detected. Please review them and close your issue if it is a duplicate. 0.147-alpha (Windows): App keeps thread writer for every opened thread until full app exit; no attach path for external clients #37450 [[macOS][regression] Desktop cannot resume Remote Control / CLI thread: already has an active writer after latest update #37403](https://github.com/openai/codex/issues/37403) _Powered by Codex Action_

Related reports appear to show the same broader writer-ownership regression in 0.147, but through different transitions:

  • Desktop navigation: a thread keeps its writer after the app navigates away from it.
  • Remote Control / CLI → Desktop handoff: the same thread cannot be resumed by Desktop because another writer remains active.

My reproduction is specifically CLI /fork: after the TUI moves from parent thread X to forked thread Y, /status confirms Y is active, but X still reports an active writer and cannot be resumed in another terminal.
These may share the same underlying writer lifecycle / ownership-release mechanism.

noah-foltz · 15 days ago

I traced the release regression and prepared a draft fix on my fork: https://github.com/noah-foltz/codex/pull/1

Root cause:

  • /fork switches to the fork but the old shutdown_current_thread path only called thread/unsubscribe.
  • Since the idle-thread cache was introduced, the app-server keeps an unsubscribed parent loaded for 30 minutes, including its rollout writer.
  • Before writer ownership was enforced, the printed codex resume <parent> command could still open that rollout concurrently. That appeared to work, but allowed two writers.
  • 5c94796dc9e88580fdf0b05ef9ce9d975a86e1a6 (#34986) added the -32600 guard for paginated histories, first shipped in rust-v0.146.0-alpha.5 and stable rust-v0.146.0.
  • dc60dadce6b5911b9b5852365f6eb9829ec14442 (#36389) expanded ownership to every history, while dbcd837c20cd891f3de4bb3976c7f643d0b2fb93 (#36950) made normal non-ephemeral TUI histories paginated. Both are present in 0.147.0, making this deterministic for ordinary /fork sessions.

The proposed fix keeps the single-writer protection. It adds an optional unload: true mode to v2 thread/unsubscribe; app-server reserves teardown, verifies no other connection remains subscribed, shuts down the thread, and waits for the writer lock to be released before responding. Primary TUI thread switches use that mode, so /fork does not advertise the parent resume command until ownership has actually been released. Ordinary unsubscriptions retain the existing 30-minute cache.

The regression test materializes a rollout in app-server process A, explicitly unloads it, and immediately resumes the same thread in process B. There is also TUI coverage asserting /fork sends unload: true. Protocol/schema, focused app-server lifecycle, focused TUI lifecycle, and exec suites pass.

Current workaround: exit the originating TUI before running the printed parent resume command, or wait for the idle unload timeout.

cyenxchen · 11 days ago

I independently reproduced this on macOS and captured descriptor-level evidence that the original TUI retains both the parent and child writers after /fork.

Environment

  • Codex CLI: 0.147.0
  • macOS: 26.6.1
  • Architecture: arm64
  • Shell: zsh 5.9

Reproduction

  1. Continue a normal CLI session (parent).
  2. Run /fork; the original TUI switches to the child session.
  3. Continue the conversation in the child.
  4. In a second terminal, run codex resume <parent-thread-id>.
  5. Resume fails with:
Failed to resume session from ~/.codex/sessions/.../rollout-...-<parent-thread-id>.jsonl:
thread/resume failed during TUI bootstrap:
thread/resume failed: thread <parent-thread-id> already has an active writer (code -32600)

Descriptor evidence

While the original TUI was actively attached to the child, lsof showed that the same original Codex process still held all four resources:

parent rollout JSONL   fd 46u
parent writer lock     fd 48u
child writer lock      fd 43u
child rollout JSONL    fd 60u

The parent rollout's last modification time was 15:08:59; the child rollout was created at 15:10:49. The parent JSONL did not change after the fork, but its writable descriptor and writer lock remained open. The last parent JSONL record also parsed successfully, so this was not caused by obvious transcript corruption.

This confirms the issue across both WSL (original report) and macOS/arm64: after the TUI switches to the fork, the parent is idle but remains writer-owned by the original process. Exiting the originating TUI releases the ownership and is the current workaround.

The lifecycle explanation in the existing root-cause comment (unsubscribe leaving the parent loaded in the idle-thread cache) is consistent with this observation.

jdcodes1 · 9 days ago

Same mechanism as #38297 (traced there on current main): the TUI fork/switch path swaps the active thread and never closes/unloads the parent, so the parent stays loaded in the app-server holding its rollout writer — which is exactly the already has an active writer rejection on cross-terminal resume. Code refs and two fix levels (unload-on-switch-away; release the rollout writer for idle loaded threads): https://github.com/openai/codex/issues/38297#issuecomment-5323038119

Worth deduping these two into one tracked fix.

yuzushi-dev · 4 days ago

This appears to be a related but distinct variant of this issue. In my case, the lock is retained by a long-running shared app-server after the interactive client that opened the thread is no longer available.

Environment

  • Codex CLI: 0.149.0
  • Ubuntu 24.04.1, Linux x86_64
  • Session origin: Codex Desktop / VS Code integration
  • Shared server:
codex -c features.code_mode_host=true app-server --listen unix://

Reproduction from the resulting state

The original client-disconnect event was not captured, but the failure is reproducible from the resulting state:

  1. Start a Codex Desktop or VS Code session backed by a shared local app-server.
  2. Close, disconnect, or otherwise lose the client that opened the thread while leaving the app-server running.
  3. From another terminal, run:
codex resume <THREAD_ID>

The same failure occurs when selecting the thread through the resume UI.

Actual result

Failed to resume session from ...: thread/resume failed during TUI bootstrap: thread/resume failed: thread <THREAD_ID> already has an active writer (code -32600)

The failure occurs before the resumed TUI becomes usable.

Diagnostic evidence

The writer lock is actively held by the shared app-server, not merely left as an unowned file. Descriptor inspection shows that the same app-server process has both the affected thread's JSONL history and its writer-lock file open. The process also holds writer locks and history files for several older threads.

The affected thread metadata identifies originator: Codex Desktop, source: vscode, and cli_version: 0.149.0.

I also investigated a local session-handoff wrapper. The affected thread and lock predate its installation, and the app-server environment contains no SESSION_HANDOFF_* variables, so session handoff does not appear to cause this instance.

Expected result

Once the original interactive client is gone, a new client should be able to resume the thread. The app-server should release or reclaim writer ownership for disconnected threads, while preserving single-writer protection for genuinely active clients.

This looks like an app-server writer-lifecycle issue related to the /fork and idle-thread cases already discussed here, but the trigger is client loss / persistence of a shared app-server rather than /fork. The fact that one daemon retains locks for multiple historical threads may help distinguish the cases.