can1357/oh-my-pi · info · ToolAbortError

Command aborted

Error message

Command aborted

What it means

The ACP client-bridge terminal route has no signal parameter in its createTerminal contract, so allocation cannot be cancelled retroactively. The tool therefore checks signal?.aborted immediately before allocating a terminal and throws ToolAbortError if cancellation already happened. This enforces kill-before-allocate ordering and distinct abort vs timeout result shapes.

Source

Thrown at packages/coding-agent/src/tools/bash.ts:1136

				? await applyDirenvPreflight(command, commandCwd, {
						callerEnv: resolvedEnv,
						signal,
						timeoutMs: this.session.settings.get("bash.direnvLoadTimeoutMs"),
						callerTimeoutMs: timeoutMs,
						direnvSetting: this.session.settings.get("bash.direnv"),
					})
				: undefined;

		// Route through the client terminal when the client advertises the terminal capability.
		// Skip when pty=true (PTY needs the local terminal UI).
		if (clientBridge?.capabilities.terminal && clientBridge.createTerminal && !pty) {
			// Invariant (ACP terminal bridge): createTerminal has no signal in its
			// contract; allocation cannot be cancelled retroactively. Guard before
			// allocation. Shared timeout helper / pure AbortSignal fusion rejected:
			// we need explicit kill-before-read ordering and distinct abort vs
			// timeout result shapes. Per-route race retained for testability.
			if (signal?.aborted) {
				throw new ToolAbortError("Command aborted");
			}

			const bridgeWallTimeStart = performance.now();
			const killGraceMs = 1000;
			const outputSnapshotGraceMs = 2000;
			// Cancellable timeout: a bare Bun.sleep(timeoutMs) would leave a live,
			// ref'd timer for the full command timeout after fast completions —
			// accumulating timers and delaying process shutdown in SDK/headless use.
			// `timeoutMs` is optional (#4642): without one, no timer is armed and
			// the promise simply never resolves.
			const { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers<{
				kind: "timeout";
			}>();
			const timeoutTimer = timeoutMs ? setTimeout(() => resolveTimeout({ kind: "timeout" }), timeoutMs) : undefined;
			const { promise: abortedP, resolve: resolveAborted } = Promise.withResolvers<void>();
			let handle: ClientBridgeTerminalHandle | undefined;
			let killStarted = false;
			const fireKill = (): Promise<void> => {

View on GitHub (pinned to 9690622007)

Solutions

  1. No fix required — the command never ran; re-issue the call if execution is still wanted.
  2. In the host, avoid aborting signals for calls you still intend to run; check signal state before dispatching tools.
  3. If aborts stem from preflight timeouts, raise bash.direnvLoadTimeoutMs or disable direnv.
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) {
  // skip dispatching the tool entirely
  return;
}

Try / catch

try {
  await bash.execute(id, { command }, signal);
} catch (e) {
  if (e instanceof ToolAbortError && e.message === "Command aborted") {
    return; // cancelled before execution; nothing ran
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the bash tool through a session whose client bridge advertises the terminal capability, when the AbortSignal is already aborted at entry (cancelled before the tool ran, or aborted during direnv preflight).

Common situations: Queued tool calls executed after the user cancelled the turn; preflight (direnv load) consuming the abort window; host frameworks aborting signals before the tool is dispatched.

Related errors


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