Skip to content

[bug] A chat turn is bound to the request — navigating away can lose the reply while its tool side effects persist #251

Description

@serge-ivo

The question

Send a chat message (typed or spoken), then navigate away before the reply lands. Does the turn
still complete?

Finding: a Loop is durable, a plain chat turn is not

Loops are safe. POST /loop writes an agent_loop_runs row and a server-side driver runs it.
InstanceDetail.tsx:292 states the design outright: "The console used to BE the loop… That died
with the tab and could not be budgeted, because the platform did not drive it. Now the workflow
drives and this only reports"
, and "the run itself is durable and carries on regardless of
whether this tab can see it."
Correct, and exactly right.

A plain chat turn has none of that. AgentDO.handleChat awaits this.think(...) and returns
the reply inline. There is no ctx.waitUntil, no blockConcurrencyWhile, nowhere in
agent-do.ts
— the whole turn hangs off the inbound request. Nothing keeps it alive if the
client goes away.

Why the failure is worse than "you lose the reply"

The assistant message is only appended after think() returns. But tools execute during it,
and their side effects are already committed — memory writes, created tasks, a filed GitHub issue,
a started pipeline, a start_work handoff.

So an interrupted turn can leave: work done, nothing in the transcript. Come back and the
conversation shows your message and no reply, while the agent has in fact acted. The next turn is
then built from a history that contradicts what happened — and the honesty rules in the system
prompt cannot help, because the record itself is wrong.

This is the same class the codebase already fixed once for partial failures: #24 added
partialToolLog so a late-round error still surfaces the side effects that landed. That guard runs
inside the catch. A cancelled request never reaches it.

Worth checking before fixing

Confirm empirically whether Cloudflare actually cancels the DO's in-flight work when the client
disconnects, or whether it runs to completion. The absence of waitUntil means it is unprotected
either way, but the fix differs: if it already completes, this is a documentation and
status-reporting matter (#252). If it is cancelled, the turn needs to survive the request.

Reproduce: send a message that triggers a slow tool round, close the tab immediately, then re-read
the thread via GET /v1/instances/:id/messages and the activity log. If activity shows tool calls
with no assistant message, it is cancelling.

If it is cancelling

Detach the turn from the request. The platform already has the pattern — a durable run row plus a
driver (agent_loop_runs, the Loop). A chat turn could take the same shape, or at minimum be
wrapped so the DO finishes and persists the reply even after the client is gone.

Verification

  • Send → navigate away mid-turn → the assistant message is present on return.
  • Tool side effects and the transcript never disagree.

Activity

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions