You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Codex sessions read as Done while working: Codex's shared app-server daemon runs every session's hooks with the first session's BERTH_SESSION #8
With a recent Codex CLI (seen with 0.160.1 on Linux), a Codex session started by Berth can sit in Done while Codex is visibly working, and its hook events show up on a different session.
Why
Newer Codex runs one shared app-server daemon per machine (daemon_auto_start, stable and on by default); every Codex TUI attaches to it, and Codex runs hooks from that daemon. The daemon is started by whichever Codex session comes first, so it inherits that session's environment, including BERTH_SESSION and BERTH_AGENT. From then on, every Codex session's hooks run berth hook codex … with the first session's BERTH_SESSION:
$ for p in $(pgrep -f codex); do …environ… done
… app-server-daemon … BERTH_SESSION=codex-hooks ← started by the first session (since exited)
… node …/codex "So i want…" BERTH_SESSION=berthd-codex-15uq
Berth's journal then shows the new session with only screen-sourced events (agent.waiting / agent.finished, source: screen), while agent.started / agent.finished from Codex land on the old, exited session. The screen poller ends the turn, so the dashboard, phone and notifications say Done while Codex says "Working (2m 13s)".
Possible fixes
Start Codex with --no-daemon in the built-in codex preset (or set daemon_auto_start = false in the integration), so each session runs its own app server and hooks; or
map Codex hook events to sessions by Codex's own thread id (the hook payload has it, and berthd already ties sessions to their Codex rollout), instead of trusting BERTH_SESSION from the hook's environment.
Workaround today: [features] daemon_auto_start = false in ~/.codex/config.toml on the box, then stop the running daemon.
A second Codex behaviour that leads to the same symptom, seen with the daemon turned off (Codex 0.160.1, Linux):
Codex can end a turn while a command keeps running in the background ("1 background terminal running") and then carry on by itself when that command finishes. Its notify fires agent-turn-complete at the turn's end, but no hook fires when it resumes, since there is no new prompt. Berth records the turn as finished, and because the screen poller only watches sessions with an open turn, nothing moves it back to running while the screen says Working (1m 11s • esc to interrupt) · 1 background terminal running.
Journal for that session:
11:48:00 agent.started codex
11:48:04 agent.finished codex via: notify ← Codex keeps working after this
Possible directions: keep screen-polling a Codex session for a while after a turn ends when its screen shows Codex's Working (…) line, or treat that line as a start the way approval prompts are treated as waits.
Two more findings from a longer Codex session (Codex 0.160.1, daemon off):
Codex reported its turn complete 6 seconds in and then worked for a long time. Journal: agent.started codex at 13:46:45, agent.finished codex via: notify at 13:46:51; after that, nothing from Codex while it ran many commands with background terminals open. The session showed as finished (Idle with turn check) the whole time.
Codex's question box isn't seen. Later the agent asked the user something through Codex's input prompt (the panel ending Type your answer · enter submit · ^] skip, under "Queued follow-up inputs"). No hook fired, the screen regex doesn't match that panel, and the screen poller isn't watching the session (no open turn), so it never showed as Needs you.
I'm looking into whether Codex's other hook events (PreToolUse, PostToolUse) cover both cases and will report back here.
Follow-up with the causes, from a hook probe on Codex 0.160.1 (field names and tool names only were logged):
1. Codex's notify fires at the end of each assistant message, not the end of the turn. In one turn: notifyagent-turn-complete at 13:58:09, then PreToolUse (Bash) at 13:58:10, then the turn's real end, the Stop hook, at 13:58:15. Berth maps both notify and Stop to agent.finished, so a long turn reads as finished after its first message. That is the "turn complete 6 seconds in" case above.
2. Codex has PreToolUse / PostToolUse hooks, and Berth doesn't install them. The Codex adapter already maps PostToolUse to agent.started with signal: tool, but integrations install codex only writes SessionStart, UserPromptSubmit, PermissionRequest and Stop.
3. Codex's question box is a tool, and PreToolUse sees it. Asking the user fired PreToolUse with tool_name: "request_user_input_async" and a questions field in tool_input. That is a precise "needs you" signal, with no screen reading.
Verified locally: with Berth's notify line removed from ~/.codex/config.toml (Codex has hooks, so Stop is enough) and PostToolUse installed, a 45-second turn with three commands stayed running throughout and finished once, at Stop.
Suggested change (small):
integrations install codex: add PreToolUse and PostToolUse; don't add notify when Codex supports hooks (or keep it only for versions without them).
Codex adapter: PreToolUse with a tool_name starting request_user_input → agent.waiting (reason: question); other PreToolUse → agent.started (signal: tool), like PostToolUse.
Optionally ignore notify-sourced agent.finished for a session whose hooks have been seen.
Fixed in two parts: #71 (Codex's notify firing per message, and its question box) and #131 (the shared daemon: Shipyard starts Codex with --no-daemon when it has the flag, and the ledger no longer takes a Codex hook's session name on trust). Reproduced on Codex 0.163.0 before the fix; details in #131.
Activity
Stener1 commentedon Oct 7, 2026
A second Codex behaviour that leads to the same symptom, seen with the daemon turned off (Codex 0.160.1, Linux):
Codex can end a turn while a command keeps running in the background ("1 background terminal running") and then carry on by itself when that command finishes. Its
notifyfiresagent-turn-completeat the turn's end, but no hook fires when it resumes, since there is no new prompt. Berth records the turn as finished, and because the screen poller only watches sessions with an open turn, nothing moves it back to running while the screen saysWorking (1m 11s • esc to interrupt) · 1 background terminal running.Journal for that session:
Possible directions: keep screen-polling a Codex session for a while after a turn ends when its screen shows Codex's
Working (…)line, or treat that line as a start the way approval prompts are treated as waits.🤖 Generated with Claude Code
Stener1 commentedon Oct 9, 2026
Two more findings from a longer Codex session (Codex 0.160.1, daemon off):
Codex reported its turn complete 6 seconds in and then worked for a long time. Journal:
agent.started codexat 13:46:45,agent.finished codex via: notifyat 13:46:51; after that, nothing from Codex while it ran many commands with background terminals open. The session showed as finished (Idle with turn check) the whole time.Codex's question box isn't seen. Later the agent asked the user something through Codex's input prompt (the panel ending
Type your answer · enter submit · ^] skip, under "Queued follow-up inputs"). No hook fired, the screen regex doesn't match that panel, and the screen poller isn't watching the session (no open turn), so it never showed as Needs you.I'm looking into whether Codex's other hook events (
PreToolUse,PostToolUse) cover both cases and will report back here.🤖 Generated with Claude Code
Stener1 commentedon Oct 9, 2026
Follow-up with the causes, from a hook probe on Codex 0.160.1 (field names and tool names only were logged):
1. Codex's
notifyfires at the end of each assistant message, not the end of the turn. In one turn:notifyagent-turn-completeat 13:58:09, thenPreToolUse(Bash) at 13:58:10, then the turn's real end, theStophook, at 13:58:15. Berth maps bothnotifyandStoptoagent.finished, so a long turn reads as finished after its first message. That is the "turn complete 6 seconds in" case above.2. Codex has
PreToolUse/PostToolUsehooks, and Berth doesn't install them. The Codex adapter already mapsPostToolUsetoagent.startedwithsignal: tool, butintegrations install codexonly writesSessionStart,UserPromptSubmit,PermissionRequestandStop.3. Codex's question box is a tool, and
PreToolUsesees it. Asking the user firedPreToolUsewithtool_name: "request_user_input_async"and aquestionsfield intool_input. That is a precise "needs you" signal, with no screen reading.Verified locally: with Berth's
notifyline removed from~/.codex/config.toml(Codex has hooks, soStopis enough) andPostToolUseinstalled, a 45-second turn with three commands stayed running throughout and finished once, atStop.Suggested change (small):
integrations install codex: addPreToolUseandPostToolUse; don't addnotifywhen Codex supports hooks (or keep it only for versions without them).PreToolUsewith atool_namestartingrequest_user_input→agent.waiting(reason: question); otherPreToolUse→agent.started(signal: tool), likePostToolUse.notify-sourcedagent.finishedfor a session whose hooks have been seen.Happy to send a PR for this if it's welcome.
🤖 Generated with Claude Code
fix(codex): a Codex session's hooks reach it even with Codex's shared…
Stener1 commentedon Oct 16, 2026
Fixed in two parts: #71 (Codex's
notifyfiring per message, and its question box) and #131 (the shared daemon: Shipyard starts Codex with--no-daemonwhen it has the flag, and the ledger no longer takes a Codex hook's session name on trust). Reproduced on Codex 0.163.0 before the fix; details in #131.