can1357/oh-my-pi · warning · ToolAbortError

${latestText} || Eval cell aborted

Error message

${latestText} || Eval cell aborted

What it means

While foreground-waiting on an auto-backgrounded eval cell, the tool races the job's completion against the wait budget and the abort signal. If the caller's AbortSignal fires (the agent turn was cancelled/aborted), waitResult.kind becomes "aborted": the job is cancelled and a ToolAbortError is thrown, using the latest streamed output text as the message, or this generic message when nothing was streamed yet.

Source

Thrown at packages/coding-agent/src/tools/eval.ts:604

		// Suppress the completion delivery up front so a job finishing while we
		// foreground-wait cannot also be injected by the delivery loop. Lifted
		// via resumeDeliveries() if we end up backgrounding after all.
		autoBgManager.acknowledgeDeliveries([jobId]);
		const waitResult = await raceJobSettlement(
			completion.promise,
			autoBackgroundWaitMs,
			signal,
			ctx?.toolCall?.steeringSignal,
		);
		if (waitResult.kind === "completed") {
			return waitResult.result;
		}
		if (waitResult.kind === "failed") {
			throw waitResult.error;
		}
		if (waitResult.kind === "aborted") {
			autoBgManager.cancel(jobId);
			throw new ToolAbortError(latestText || "Eval cell aborted");
		}
		forwardUpdates = false;
		autoBgManager.resumeDeliveries([jobId]);
		// "steer": a queued user/peer message arrived mid-wait — background the
		// cell (it keeps running) so the message injects promptly.
		const steerNotice =
			waitResult.kind === "steer"
				? "Backgrounded early to handle an incoming message; the cell keeps running."
				: undefined;
		return this.#buildBackgroundStartResult(jobId, cells, languages, notice, latestText, latestDetails, steerNotice);
	}

	/**
	 * Tool result returned when a cell converts into a background job: the live
	 * output tail plus the background notice, with details carrying the running
	 * cell snapshot and the async job marker the transcript renderer keys on.
	 */
	#buildBackgroundStartResult(

View on GitHub (pinned to 9690622007)

Solutions

  1. This is expected cancellation behavior — check that the abort was intentional (user interrupt vs. spurious signal).
  2. Re-issue the eval cell if the work was still needed; the job was cancelled via autoBgManager.cancel(jobId).
  3. Avoid passing short-lived signals to execute() for long cells, or run the cell with auto-background disabled to control cancellation semantics explicitly.

Example fix

// before: shared signal aborted by unrelated timeout
await evalTool.execute(id, params, shortLivedSignal);
// after: dedicated controller scoped to the cell
const c = new AbortController();
const p = evalTool.execute(id, params, c.signal);
// abort only when this cell should stop: c.abort()
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) return; // don't start work that will be aborted

Type guard

function isAbort(e: unknown): e is ToolAbortError { return e instanceof ToolAbortError || (e instanceof Error && e.name === "ToolAbortError"); }

Try / catch

try {
  await evalTool.execute(id, params, signal);
} catch (e) {
  if (isAbort(e)) {
    // intentional cancellation; cleanup or resubmit if work still needed
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling EvalTool.execute with an AbortSignal and aborting it while a backgrounded cell is still running (raceJobSettlement returns aborted); happens on user interrupt, cancellation of the agent turn, or upstream deadline.

Common situations: Users pressing escape/interrupt while a long eval cell runs in the background; orchestrators cancelling tool calls on timeouts; agent loops that abort superseded tool calls.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/38a8bf0eaac13360. Report an issue: GitHub.