vercel/ai · error · Error
${harnessId} ACP session ${sessionId} is stopped; cannot sus
Error message
${harnessId} ACP session ${sessionId} is stopped; cannot suspend. What it means
doSuspendTurn operates on the live session's channel; once the session is stopped (via stop/destroy/terminateBridge or a prior suspend/detach), the channel is closing or closed and suspension is impossible. The harness throws to prevent suspending an already-terminated ACP session identified by sessionId.
Source
Thrown at packages/harness-acp/src/v1/acp-v1-harness.ts:1588
turnStartConfig,
recoveryMode: {
type: 'lossy-rerun',
acpSessionId: latestACPSessionId!,
reason: recoveryStatus?.reason ?? 'bridge process loss',
},
});
},
});
},
doCompact: async () => {
throw unsupported({
harnessId,
message: 'ACP v1 does not define manual session compaction.',
});
},
doSuspendTurn: async () => {
if (stopped) {
throw new Error(
`${harnessId} ACP session ${sessionId} is stopped; cannot suspend.`,
);
}
if (!turnInFlight) {
throw new Error(
`${harnessId} ACP session ${sessionId} has no in-flight turn to suspend.`,
);
}
stopped = true;
const lastSeenEventId = await channel.suspend();
return {
type: 'continue-turn',
harnessId,
specificationVersion: 'harness-v1',
data: createLifecycleData({
bridge: {
port: bridgePort,
token: bridgeToken,View on GitHub (pinned to 69428b1f8b)
Solutions
- Track session lifecycle and only call suspendTurn while the session is active (not stopped).
- If the turn must survive shutdown, suspend it before calling stop/destroy on the session.
- If already suspended/detached, use the returned lifecycle data to resume (continue-turn/resume-session) instead of suspending again.
- Guard the suspend call with a local 'stopped' flag set when you stop the session.
Example fix
// before await session.stop(); await session.suspendTurn(); // throws: session stopped // after await session.suspendTurn(); // suspend first await session.stop();
Defensive patterns
Strategy: validation
Validate before calling
let stopped = false;
async function safeSuspend(session) {
if (stopped) throw new Error('session already stopped');
return session.suspendTurn();
} Try / catch
try {
await session.suspendTurn();
} catch (e) {
if (e instanceof Error && e.message.includes('is stopped; cannot suspend')) {
// use previously returned lifecycle data to resume instead
} else throw e;
} Prevention
- Suspend before stop/destroy in shutdown sequences
- Make cleanup idempotent so suspend/stop are not called twice
- Discard session handles after stop; use persisted lifecycle data for resumption
When it happens
Trigger: Calling suspendTurn on a session that was already stopped — after stop/destroy was issued, after a prior suspendTurn or detach set stopped=true, or on a session restored as stopped.
Common situations: Double-suspending after a race; calling suspend after the app already terminated the bridge during shutdown; invoking suspend on a stale session handle kept after detach; shutdown handlers running twice.
Related errors
- ${harnessId} ACP session ${sessionId} has no in-flight turn
- ${harnessId} ACP session ${sessionId} is stopped; cannot det
- ${harnessId} ACP session ${sessionId} has an in-flight turn;
- claude-code session ${sessionId} is stopped; cannot suspend.
- The ${model.provider} model "${model.modelId}" does not supp
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/5295062f1cf3adf5.
Report an issue: GitHub.