Skip to content

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

Closed

Description

@Stener1

What happens

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.

🤖 Generated with Claude Code

Activity

  1. Stener1 commented on Oct 7, 2026

    @Stener1
    ContributorAuthor

    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.

    🤖 Generated with Claude Code

  2. Stener1 commented on Oct 9, 2026

    @Stener1
    ContributorAuthor

    Two more findings from a longer Codex session (Codex 0.160.1, daemon off):

    1. 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.

    2. 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

  3. Stener1 commented on Oct 9, 2026

    @Stener1
    ContributorAuthor

    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: notify agent-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.

    Happy to send a PR for this if it's welcome.

    🤖 Generated with Claude Code

  4. Stener1 commented on Oct 16, 2026

    @Stener1
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions