[CLI][Linux] VS Code Remote-SSH terminal disconnect leaves completed TUI writer locks with no safe recovery
What version of Codex CLI is running?
codex-cli 0.147.0 when the failures were observed. The host is now on the current codex-cli 0.148.0 release. The 0.148.0 release notes do not list a fix for this lifecycle/recovery case, and the 0.148.0 WriterLockCoordinator still returns the same ownerless conflict.
What subscription do you have?
API-key authentication through a Responses-compatible provider; no ChatGPT subscription is involved in this reproduction.
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Linux 5.15.0 x86_64 x86_64 (Ubuntu 22.04.5 LTS)
Node.js v20.20.2, npm 10.8.2, npm-installed Codex CLI.
What terminal emulator and version are you using (if applicable)?
VS Code 1.105.1 Remote-SSH integrated terminal, bash, no tmux/screen/zellij. Codex is launched after sudo su; this host has Defaults use_pty in /etc/sudoers.
Codex doctor report
The full codex doctor --json report was reviewed but is not pasted because it contains private provider URLs and local paths. Relevant sanitized fields after upgrading to 0.148.0 are:
{
"schemaVersion": 1,
"codexVersion": "0.148.0",
"installation": {
"status": "ok",
"method": "npm",
"platform": "linux-x86_64"
},
"state": {
"stateDbIntegrity": "ok",
"threadHistoryDbIntegrity": "ok",
"rolloutDbParity": "ok",
"rolloutScanErrors": 0
},
"updates": {
"latestVersion": "0.148.0",
"status": "current"
},
"note": "The only failing doctor check was TERM=dumb because doctor was captured non-interactively after recovery; it is unrelated to the original VS Code TTY reproduction."
}
What issue are you seeing?
Closing or disconnecting a VS Code Remote-SSH integrated terminal without first entering /quit can leave the standalone Codex TUI process alive behind the sudo su/PTY chain. The latest turn is already complete, but that process continues to hold the per-thread kernel writer lock indefinitely. A later codex resume from a new terminal fails with no owner identity or supported recovery path:
Error: Failed to resume session from /root/.codex/sessions/.../rollout-...-<thread-id>.jsonl:
thread/resume failed during TUI bootstrap:
thread/resume failed: thread <thread-id> already has an active writer (code -32600)
This was independently confirmed for three different threads/workspaces. In each incident:
- the rollout JSONL parsed completely;
- the last record was
event_msg/task_complete; - the rollout had not changed for 16-23 hours;
/proc/locksshowedFLOCK ADVISORY WRITEon the exact lock-file inode;- the owning native Codex PID had open FDs for both the rollout JSONL and
thread-writer-locks/<thread-id>.lock; - the owner was sleeping in
futex_wait_queue_me, with no active turn; - one owner had stdin/stdout/stderr on a deleted PTY, while two remained attached to old VS Code PTYs;
- sending
SIGTERMonly to the verified native Codex PID released the lock, removed its wrapper/code-mode process family, and made a nonblockingflockprobe succeed; - the rollout byte size, JSON parse result, final
task_complete, and SHA-256 remained unchanged after recovery.
A broader read-only audit on 2026-08-19 found 10 additional old native Codex processes holding 10 distinct thread writer locks, excluding the current live control session:
- all 10 were sleeping in
futex_wait_queue_me; - all 10 rollouts ended in
task_complete; - their rollouts had been unchanged for roughly 2-5 days;
- four owners had no controlling TTY and six were attached to old PTYs;
- after npm upgraded the host to 0.148.0, all 10 still executed the deleted 0.147.0 native-binary inode and retained their locks.
The session store itself is healthy. codex doctor reports state DB integrity, thread-history DB integrity, and rollout/DB parity as ok with zero rollout scan errors. This is a live-process ownership/lifecycle problem, not a malformed JSONL or stale empty pathname.
Deleting the empty .lock file is not a safe workaround. The old process still owns an advisory lock on the already-open inode; unlinking the pathname can let a second process create and lock a different inode, violating the single-writer invariant.
What steps can reproduce the bug?
This sequence has reproduced repeatedly on this host:
- Connect to a Linux host using VS Code Remote-SSH.
- Open an integrated terminal.
- Enter
sudo suon a host configured withDefaults use_pty. - Start
codexin a project directory. - Complete a turn and wait until the TUI is idle at the prompt.
- Close/disconnect the VS Code integrated terminal or Remote-SSH client without first entering
/quitor/exitin Codex. Depending on the VS Code/sudo PTY lifecycle, the old PTY may remain or become deleted while the Codex process survives. - Open a new VS Code Remote-SSH terminal, enter the same root environment and project directory, and run
codex resumeorcodex resume <thread-id>. - Observe
already has an active writer (code -32600)indefinitely.
The conflict can be proven without editing any Codex state:
LOCK=/root/.codex/thread-writer-locks/<thread-id>.lock
stat "$LOCK" # record the inode
awk '$0 ~ /<inode>/' /proc/locks # FLOCK ADVISORY WRITE + owning PID
find /proc/<pid>/fd -lname "$LOCK" -printf '%p -> %l\n'
ps -p <pid> -o pid,ppid,lstart,tty,stat,etime,wchan,args
tail -n 1 /root/.codex/sessions/.../<rollout>.jsonl
flock -n "$LOCK" true # exits 1 while the old process owns it
Safe manual recovery currently requires identifying the exact owner through the kernel lock and open FDs, verifying its executable/cwd/TTY and completed rollout, sending targeted SIGTERM, and requiring flock -n to return 0 before resuming. Broad pkill, killall, SIGKILL, or lock-file deletion would be unsafe on a host with other live Codex sessions.
What is the expected behavior?
Please preserve the single-writer safety invariant, but make ownership lifecycle and recovery usable:
- When a standalone TUI loses its real terminal/client and has no running turn, it should shut down cleanly or release/unload the thread writer after an appropriate grace period.
- If a live process still owns the writer, the conflict should identify enough sanitized owner metadata to recover safely: surface/runtime, PID where applicable, process start time, TTY, acquisition time, last thread activity, and whether a turn is running or complete.
- Provide a supported, confirmation-gated handoff/recovery command, for example
codex resume --takeover <thread-id>orcodex sessions locks, which validates PID identity and owner activity, asks the owner to release/exit, and only acquires the writer after the original kernel lock is gone. - Offer read-only inspection or
forkas explicit alternatives when takeover is not safe. - Extend
codex doctorto report completed threads held for an unusually long time by processes with no TTY, a deleted executable, or a disconnected client.
This should not be implemented by blindly timing out or unlinking a held lock. A lease/owner record with a nonce or epoch, heartbeat/last-activity metadata, and a coordinated release path would retain fencing against late writes.
Additional information
The 0.148.0 implementation in codex-rs/thread-store/src/local/writer_lock.rs explains the current behavior:
- on
TryLockError::WouldBlock, it returns onlythread <id> already has an active writer; - the lock file contains no owner/recovery metadata;
- startup cleanup removes only lock paths that can already be locked, so it correctly cannot recover a lock held by an orphaned-but-live process.
The npm wrapper registers SIGHUP forwarding, so this report does not claim that Codex ignores a SIGHUP it actually receives. With VS Code Remote-SSH plus nested sudo use_pty, the evidence only establishes that the TUI can survive the apparent terminal close/disconnect and no supported recovery exists afterward.
Related but distinct reports:
- #37403: Desktop/Remote/CLI cross-surface writer handoff.
- #37552: intentionally opening the same thread concurrently on multiple devices.
- #37856: stale VS Code extension app-server ownership after renderer disconnect.
This report concerns a standalone CLI TUI process left behind by the VS Code Remote-SSH integrated-terminal/PTY lifecycle. No Desktop app, Remote Control daemon, or VS Code Codex extension app-server is required.
Privacy: hostnames, usernames, IP addresses, private project paths/names, provider URLs, account data, full thread IDs, conversation contents, and raw logs are intentionally omitted. Sanitized process/lock evidence can be provided if maintainers need it.
1 Comment
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action