{"record":{"id":"f71b9d464b6bd30f","repo":"vercel/ai","slug":"harnessid-acp-session-sessionid-has-no-in-fl","errorCode":null,"errorMessage":"${harnessId} ACP session ${sessionId} has no in-flight turn to suspend.","messagePattern":"(.+?) ACP session (.+?) has no in-flight turn to suspend\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/harness-acp/src/v1/acp-v1-harness.ts","lineNumber":1593,"sourceCode":"            },\n          });\n        },\n      });\n    },\n    doCompact: async () => {\n      throw unsupported({\n        harnessId,\n        message: 'ACP v1 does not define manual session compaction.',\n      });\n    },\n    doSuspendTurn: async () => {\n      if (stopped) {\n        throw new Error(\n          `${harnessId} ACP session ${sessionId} is stopped; cannot suspend.`,\n        );\n      }\n      if (!turnInFlight) {\n        throw new Error(\n          `${harnessId} ACP session ${sessionId} has no in-flight turn to suspend.`,\n        );\n      }\n      stopped = true;\n      const lastSeenEventId = await channel.suspend();\n      return {\n        type: 'continue-turn',\n        harnessId,\n        specificationVersion: 'harness-v1',\n        data: createLifecycleData({\n          bridge: {\n            port: bridgePort,\n            token: bridgeToken,\n            lastSeenEventId,\n            ...(sandboxId == null ? {} : { sandboxId }),\n            stateDir: bridgeStateDir,\n          },\n        }),","sourceCodeStart":1575,"sourceCodeEnd":1611,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-acp/src/v1/acp-v1-harness.ts#L1575-L1611","documentation":"Suspending captures the channel's last seen event id and detaches from the live bridge; it requires a turn to actually be in flight. If turnInFlight is false there is nothing to suspend, so doSuspendTurn throws. To end a session with no active turn, use detach instead.","triggerScenarios":"Calling suspendTurn when no turn is active: before the first prompt, after the current turn finished (done), or after a prior suspend already ended the turn.","commonSituations":"Calling suspend during an idle period between turns assuming it pauses the session; suspending after the turn's done event already fired; a race where the turn completed before the suspend call executed.","solutions":["Call detach instead of suspendTurn when no turn is in flight.","Suspend only while a turn is actively running (between prompt start and done), e.g. from the turn's event stream when an interruption is detected.","Track turn state in your app and branch: suspend if in flight, detach otherwise.","If the goal is just to preserve the session for later, detach with no in-flight turn returns a resume-session handle."],"exampleFix":"// before\nawait session.suspendTurn(); // throws: no in-flight turn\n// after\nif (turnInFlight) {\n  await session.suspendTurn();\n} else {\n  await session.detach();\n}","handlingStrategy":"validation","validationCode":"if (turnInFlight) {\n  await session.suspendTurn();\n} else {\n  await session.detach();\n}","typeGuard":null,"tryCatchPattern":"try {\n  await session.suspendTurn();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('no in-flight turn to suspend')) {\n    await session.detach();\n  } else throw e;\n}","preventionTips":["Only suspend while the turn's event stream is still active","Branch on turn state: suspend when in flight, detach when idle","Remember suspend ends the turn's liveness — do not suspend after done"],"tags":["acp","suspend","turn-state","session-lifecycle"],"backgroundTag":"no-in-flight-turn","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}