{"record":{"id":"27d855ee5a5c9545","repo":"can1357/oh-my-pi","slug":"command-aborted-27d855","errorCode":null,"errorMessage":"Command aborted","messagePattern":"Command aborted","errorType":"exception","errorClass":"ToolAbortError","httpStatus":null,"severity":"info","filePath":"packages/coding-agent/src/tools/bash.ts","lineNumber":1136,"sourceCode":"\t\t\t\t? await applyDirenvPreflight(command, commandCwd, {\n\t\t\t\t\t\tcallerEnv: resolvedEnv,\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t\ttimeoutMs: this.session.settings.get(\"bash.direnvLoadTimeoutMs\"),\n\t\t\t\t\t\tcallerTimeoutMs: timeoutMs,\n\t\t\t\t\t\tdirenvSetting: this.session.settings.get(\"bash.direnv\"),\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\n\t\t// Route through the client terminal when the client advertises the terminal capability.\n\t\t// Skip when pty=true (PTY needs the local terminal UI).\n\t\tif (clientBridge?.capabilities.terminal && clientBridge.createTerminal && !pty) {\n\t\t\t// Invariant (ACP terminal bridge): createTerminal has no signal in its\n\t\t\t// contract; allocation cannot be cancelled retroactively. Guard before\n\t\t\t// allocation. Shared timeout helper / pure AbortSignal fusion rejected:\n\t\t\t// we need explicit kill-before-read ordering and distinct abort vs\n\t\t\t// timeout result shapes. Per-route race retained for testability.\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new ToolAbortError(\"Command aborted\");\n\t\t\t}\n\n\t\t\tconst bridgeWallTimeStart = performance.now();\n\t\t\tconst killGraceMs = 1000;\n\t\t\tconst outputSnapshotGraceMs = 2000;\n\t\t\t// Cancellable timeout: a bare Bun.sleep(timeoutMs) would leave a live,\n\t\t\t// ref'd timer for the full command timeout after fast completions —\n\t\t\t// accumulating timers and delaying process shutdown in SDK/headless use.\n\t\t\t// `timeoutMs` is optional (#4642): without one, no timer is armed and\n\t\t\t// the promise simply never resolves.\n\t\t\tconst { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers<{\n\t\t\t\tkind: \"timeout\";\n\t\t\t}>();\n\t\t\tconst timeoutTimer = timeoutMs ? setTimeout(() => resolveTimeout({ kind: \"timeout\" }), timeoutMs) : undefined;\n\t\t\tconst { promise: abortedP, resolve: resolveAborted } = Promise.withResolvers<void>();\n\t\t\tlet handle: ClientBridgeTerminalHandle | undefined;\n\t\t\tlet killStarted = false;\n\t\t\tconst fireKill = (): Promise<void> => {","sourceCodeStart":1118,"sourceCodeEnd":1154,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/bash.ts#L1118-L1154","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (signal?.aborted) {\n  // skip dispatching the tool entirely\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await bash.execute(id, { command }, signal);\n} catch (e) {\n  if (e instanceof ToolAbortError && e.message === \"Command aborted\") {\n    return; // cancelled before execution; nothing ran\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["abort","cancellation","acp","terminal"],"backgroundTag":"abort-signal-already-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}