Skip to content

Codex primary supervision goes dark when an API-error or interrupted turn bypasses Stop #2955

Description

@stanzhang

Summary

A Codex-primary Firstmate home can remain silently unsupervised indefinitely when its bounded foreground watcher checkpoint has returned and the handling turn ends in an API/model error or interruption before starting the next checkpoint.

Codex 0.149.0 runs the public Stop hook only on clean no-follow-up completion. Its TurnAborted and general error branches bypass that call. Firstmate registers its turn-end supervision guard only under Stop.

No Stop means no guard, no replacement checkpoint, and no alarm.

Evidence

Installed harness:

codex-cli 0.149.0

Firstmate's protocol intentionally ends a quiet watcher checkpoint every 180 seconds and assigns the model to drain and start the next one:

- `docs/supervision-protocols/codex.md:3-13`
- `bin/fm-watch-checkpoint.sh:76-105`

Tracked Codex hooks register the supervision guard only for `Stop`:

- `.codex/hooks.json:31-41`

The exact installed Codex tag has one public Stop invocation on the clean branch:

- `openai/codex@rust-v0.149.0 codex-rs/core/src/session/turn.rs:502-510`

The abnormal branches bypass it:

- `TurnAborted`: `session/turn.rs:555-557`
- invalid request error: `session/turn.rs:558-574`
- general API/model error: `session/turn.rs:576-585`
- hard abort terminal event: `codex-rs/core/src/tasks/mod.rs:900-962`

Codex's public hook catalog contains no `StopFailure`, `TurnError`, or `TurnAborted` event:

- `codex-rs/config/src/hook_config.rs:35-58,122-135`
- `codex-rs/hooks/src/lib.rs:22-35`

## Mechanism

1. The watcher is one-shot and a foreground checkpoint bounds it to 180 seconds.
2. On quiet timeout or actionable close, no watcher remains while Codex handles the return.
3. The protocol tells the model to start the next checkpoint.
4. If that turn ends through Codex's error/abort path first, the public Stop hook is not invoked.
5. `fm-turnend-guard.sh` therefore cannot block the blind turn or print its warning.
6. `.watcher-down` is durable but passive; without another watcher or model turn, nothing presents it.
7. An alive idle secondmate endpoint is excluded from parent pane-stale alarms, and the parent skips foreign wake-queue inspection for remote secondmates.

## Trigger, masking condition, and symptom

- **Trigger:** checkpoint watcher exits, then the Codex primary turn errors/aborts before issuing the next checkpoint.
- **Masking condition:** only `Stop` owns the guard; abnormal terminal paths skip it; no independent process watches the watcher.
- **Operator symptom:** the Codex pane contains the underlying error, but there is no Firstmate blind-supervision banner or bounded central alert. Remote inner workers can remain unsupervised until a later input or restart.

## Impact

This directly affects a Codex secondmate's supervision of its own crewmates. The main primary may remain healthy while failing to detect the remote secondmate's inner watcher loss. Durable queue custody prevents data loss, but blocked or completed work is neither processed nor reported for an unbounded unattended interval.

## Suggested fix

Give Codex primary homes an external continuity owner that does not depend on another model turn. Within `FM_GUARD_GRACE`, it must either prove an identity-matched watcher exists or send one rate-limited pane-independent active alert.

For remote secondmates, add an authenticated parent-side check of inner watcher health when the endpoint remains alive.

If Codex later exposes blocking abnormal-turn hooks, register all terminal statuses and retain the external alarm as defense in depth.

Add an isolated live regression that:

1. lets one foreground checkpoint end;
2. induces a Codex API-error terminal turn before re-checkpointing;
3. sends no later user input;
4. proves a watcher or active alert exists within the bound.

## Workaround

There is no `.codex/hooks.json`-only workaround in Codex 0.149.0 because no abnormal-turn event is exposed. `SessionEnd` and legacy `notify` do not cover this path. A separately owned local watchdog could mitigate it, but that is a control-plane feature rather than a hook override.

## Commands and evidence collected

All diagnostic inspection was read-only. No watcher, live session, or project code was changed. Outside the report, only the task's required captain-hold completion bookkeeping and final sparse status line are permitted by the launch brief's completion gate.

```sh
git rev-parse HEAD
codex --version
jq -r '.hooks | keys[]' .codex/hooks.json
jq -r '.hooks | keys[]' /Users/sz/.codex/hooks.json
gh-axi issue view 2940 --repo kunchenguid/firstmate --full
gh-axi repo clone openai/codex .scratch/codex
git -C .scratch/codex switch --detach rust-v0.149.0
git -C .scratch/codex rev-parse HEAD
rg -n 'run_turn_stop_hooks|run_legacy_after_agent_hook' .scratch/codex/codex-rs/core/src/session/turn.rs
rg -n 'StopFailure|TurnError|TurnAborted|SessionEnd|"Stop"' .codex/hooks.json docs bin tests

Key outputs:

```text
8fa0505e48a155da78a9aeeb50911719dc558710
codex-cli 0.149.0
Codex source: 758ef40f50c1 (rust-v0.149.0)

tracked hook keys:
PreToolUse
SessionStart
Stop

global hook keys:
SessionStart

## What would settle the remaining runtime questions

Use an isolated, disposable Firstmate home with no shared live tasks, an installed Codex 0.149.0 binary, and a controlled failure injector or proxy. Capture the Codex hook log, TUI transcript, desktop notifications, watcher lock/beacon, `.watcher-down`, and any parent-side alert through at least `FM_GUARD_GRACE`, while deliberately sending no later prompt. Run clean completion as the control. This is not needed to establish the source-level hook gap, but it would establish exact operator presentation and timeout classification end to end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-prTriage: real bug or VISION-aligned feature, open for a PR

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions