Skip to content

feat(threads): show where a thread continued after a server restart - #515

Merged
incognitojam merged 5 commits into
mainfrom
styal/verify-thread-resumed-after-restart
Sep 29, 2026
Merged

incognitojam merged 5 commits into
mainfrom
styal/verify-thread-resumed-after-restart

Conversation

@incognitojam

@incognitojam incognitojam commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Note

Threads that the server continues after a restart now show Continued after server restart at the point the agent picked up again, and the work before and after the restart folds into one response. It's a new activity kind, so no migration is needed.

When the server restarts with a thread mid-turn, it resumes the provider session and sends a continuation prompt ("Continue where you left off.", or the background-work prompt). That prompt isn't a thread message, so the conversation showed no sign of the restart. The transcript jumped from the last output before the restart to the agent's next message, and anything the restart stopped (background shells, monitors, subagents) had no visible cause. A prompted continuation also runs in a new provider turn, so one request showed as two "Worked for" groups, and the first ended on a progress update that read like a final answer.

What changed

  • Server: reconcileProviderSessions records a server-restart.continued activity once the continuation has been sent. The row is attached to the turn that sendTurn returns, which is the turn that carries the continued work. Its timestamp is taken just before sending, so it sorts ahead of the agent's first output. If sending fails, no row is recorded and the thread shows the existing error instead.
    • A thread whose turn was running gets "Continued after server restart".
    • A settled thread whose background work was stopped gets "Asked to restart background work after server restart", recorded as a separate server-restart.background-continued kind.
    • This applies to every provider, including those that continue without a prompt.
  • Contracts: adds the two activity kinds.
  • Client runtime: adds isStandaloneTimelineActivityKind, the grouping rule web and mobile share, which covers context compaction and the restart rows, deriveContinuedTurnRoots, which both clients use to find the turn a continuation belongs to, and restartDowntimeMs, which both use to leave the restart gap out of the response's duration. Each standalone kind names how clients draw it, so a new kind has to choose between the divider and the restart note.
  • Web and mobile: the restart shows as a note in the turn's work, styled like a work-log row. Like the compaction row, it is never merged into a group of work rows, and it stays visible when it is the only thing a folded turn would hide. Compaction keeps its divider because it breaks the agent's context; a restart continues the same conversation, so it isn't drawn as a break.
  • Folding: a turn that opens with a server-restart.continued note, with no user message since the previous turn, folds together with that turn: one "Worked for" row covering both, and only the final answer left visible. That duration runs from the user's message to the final answer, less the time between the interrupted turn's last entry and the restart note, so a long outage doesn't count as work. The interrupted turn's last message stays inside the fold. Background-work continuations stay a separate response, because the earlier turn had already finished and its answer should stay visible.
  • Storage: no migration. Activity kinds are free strings on the existing thread.activity-appended event.
  • Docs and ledger: adds a line to the updating guide, a highlight in the styal-differences overview, and a fork feature ledger entry.
Before After
Before: one request shows as two groups, Worked for 1m and Worked for 2m 1s, with no sign of the restart After: one Worked for 3m 10s group followed by the final answer

Expanded, the note sits between the last command before the restart and the agent's first message after it:

After, expanded: Continued after server restart between the pre-restart command and the resumed message

