paperclipai/paperclip · error
OpenCode interruption is unavailable
Error message
OpenCode interruption is unavailable
What it means
The proxy throws this for a 'turn/interrupt' request when no session is open or the open OpenCode session object exposes no `interrupt` capability. Interruption is optional in the driver contract, so the proxy fails closed rather than silently ignoring the request.
Source
Thrown at packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts:375
message: { role: "user", text: messageText },
});
activeTurnId = turn.turnId;
// OpenCode normally publishes session/turn startup over SSE, but a fast
// completion can make the synchronous prompt response the only place the
// authoritative turn id is observed. Emit the normalized notification
// after this request's JSON-RPC response when SSE did not already announce
// it. runnerd buffers notifications received while awaiting a response,
// so replaying an earlier SSE announcement would violate strict turn
// binding at the outer driver.
queueMicrotask(() => {
announceTurnStarted(session!, turn.turnId);
});
result = { turn: { id: turn.turnId, status: "inProgress" } };
break;
}
case "turn/interrupt":
if (!session?.interrupt)
throw new Error("OpenCode interruption is unavailable");
await session.interrupt({
turnId: text(params.turnId, activeTurnId ?? ""),
});
result = true;
break;
case "thread/read":
if (!session) throw new Error("OpenCode thread is not open");
result = {
thread: {
id: session.ids().driverSessionId,
cwd,
turns: activeTurnId
? [{ id: activeTurnId, status: "inProgress" }]
: [],
},
transcript: await session.read?.(),
};
break;View on GitHub (pinned to 01ad858492)
Solutions
- Ensure 'thread/start' or 'thread/resume' completed successfully before interrupting.
- Check whether the underlying OpenCode driver session supports interrupt; disable the stop control if `session.interrupt` is absent.
- Upgrade the opencode CLI/adapter to a version whose session implements cancellation.
- As a fallback, let the turn complete or kill the underlying process instead of calling interrupt.
Example fix
// before
await rpc({ method: 'turn/interrupt', params: { turnId } });
// after
if (capabilities.interrupt) {
await rpc({ method: 'turn/interrupt', params: { turnId } });
} Defensive patterns
Strategy: type-guard
Validate before calling
const canInterrupt = session !== null && typeof session.interrupt === 'function';
Type guard
function supportsInterrupt(s): s is { interrupt: (p: { turnId: string }) => Promise<unknown> } {
return s !== null && typeof (s as any).interrupt === 'function';
} Try / catch
try {
await rpc({ method: 'turn/interrupt', params: { turnId } });
} catch (e) {
if (e.message.includes('interruption is unavailable')) {
console.warn('turn cannot be cancelled for this session kind');
} else throw e;
} Prevention
- Gate stop/interrupt UI controls on session capability.
- Open the thread before attempting any per-turn operations.
- Pin an opencode CLI/adapter version known to support cancellation.
When it happens
Trigger: Calling 'turn/interrupt' (a) before a thread is opened (session null), or (b) against an OpenCode session implementation that does not implement interrupt() (no cancel support in that transport/version).
Common situations: Client UI always shows a stop button even when the session kind lacks cancellation; opencode CLI version or transport without interrupt support; interrupt raced with session teardown.
Related errors
- OpenCode thread is not open
- ${prefix}: the capability must be an object.
- DUPLEX_CHANNEL_CAPABILITY_DENIED
- native_session_attach_binding_mismatch
- native_session_multi_run_unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/b9d1015759ec6294.
Report an issue: GitHub.