Skip to content

Commit b65066b

Browse files
committed
fix(provider): raise explicit error when interrupting inactive session so reactor force-clears
Follow-up to 5a63be9. The previous change silenced the error but also broke the reactor's catchCause path: `interruptTurn` would return successfully when no active session existed (isActive === false), which meant the reactor NEVER ran its force-clear code, and the "Working" indicator stayed stuck on any thread whose provider session was orphaned between restarts. Raise a specific `ProviderValidationError` from `interruptTurn` when the routed session is inactive, so the reactor's existing catchCause handler always runs, always dispatches a `thread.session.set` with status="interrupted", and always surfaces the visible "Turn force-stopped" activity. The contract's `Effect<void, ProviderServiceError>` return type is preserved — the failure is exactly what the reactor already knows how to handle. User-reported repro: thread `013848ab-2305-4125-92b9-9ed3c6faeffe` stuck on "Working" for hours; clicking Stop now correctly transitions the thread to an interrupted/error-free state so the user can send a new message to re-establish the provider session. Verified: - Typecheck: 9/9 packages clean - @marcode/server: 995 pass, 5 skipped (24 ProviderCommandReactor, 26 ProviderService). - @marcode/web: 1074 pass.
1 parent 5a63be9 commit b65066b

1 file changed

Lines changed: 11 additions & 6 deletions

File tree

apps/server/src/provider/Layers/ProviderService.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,11 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
474474
return yield* Effect.gen(function* () {
475475
// Don't resurrect a dead session just to kill it. If the provider has
476476
// no active session (process crashed, app was closed mid-turn, or
477-
// recovery has no resume cursor), interrupt is a no-op from the
478-
// adapter's perspective — the session is already gone. The reactor
479-
// still force-clears the orchestration thread state regardless, so
480-
// the UI unblocks.
477+
// recovery has no resume cursor), raise a validation error — the
478+
// reactor's catchCause force-clears the orchestration thread state
479+
// so the UI unblocks from "Working". Raising here (vs. returning
480+
// silently) guarantees the reactor's catch path always runs for
481+
// orphaned sessions.
481482
const routed = yield* resolveRoutableSession({
482483
threadId: input.threadId,
483484
operation: "ProviderService.interruptTurn",
@@ -490,9 +491,13 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
490491
"provider.thread_id": input.threadId,
491492
"provider.turn_id": input.turnId,
492493
});
493-
if (routed.isActive) {
494-
yield* routed.adapter.interruptTurn(routed.threadId, input.turnId);
494+
if (!routed.isActive) {
495+
return yield* toValidationError(
496+
"ProviderService.interruptTurn",
497+
`Provider session for thread '${input.threadId}' is not active; nothing to interrupt.`,
498+
);
495499
}
500+
yield* routed.adapter.interruptTurn(routed.threadId, input.turnId);
496501
}).pipe(
497502
withMetrics({
498503
counter: providerTurnsTotal,

0 commit comments

Comments
 (0)