Skip to content

feat(telegram-codex): survive a restart with cause-aware recovery context (DIVE-3965) - #69

Merged
5dive-bot merged 1 commit into
mainfrom
dive-3965-codex-continuity
Sep 13, 2026
Merged

feat(telegram-codex): survive a restart with cause-aware recovery context (DIVE-3965)#69
5dive-bot merged 1 commit into
mainfrom
dive-3965-codex-continuity

Conversation

@5dive-bot

Copy link
Copy Markdown
Collaborator

What this is

P06 of the Codex channels series (DIVE-3965), plugin half. The CLI half is 5dive-ai/5dive#PENDING.

The dispatcher already resumed its Codex thread across a restart. What it did not do was
say what the restart had COST — and three different costs were collapsed into one sentence
asking the person to resend.

What changed

  • A clean stop and a crash are different sentences. markCleanShutdown() persists the
    intent on the way out, synchronously, and initialize() clears it. The ABSENCE of that
    flag is the load-bearing half: a SIGKILL, an OOM or a host reboot never gets to write it,
    so "restarted" stops being what the chat is told about a crash.
  • A stale thread is recorded, not silently replaced. A thread/resume the app-server
    rejects already fell back to thread/start; now the reason is captured and carried.
  • A one-line recovery context rides the NEXT real turn, prepended as a separate text
    input item. The interrupted message is never resubmitted — replaying it would duplicate
    work Codex may already have done before it died, which is what "without duplicating
    turns" means. It is consumed only once the turn it rode on actually exists, so a
    turn/start that threw keeps it for the retry.
  • The interrupted-turn notice is sent exactly once per interrupted turn.

Tests

test/codex-dispatcher.test.ts, six new arms: clean-vs-crash wording, markCleanShutdown
persistence, recovery-context injection with an explicit "exactly one turn/start, and it is
NOT the interrupted message" assertion, stale-thread recovery, the once-only notice, and
the shape of the recovery line itself.

bun test at this head: 1204 pass / 0 fail across 47 files.

🤖 Generated with Claude Code

…text, not an apology (DIVE-3965)

The dispatcher already resumed its Codex thread, but everything a restart had cost was collapsed into one sentence asking the person to resend. Three facts were missing from it.

A clean stop and a crash now read differently: markCleanShutdown() persists the intent on the way out, and its ABSENCE is the load-bearing half — a SIGKILL, an OOM or a host reboot never gets to write it.

A thread the app-server can no longer resume is now recorded rather than silently replaced, so the person is not the only one who notices the conversation is gone.

Both facts become a one-line recovery context that rides the NEXT real turn's input. The interrupted message is never resubmitted: replaying it would duplicate work Codex may already have done before it died. The context is consumed only once the turn it rode on exists, so a failed turn/start keeps it for the retry.

The interrupted-turn notice is sent exactly once per interrupted turn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants