{"record":{"id":"9b023214e9c1a110","repo":"vercel/ai","slug":"acp-connection-closed-before-cancellation","errorCode":null,"errorMessage":"ACP connection closed before cancellation.","messagePattern":"ACP connection closed before cancellation\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/harness-acp/src/v1/bridge/index.ts","lineNumber":238,"sourceCode":"    sessionId: activeSession.sessionId,\n    model: start.model,\n    mapping: start.modelMapping,\n  });\n\n  let rejectCancellationFailure!: (error: unknown) => void;\n  const cancellationFailure = new Promise<never>((_, reject) => {\n    rejectCancellationFailure = reject;\n  });\n  void cancellationFailure.catch(() => {});\n  let cancellationRequested = false;\n  let cancellationFailureError: Error | undefined;\n  const cancel = async () => {\n    if (cancellationRequested) return;\n    cancellationRequested = true;\n    activePermissionController?.cancelAll();\n    try {\n      if (connection == null) {\n        throw new Error('ACP connection closed before cancellation.');\n      }\n      await connection.agent.notify(acp.methods.agent.session.cancel, {\n        sessionId: activeSession.sessionId,\n      });\n    } catch (error) {\n      cancellationFailureError = createACPBridgeError({\n        stage: 'session cancellation',\n        cause: error,\n      });\n      rejectCancellationFailure(cancellationFailureError);\n    }\n  };\n  turn.emit({ type: 'stream-start' });\n  const emitStreamEvent = createEmitStreamEvent({\n    emit: event => turn.emit(event as BridgeEvent),\n    emitToolCallCandidate: ({ toolCall }) => {\n      turn.emit({\n        type: 'acp-tool-call-candidate',","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/bridge/index.ts#L220-L256","documentation":"In the cancel path of runTurn, if the ACP connection object is already null (the agent connection closed), the bridge cannot send the session/cancel notification and throws this Error. The throw is caught immediately and recorded as cancellationFailureError, so it signals that cancellation could not be delivered to the agent rather than crashing the turn outright.","triggerScenarios":"cancel() is invoked (e.g. by an abort signal or turn teardown) after the ACP agent process/connection has already terminated, so connection == null when attempting connection.agent.notify(acp.methods.agent.session.cancel, ...).","commonSituations":"User aborts the request at the same moment the agent process crashes or exits; the agent already finished/closed the connection before the abort signal fired; timeout logic cancels a turn whose connection already dropped.","solutions":["Treat this as a benign race when the connection is already closed — the turn is over, so cancellation delivery is moot.","Check agent stderr/logs to determine why the connection closed prematurely if cancellations routinely fail this way.","Add application-level guard so cancellation is only requested while a turn is known to be active."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await bridge.runTurn({ prompt, abortSignal });\n} catch (error) {\n  if (error instanceof Error && error.message === 'ACP connection closed before cancellation.') {\n    // benign: agent already disconnected; inspect logs if it happens often\n    return;\n  }\n  throw error;\n}","preventionTips":["Check agent process health if cancellations frequently race with connection closure.","Only request cancellation while a turn is known to be active.","Capture agent stderr to diagnose premature connection drops."],"tags":["acp","cancellation","connection","race-condition"],"backgroundTag":"connection-closed-before-cancel","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}