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
- No fix required — the command never ran; re-issue the call if execution is still wanted.
- In the host, avoid aborting signals for calls you still intend to run; check signal state before dispatching tools.
- 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
- Check signal.aborted in the host before dispatching tool calls.
- Don't abort signals for calls you still expect to complete.
- Keep direnv preflight fast or raise its timeout so it doesn't consume the abort window.
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
- Request was aborted
- Auth broker request aborted
- OAuth refresh ownership aborted by caller
- Request was aborted.
- AbortError
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/27d855ee5a5c9545.
Report an issue: GitHub.