Skip to content

[bug] Do not terminate run watch on provider stream completion #2113

Description

@zerob13

Summary

deepchat run watch can exit when one provider stream finishes even though the detached Agent run is still active or waiting for a user interaction.

Priority: P1

Reproduction

Using a sanitized detached run:

  1. Start an Agent run that reaches a tool requiring approval.
  2. Start deepchat run watch --run <run-id>.
  3. The provider round emits chat.stream.completed before the permission interaction is resolved.
  4. run watch exits successfully.
  5. An immediate run get still reports an active Session with a pending assistant message.

The permission wait is valid behavior. The defect is that the watcher reports terminal completion too early.

Root cause

RunService.isTerminalEvent() in src/main/cli/runService.ts treats either of these root-Session events as terminal:

  • chat.stream.completed
  • chat.stream.failed

Those events describe one stream/provider-round boundary, not necessarily the entire durable run lifecycle. A tool loop, follow-up provider round, permission interaction, question interaction, pending-input handoff, or resume can still follow.

The existing tests in test/main/cli/runService.test.ts explicitly expect a root chat.stream.completed event to resolve the watcher, so the incorrect lifecycle assumption is currently encoded as a contract.

Related contracts and documentation

  • docs/architecture/local-control-plane/spec.md requires --jsonl to emit one terminal result/error record and says thin CLI invocations must exit after that terminal result or EOF.
  • The same document defines detached runs as recoverable from Session state and event cursors after disconnection.
  • docs/architecture/durable-execution-journal/spec.md makes execution/run_terminal the authoritative physical-run terminal fact before terminal projections.

The watcher should terminate on the durable run/session contract, not a provider-stream implementation event.

Impact

  • Automation can start downstream work while the Agent is still running.
  • Benchmarks record incomplete latency and output.
  • Scripts may omit required interaction handling or cancellation.
  • Users interpret a normal interaction pause as an unexplained stop.

Proposed direction

  • Stop using chat.stream.completed/failed as whole-run terminal signals.
  • Prefer an explicit run-terminal event or a final Session state transition to idle/error with matching run ownership.
  • Add a public phase such as running | awaiting_interaction | terminal; do not represent an interaction wait as ordinary active generation only.
  • Preserve cursor catch-up, descendant-run filtering, overflow handling, and already-terminal fast paths.

Acceptance criteria

  • A provider stream completion does not terminate run watch while another round or interaction remains possible.
  • awaiting_interaction is observable without treating the permission/question wait as an error.
  • The watcher emits exactly one terminal result for the owned run.
  • Descendant Session events cannot terminate the root watcher.
  • Already-terminal snapshots still return immediately.
  • Tests cover provider-round completion, permission pause/resume, question pause/resume, cancellation, failure, and final completion.

User benefit

CLI users and harnesses can trust that “watch finished” means the run actually reached a terminal state.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions