paperclipai/paperclip · error · RunnerGoalActionError

runner_goal_stop_unconfirmed

runner_goal_stop_unconfirmed

Error message

runner_goal_stop_unconfirmed: The provider did not confirm that the live agent goal stopped.

What it means

settleLiveRunnerGoalBeforeInterrupt polls the goal projection every 25ms for up to 5 seconds after issuing a pause or clear to the live provider. If the projection never shows status "paused" (for pause) or a null goal (for clear) within that window, it throws RunnerGoalActionError(runner_goal_stop_unconfirmed): the command was delivered but the provider never confirmed the stop, so the caller must not interrupt the process.

Source

Thrown at server/src/services/runner-goals.ts:997

        commandId: `goal_${requestId}`,
      });
  const completion = adapterControl?.completion ?? nativeControl?.completion ?? null;
  if (!completion) {
    throw new RunnerGoalActionError(
      "runner_goal_live_controller_unavailable",
      "The live agent goal controller is unavailable; the run was not interrupted.",
    );
  }
  await completion;

  const deadline = Date.now() + 5_000;
  while (Date.now() < deadline) {
    const observed = await goals.projection(binding.companyId, binding.issueId, binding.agentId);
    if (action === "pause" && observed?.goal?.status === "paused") return "paused";
    if (action === "clear" && observed?.goal === null) return "cleared";
    await new Promise<void>((resolve) => setTimeout(resolve, 25));
  }
  throw new RunnerGoalActionError(
    "runner_goal_stop_unconfirmed",
    "The provider did not confirm that the live agent goal stopped.",
  );
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry settleLiveRunnerGoalBeforeInterrupt after a short wait — the provider may simply have been slower than the 5s deadline; check the projection first, as the goal may now be paused/cleared.
  2. Inspect the provider/adapter logs to see whether the pause/clear command was actually processed; fix the provider handler if it acks without applying.
  3. Verify the goal event path (applyRunnerGoalPrpEvent / projection) is applying state changes for this session; a stuck projection makes confirmation impossible.
  4. If the goal is genuinely stuck active, stop it through the provider's own UI/CLI, then re-run the interrupt flow once the projection shows paused or cleared.

Example fix

// before
try { await settleLiveRunnerGoalBeforeInterrupt(db, binding); } catch (e) { interruptProcess(); }
// after
try {
  await settleLiveRunnerGoalBeforeInterrupt(db, binding);
} catch (e) {
  if (e?.code === "runner_goal_stop_unconfirmed") {
    const p = await runnerGoalService(db).projection(binding.companyId, binding.issueId, binding.agentId);
    if (p?.goal?.status === "paused" || p?.goal === null) return; // late confirmation; now safe
  }
  throw e; // still unconfirmed — do NOT interrupt
}
Defensive patterns

Strategy: retry

When it happens

Trigger: Calling settleLiveRunnerGoalBeforeInterrupt() where the adapter/PRP completion resolves but goals.projection() still returns an active goal after 5s: the provider accepted the pause/clear command yet never applied it, or applied it slower than the 5s deadline, or the projection read path diverges from the applied state.

Common situations: Slow or overloaded provider taking longer than 5 seconds to honor pause/clear; a provider that acks control commands without acting on them; projection staleness (event not yet applied via applyRunnerGoalPrpEvent); long-running non-interruptible work in the agent that defers the pause.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/0e75f7663e9e68ee. Report an issue: GitHub.