Explicit steer is refused with "No running provider turn found" during run startup/completion gaps#23271

opened6d agobyGgoleary

Summary

When a user sends a follow-up with the default Steer follow-up behavior while a run is active but has no running provider turn, the message is refused with No running provider turn found for active run <runId>. This happens when the run is starting, before the provider's first event, or in the gap as a turn finishes. The message isn't delivered.

#15600 / #15691 fixed this for deliveryIntent: "auto". The client sends an explicit deliveryIntent: "steer" whenever it sees a run as running and followUpBehavior is steer (the default), so the most common send path still hits the error.

Repro (observed on v0.1.1, Windows, Claude provider)

From the server trace and the provider event log for one thread:

  • 02:11:29.777Z: run ordinal:10 starts after a user message.
  • 02:11:32.676Z: user sends a follow-up. message.dispatch → Orchestrator.dispatchSteerIntoRun fails with OrchestrationV2DispatchCommandError: No running provider turn found for active run run:thread:…:ordinal:10.
  • 02:11:33.434Z: first native event from the Claude provider for that turn.

The follow-up stays in the composer and the user has to resend it.

Cause

In CommandPolicy resolveDeliveryIntent, explicit steer (and restart) return before the gap check that #15691 added:

js
1if (deliveryIntent === "steer") return { type: "steer_active", targetRunId: activeRun.id };
2if (deliveryIntent === "restart") return { type: "restart_active", targetRunId: activeRun.id };
3if (activeRun.status === "preparing" || activeRun.status === "starting" ||
4 !projection.providerTurns.some((t) => t.runAttemptId === activeRun.activeAttemptId && t.status === "running"))
5 return { type: "queue_after_active" };

Expected

An explicit steer that arrives while the active attempt has no running provider turn shouldn't be rejected. It should be queued and delivered to the next or just-starting turn, as auto now is. From the user's side, steering a run that is just about to start and queueing for that run look the same.

Suggested fix

Apply the gap check to steer as well as auto: return queue_after_active when the run is preparing/starting or the active attempt has no running provider turn. Leave restart as it is. Add a steer row to the existing gap table in CommandPolicy.test.ts (absent, pending, completed, previous-attempt running).

Synced from GitHub

Adding a matching macOS observation, with sanitized timing evidence that may help with the startup-gap case. Thanks for tracking this down.

Environment: macOS desktop, a locally localized build 0.1.2-zh.1 based on upstream v0.1.2, using the Claude provider with claude-opus-5-5. This was observed during normal use, not a synthetic stress test.

Two occurrences were correlated using server spans, the provider protocol log, and read-only run/provider-turn projections. Times below are relative to each run's recorded startedAt; identifiers, message contents, paths, account configuration, and absolute timestamps are omitted.

ObservationSample ASample B
Run recorded as running+0.000 s+0.000 s
Follow-up rejected in Orchestrator.dispatchSteerIntoRun+9.887 s+26.049 s
Provider turn's recorded start+12.911 s+30.106 s
Startup checkpoint capture duration12.890 s30.041 s

Both rejections reported:

text
1No running provider turn found for active run <redacted>.

For sample A, the original prompt was offered to Claude at +12.912 s. The GitVcsDriver.checkpoints.captureCheckpoint span inside RunExecutionService.startRootRun took 12.890 s, including a Git subprocess taking 12.796 s. The original run subsequently completed; the rejected follow-up remained in the composer. Sample B's original run also subsequently completed.

This supports a concrete way the startup gap becomes user-visible for seconds: the run is already recorded as running while baseline checkpoint capture is still pending, before the Claude provider turn exists. I have not investigated why those checkpoints were slow, so I am not attributing that to repository size or a particular Git operation.

The upstream v0.1.2 source matches the issue's diagnosis: explicit steer returns before the no-running-provider-turn check, whereas auto queues. The web composer can send that explicit intent while the run reads as running.

This is additional evidence for the reported failure only. I have not runtime-tested either proposed fix (#23559 or #25749) on this machine. No raw logs or screenshots are attached, to avoid exposing private workspace and conversation data.