Skip to content

fix(app): drain a queue whose agent stopped running unobserved - #2994

Closed
lxsolutions wants to merge 1 commit into
getpaseo:mainfrom
lxsolutions:fix/2991-drain-stranded-queue
Closed

lxsolutions wants to merge 1 commit into
getpaseo:mainfrom
lxsolutions:fix/2991-drain-stranded-queue

Conversation

@lxsolutions

Copy link
Copy Markdown

Linked issue

Related to #2991

Deliberately not Closes — see Not covered below. This fixes a real path that produces exactly the reported symptom, but #2991 has no logs, so I can't prove it is the path the reporter hit.

Type of change

  • Bug fix
  • New feature
  • Enhancement
  • Refactor
  • Docs

Reasoning

A queued message is only sent by onAgentStoppedRunning, which fires from the directory replica on a running -> not-running transition. reconcileAgentDirectory computes that set by comparing the incoming snapshot against the previously known status:

if (statuses.get(entry.agent.id) === "running" && entry.agent.status !== "running") {
  stoppedRunningAgentIds.add(entry.agent.id);
}

So the send depends on this client having personally watched the agent go from running to idle. If the turn ended while the app was disconnected, backgrounded, or had not yet loaded the directory, the agent is simply already idle by the time the app catches up. There is no transition left to observe, nothing calls drainQueuedAgentMessage, and the message sits in the queue until the user presses send by hand — which is the reported behaviour.

The queue is durable state, but the only thing that moves it is a one-shot in-memory edge. This adds the missing level check: once the directory bootstrap has settled and the app's view of agent status is current, re-check the queues and send for any agent holding one that is not running.

Goals

  • A queue whose agent stopped running unobserved goes out once the app reconnects.
  • An agent that is still running keeps its queue untouched.
  • The existing edge trigger and drain serialization are unchanged.

Non-goals

  • Not restructuring the queue into the durable claim/dispatch state machine suggested in the issue thread. That is the larger design; this is the missing trigger under the existing one.
  • Not adding retry for a drain that fails mid-send (result.status === "failed" still only logs). Worth doing, but it is a separate failure mode from the one here.
  • No protocol change.

QA

$ npx vitest run src/runtime/host-runtime.test.ts
 Test Files  1 passed (1)
      Tests  67 passed (67)

67 = the 65 that existed plus the 2 added here:

  • drains a queue whose agent stopped running unobserved — agent seeded as already idle with a non-empty queue, no transition ever observed. Fails on main (nothing sends), passes here.
  • leaves a queue alone while its agent is still running — the guard in the other direction, so the sweep can't send into a live turn.
$ npm run lint -- packages/app/src/runtime/host-runtime.ts packages/app/src/runtime/host-runtime.test.ts
Found 0 warnings and 0 errors.

npm run typecheck --workspace packages/app reports one error, in src/components/draggable-list.native.tsx(122,7) — a react-native-draggable-flatlist prop typing issue. It reproduces identically on a clean checkout of main with these changes stashed, so it is pre-existing and unrelated. No new type errors.

Platforms: logic change in HostRuntimeStore, no UI. Covered by the app unit suite, which is platform-agnostic. Not separately exercised on iOS, Android, web, or Electron.

Not covered:

  • The reporter's environment is Windows 11 / Electron / Paseo 0.2.5, and bug: messages that are queued not getting triggered or launched #2991 has no logs and no provider. I have not reproduced their session; I found a code path that strands a queue exactly as described and covered it. If the maintainer wants this closed as the fix, the reporter should confirm against a build with this change.
  • The new sweep's logic is unit-tested directly. The one-line call site wiring it into the directory bootstrap is covered by typecheck and lint but not by an integration test that drives a full reconnect — that harness is heavier and I'd rather flag the gap than imply coverage I didn't write.

Queued messages only go out on `onAgentStoppedRunning`, which fires on a
running -> not-running transition in the directory replica. An agent that
finished its turn while this client was disconnected, backgrounded, or had not
yet loaded the directory has no transition left to observe once the app catches
up: `reconcileAgentDirectory` only reports an agent whose previous known status
was `running`. The queue then sits until the user sends it by hand.

Re-check the queues against current state once the directory bootstrap has
settled, and send for any agent that is holding one and is not running.

Related to getpaseo#2991

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lxsolutions
lxsolutions force-pushed the fix/2991-drain-stranded-queue branch from 268e865 to c9e3e1b Compare August 8, 2026 06:40
@boudra

boudra commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Closing in favor of the implementation merged in #4160.

@boudra boudra closed this Sep 8, 2026
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