{"record":{"id":"8c5fe9ebb061758f","repo":"vercel/ai","slug":"sandboxchannel-cannot-send-message-type-chan","errorCode":null,"errorMessage":"SandboxChannel: cannot send ${message.type} — channel is closed.","messagePattern":"SandboxChannel: cannot send (.+?) — channel is closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/harness/src/utils/sandbox-channel.ts","lineNumber":260,"sourceCode":"    return () => {\n      set!.delete(listener as unknown as Listener<TOut, EventTypeOf<TOut>>);\n    };\n  }\n\n  onClose(handler: (code: number, reason: string) => void): void {\n    this.onCloseHandlers.add(handler);\n  }\n\n  onReconnect(handler: () => void): () => void {\n    this.onReconnectHandlers.add(handler);\n    return () => {\n      this.onReconnectHandlers.delete(handler);\n    };\n  }\n\n  send(message: TIn): void {\n    if (this.terminal) {\n      throw new Error(\n        `SandboxChannel: cannot send ${message.type} — channel is closed.`,\n      );\n    }\n    this.rawSend(JSON.stringify(message));\n  }\n\n  /**\n   * Mark that the host is tearing the session down. The next socket close is\n   * then treated as terminal rather than triggering a reconnect. Call before\n   * sending a `stop` / `destroy` message whose completion closes the bridge\n   * socket.\n   */\n  beginClose(): void {\n    this.closing = true;\n  }\n\n  close(): void {\n    if (this.terminal) return;","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness/src/utils/sandbox-channel.ts#L242-L278","documentation":"Thrown by SandboxChannel.send in packages/harness/src/utils/sandbox-channel.ts when a message is sent on a channel that has reached terminal (closed) state. Once a channel is terminal no further messages can be serialized or transmitted; every send attempt fails immediately rather than silently dropping the message. A new channel must be established to continue communicating.","triggerScenarios":"Calling `channel.send(message)` after the channel entered its terminal state — e.g. the sandbox process exited, the WebSocket closed fatally, or `close()` was invoked — and application code still holds the stale channel reference.","commonSituations":"Fire-and-forget command queues continuing to push messages after sandbox teardown; long-running workers holding a channel across sandbox restarts; race where the sandbox dies mid-task and the next `send` throws.","solutions":["Check the channel's connection/terminal state before calling send, and recreate the channel when it is terminal.","Wrap sends in error handling that triggers channel recreation and message replay.","Stop producers (queues/callbacks) when the channel's terminal/close callback fires.","Use the channel's reconnect handlers to detect permanent closure versus transient disconnect."],"exampleFix":"// before\nawait channel.close();\nchannel.send({ type: 'run' }); // throws\n// after\nawait channel.close();\nchannel = createChannel();\nawait channel.open();\nchannel.send({ type: 'run' });","handlingStrategy":"try-catch","validationCode":"if (channel.isTerminal?.()) {\n  throw new Error('Channel closed — recreate before sending ' + message.type);\n}","typeGuard":"function canSend(channel: { isTerminal?: () => boolean }): boolean {\n  return !(channel.isTerminal?.() ?? true);\n}","tryCatchPattern":"try {\n  channel.send(message);\n} catch (error) {\n  if (/channel is closed/.test((error as Error).message)) {\n    await recreateAndReplay([message]); // new channel, replay unsent messages\n  } else throw error;\n}","preventionTips":["Pause message producers when the terminal/close callback fires.","Queue messages with replay support so a closed channel loses nothing.","Bind send calls to a live-connection check.","Handle sandbox process exit events eagerly instead of waiting for send to throw."],"tags":["sandbox","channel","lifecycle","messaging"],"backgroundTag":"channel-already-closed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}