Validation

  • Server: serverRuntimeStartup.reconcile.test.ts asserts that each continued thread records exactly one row, attached to the turn sendTurn returned. It covers the marked-update and opt-in-restart paths, the promptless (Codex) and prompted (Claude) continuations, and the background-work continuation with its label.

  • Web: MessagesTimeline.logic.test.ts covers a lone restart row and a lone compaction row staying visible outside folded work, a continued turn folding into the turn it continued (one fold, duration without the restart gap, note in order when expanded), and a background-work continuation staying separate. The merge tests fail with the merge disabled.

  • Mobile: threadActivity.test.ts covers the same merge and background cases. Running mobile's feed derivation on the copied thread's real messages and activities gives one fold for the request, matching web.

  • Browser: checked in the web app on a copy of a real Claude thread that the server continued after a restart. The event was inserted at the continuation time, and every copied resume cursor was removed first so no live provider session could be resumed. The request collapses into one "Worked for 3m 10s" row followed by the final answer (3m 28s from message to answer, less 18s before the restart note), and expanding it shows the note between the last pre-restart command and the resumed message. Removing the event gives the before shot.

  • Real restarts: on an isolated dev server with "Continue threads after restarts" on, I restarted the server mid-turn three times on a Claude thread and once on a Codex thread (which continues without a prompt). Each restart recorded exactly one note, on the turn sendTurn returned, and every later event of that turn used the same turn ID. The note sorted first in its turn, and web and Android each showed one fold per request. For background work, I wrote the marker a self-update writes and restarted the server: the background note landed on the new turn and showed as a separate response on web and Android.

Not verified

  • Older clients: I expect them to show the new kinds as a generic work row.
  • Real self-update: a dev server can't run one, so the background-work marker was written by hand.

Written by an agent (Claude Code, claude-opus-5-5).

@incognitojam
incognitojam force-pushed the styal/verify-thread-resumed-after-restart branch from 4ea0a31 to d5fcb84 Compare September 28, 2026 17:05
@incognitojam
incognitojam marked this pull request as ready for review September 28, 2026 17:06
@incognitojam incognitojam added the preview:web Deploy a hosted-web preview to Cloudflare Workers for this PR on every push. label Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Web preview

The preview was removed because this PR closed or the preview label was removed.

When the server continues a thread's work after a restart, it now records
a server-restart.continued activity once the continuation is sent. Web and
mobile show it as a note in the turn's work, kept out of grouped work rows
so a folded turn does not hide it.
The note used the interrupted turn, or no turn for background work, but a
prompted continuation starts a new turn. Use the turn ID sendTurn returns so
the note sits on the turn that carries the continued output. Relabel the
background note to say the agent was asked to restart its background work, and
move the grouping rule for standalone rows from contracts to client-runtime.
A prompted continuation after a server restart runs in a new provider turn, so
one request showed as two "Worked for" groups, and the first ended on a progress
update that read like a final answer. Both clients now treat a turn opened by a
restart note, with no user message since the previous turn, as part of that
turn: one fold, one duration, one final answer. Background-work continuations
get their own activity kind and stay separate, since the earlier turn had
already finished.
…ration

A response folded across a server restart measured from the user's message
to the final answer, so the time the server was down counted as work. It now
subtracts the gap between the interrupted turn's last entry and the turn that
continued it.

Each standalone activity kind also names how clients draw it, so a new kind
no longer falls through to the restart note's icon.
@incognitojam
incognitojam force-pushed the styal/verify-thread-resumed-after-restart branch from d8bdc8a to 1b98667 Compare September 29, 2026 10:40
@incognitojam
incognitojam merged commit e9d56a4 into main Sep 29, 2026
24 checks passed
@incognitojam
incognitojam deleted the styal/verify-thread-resumed-after-restart branch September 29, 2026 11:06
incognitojam pushed a commit that referenced this pull request Oct 3, 2026
…otgg#12147)

(cherry picked from commit 22539f2)

Fork adaptation: Thought and tool activity groups on web and mobile exclude every standalone row the fork defines (context compaction and server-restart notes), not only compaction, so a restart note keeps its own row; mobile uses the fork's standalone-row check in place of the compaction check that fork #515 replaced. The fork's fold summary types stay beside upstream's activity group types.

Upstream-PR: 12147
Fork-Feature: server-restart-continuation-note, inspectable-tool-activity

This branch was previously deployed

1 inactive deployment
web-preview — 1b986675 Deployed Sep 29, 2026 by incognitojam via Remove web preview #163
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:web Deploy a hosted-web preview to Cloudflare Workers for this PR on every push. size:L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant