fix(session): restore codex slash commands + recover dead resume anchors on the direct-CLI path#679
Conversation
The session-port (#609) dropped the codex-acp bridge's slash-command layer: codex_conn hardcoded an empty slash_commands catalog, so the in-conversation '/' menu showed no agent commands for codex while the home page still showed the stale bridge-era catalog persisted in agent_metadata (ELECTRON-3PX). codex's app-server has no command-discovery wire (all 112 methods audited, samples/codex-cli/0.137.0/schema-full/ClientRequest.json), and it does not interpret slash text in a prompt. The bridge instead advertised a static 6-command table and translated each name to a native op at prompt time (zed-industries/codex-acp v0.14.0 src/thread.rs:2894 builtin_commands, :3252 handle_prompt, :4195 extract_slash_command). Replicate both halves: - capabilities()/CatalogUpdated now carry the bridge's 6-command table, so the conversation REST read, the AvailableCommands push, and the agent_metadata catalog writeback all see it (home + in-session converge). - dispatch(Send) routes a leading slash to its native op: /review, /review-branch <b>, /review-commit <sha> -> review/start{target}; /compact -> thread/compact/start; /init -> the bridge's canned AGENTS.md prompt as a normal turn; /logout -> account/logout (NoTurn + Notice). Unknown names and arg-less review-branch/commit fall through verbatim as plain text, matching the bridge. Both wire ops were live-probed on codex 0.144.1 before wiring: each runs a real turn (turn/started -> items -> turn/completed), so the existing reader lifecycle applies unchanged (samples/codex-cli/0.144.1/ review_start_uncommitted.jsonl + thread_compact.jsonl + README). The probe also exposed a second dropped signal: the review VERDICT is a pre-filled agentMessage (full text on item/started, no deltas ever), and the agentMessage arm assumed delta-streaming and discarded item text - the verdict was silently lost. Emit the started edge's non-empty initial text as a MessageDelta; started.text + subsequent deltas compose identically for streamed messages, and the completed frame never re-emits. Verified live end-to-end: '/review' on a repo with a planted bug runs a real review turn and persists the verdict; the codex agent_metadata row refreshes to the 6-command catalog.
… of hanging or error-carding
A conversation whose persisted resume anchor the CLI no longer knows (e.g.
pre-port sessions upgraded onto the direct-CLI stack) failed every turn:
claude surfaced an UNKNOWN_UPSTREAM_ERROR card, and codex hung the turn
forever with the conversation locked (cron manual triggers, Sentry
ELECTRON-3Q0). The existing self-heal cleared the anchor mid-turn, but the
auto-replay rebuilt the task from the turn-start snapshot and re-resumed the
same dead session, defeating it.
- codex_conn: claim the thread/resume response; on error ("no rollout found
for thread id", live-verified samples/codex-cli/0.144.1/dead_resume.jsonl)
clear the pre-seeded binding and poison the bound-thread wait so a Send
fails fast as SessionNotFound instead of writing turn/start at a dead
threadId or timing out.
- codex_conn: a JSON-RPC error response for a pending Send is no longer a
silent drop — turn-flavored requests synthesize an is_error terminal
(shared `terminated` discipline, late real terminals absorbed); NoTurn
(/logout) surfaces a Notice. A failed dispatch also unmarks turn_in_flight.
- session_agent: a dispatch-time SessionNotFound clears the persisted anchor
directly (dispatch errors never reach the event pump's is_dead_resume_anchor)
and classifies as the retryable UserAgentSessionNotFound.
- is_unrecoverable_resume_error: recognize the codex dead-thread messages
("no rollout found for thread id", "thread not found:") next to claude's.
- turn_orchestrator/session_context: before an auto-replay attempt, re-read
ONLY the resume anchor (session_id + snapshot) into the frozen turn-start
build options, so the mid-turn anchor clear takes effect on attempt 2 and
the replay opens Fresh. Turn-scoped fields (model/skills/workspace) stay
frozen.
- send_error: classify the codex dead-thread messages as agent-session-not-
found BEFORE the generic "not found" arm misreads them as a non-retryable
provider-endpoint 404 (which blocked the replay).
- stream_relay: only a Success tip marks visible output for the replay-safety
gate; Warning/Info tips are system diagnostics (the codex mode-seed
rejection tip was making every such attempt "unsafe to replay").
Live-verified on both scenarios: codex recovers transparently (attempt 2
opens a fresh thread, reply persisted, new anchor rebound); claude replays
Fresh and rebinds a new session id.
…n direct-CLI turn apply The direct-CLI turn-time mode apply passed the persisted canonical full-access id (`agent-full-access`) straight to set_config_option, which REJECTs it against codex's live catalog — codex advertises the legacy bare token `full-access` (`:danger-full-access`->`full-access`), not the canonical id. cron forces full-auto (`agent-full-access`) on every scheduled run, so the turn hard-failed with "mode 'agent-full-access' is not one of the available modes" before the send, surfaced as UNKNOWN_UPSTREAM_ERROR (ELECTRON-3Q0). Align the canonical id to the live catalog before apply, mirroring the AcpAgentManager reconcile path (normalize_requested_mode_for_available_values): downgrade only the canonical full-access id, only when the live catalog lacks it but carries the legacy token. A catalog with no full-access tier leaves the value so set_config_option still REJECTs (never silently pick a weaker mode); non-codex backends and non-full-access modes pass through untouched.
|
Added on top of this PR (commit The dead-anchor recovery here handles a rejected
Fix: align the canonical id to the live catalog before apply, mirroring the AcpAgentManager reconcile path ( Scope: covers the Added 6 unit tests;
|
🤖 I have created a release *beep* *boop* --- ## [0.1.52](v0.1.51...v0.1.52) (2026-07-24) ### Features * **project:** wire project-bind side branch into owner creation ([#676](#676)) ([300c833](300c833)) ### Bug Fixes * **agent:** unify CLI probe pipeline with classified failures and adaptive slow-probe recheck ([#678](#678)) ([67945d2](67945d2)) * **channel:** quiet WeChat poll log noise with state-transition logging and exponential backoff ([#683](#683)) ([ea4da55](ea4da55)) * **process:** allow whitespace in workspace cwd segments ([#410](#410) parity) ([#674](#674)) ([c344b02](c344b02)) * **session:** restore codex slash commands + recover dead resume anchors on the direct-CLI path ([#679](#679)) ([7b60611](7b60611)) * **team:** converge system/lifecycle wakes into a team run ([#680](#680)) ([38facd7](38facd7)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Two session-layer fixes for the direct-CLI (session-port) stack, from Sentry user feedback:
1. codex in-session slash commands were gone (ELECTRON-3PX)
The port dropped the codex-acp bridge's private static command layer, so the in-session
/menu advertised nothing and slash text was sent to codex as plain prompt text (codex does not interpret it)./review,/review-branch,/review-commit,/init,/compact,/logout) through capabilities + CatalogUpdated, so the REST read, the frontend push, and the agent_metadata writeback all see it.review/start{target},thread/compact/start, canned init prompt as a normal turn,account/logout(NoTurn + Notice). Unknown names and argument-less/review-branch|commitfall through verbatim (bridge parity).agentMessage(review verdicts carry full text onitem/startedwith no deltas) was discarded by the delta-only arm — the verdict never persisted.Wire lifecycles live-verified on codex 0.144.1 (
review/start,thread/compact/start); e2e:/reviewcaught a planted bug and the verdict persisted.2. A dead resume anchor hung codex forever / error-carded claude (ELECTRON-3Q0)
A conversation whose persisted resume anchor the CLI no longer knows (e.g. pre-port sessions after upgrade) failed every turn — cron manual triggers surfaced
UNKNOWN_UPSTREAM_ERRORon claude, and codex left the turn permanently Running with the conversation locked. The existing dead-anchor self-heal fired but was structurally defeated: the auto-replay rebuilt the task from the frozen turn-start snapshot and re-resumed the same dead session.thread/resumeresponse; on error ("no rollout found for thread id", live-captured) clear the pre-seeded binding and fail later sends fast asSessionNotFoundinstead of writingturn/startat a dead threadId.is_errorterminal (single-terminal discipline preserved),/logoutsurfaces a Notice.SessionNotFoundclears the persisted anchor directly and classifies as the retryableUserAgentSessionNotFound.Live-verified end to end: with a planted dead anchor, a codex cron trigger now recovers transparently (attempt 2 opens a fresh thread, the reply persists, a real anchor rebinds — previously a permanent hang); claude replays Fresh and rebinds a new session id.
Full pre-push gate: 7442 tests passed.