What variant of Codex are you using?
CLI 0.166.0 (macOS, managed app-server daemon with default daemon_auto_start)
What feature would you like to see?
Summary
When the managed daemon is running, nothing on disk says which client process has a thread open. Tools that show agent status per terminal (terminal workspaces, tmux status lines, session monitors) can no longer work out which terminal a thread belongs to.
I'd like each client to keep a small registry file for itself, for example ~/.codex/clients/<pid>.json:
{ "pid": 34100, "threadId": "01a18e88-6f22-7a92-97a1-f8d9c9e3aa96", "cwd": "/path/to/repo", "client": "codex-tui" }The client writes it when it attaches to a thread, rewrites it when it switches threads (/new, /resume, fork), and deletes it on exit.
Current behavior
Before the daemon, the TUI process held thread-writer-locks/<thread-id>.lock and the rollout file itself. A tool could find the TUI's pid by checking who held the lock, then walk its parent processes to the terminal.
With the daemon (TUI path: startup_orchestration.rs#L618), the daemon holds the writer lock and the rollout for every thread. The TUI only holds a socket to the daemon. On a live session:
$ lsof -t ~/.codex/thread-writer-locks/01a18e88-6f22-7a92-97a1-f8d9c9e3aa96.lock
8642 # codex app-server --listen unix:// ... --managed-daemon (no tty, parent: daemon pid-update-loop)
$ ps -o pid,ppid,tty,command -p 34100
34100 23337 ttys104 codex # the TUI the user is typing into; holds no thread filesEvery thread reports the same pid. The daemon process is also first spawned as a child of whichever TUI started it, so until that TUI exits, all threads appear to belong to that TUI's terminal. That's the wrong answer for every other terminal.
The existing APIs don't fill the gap:
initializesends onlyClientInfo { name, title, version }(v1.rs#L37).thread/loaded/listreturns thread ids only (thread.rs#L1703).logs_2.sqliterecords thread ids under the daemon'sprocess_uuid, not the client's.
Why this matters
- Per-terminal status. A terminal app can show "working / waiting / done" on the tab that's running the agent. Today the status from one thread lands on another terminal's tab.
- Writer conflicts. Several open issues come down to "which client has this thread?" (#44449, #56751, #54738, #56598). A registry entry gives the error message and the user a pid and a cwd to point at.
- It keeps working as more clients attach. With multi-backend TUI sessions, attached threads and Remote Control, a thread can have several clients at once. One file per client process handles that without changing what a thread owns.
Why the existing alternatives don't cover this
--no-daemon/features.daemon_auto_start = falserestores the old behavior, but it means asking every user to opt out of the daemon. Tools that observe Codex shouldn't have to change how it runs.- The terminal title includes the thread name by default, but titles are configurable, change while a turn runs, and aren't unique.
- Matching on cwd fails as soon as two TUIs run in the same repo, which is common with worktrees.
Suggested behavior
- Additive and passive. No new lock, nothing for the daemon to wait on, no change to
thread-writer-locks. - A stale file from a crashed client is harmless: readers check that the pid is alive and that it's a Codex process.
- Same shape for any local client (TUI,
codex exec, IDE extension) so tools have one place to look.
If a file isn't a good fit, an experimental field on thread/loaded/list with the pid the daemon sees on each connection (LOCAL_PEERPID / SO_PEERCRED) would also work for us. The file is easier for passive tools because they don't need to connect to the daemon.
Additional information
Related: #48195 (making daemon auto-start opt-in) would also avoid this, but only for users who turn the daemon off.
Repro:
- Open two terminals. Run
codexin each with the daemon enabled (default). - Start a conversation in each.
lsof -tboththread-writer-locks/*.lockfiles. Both return the daemon pid, andps -o ppid=on it leads back to the first terminal's TUI.- Quit the first TUI. The daemon's parent becomes
launchd(pid 1), and neither thread can be traced to a terminal.
Activity
0No activity yet.