vercel/ai · error · Error

${harnessId} ACP session ${sessionId} has an in-flight turn;

Error message

${harnessId} ACP session ${sessionId} has an in-flight turn; suspend it instead.

What it means

Detach requires an idle session. If a turn is currently in flight, doDetach refuses to tear down the attachment because the in-flight turn would be lost; the caller must suspend instead, which captures the turn state and returns a continue-turn handle for later resumption.

Source

Thrown at packages/harness-acp/src/v1/acp-v1-harness.ts:1621

        data: createLifecycleData({
          bridge: {
            port: bridgePort,
            token: bridgeToken,
            lastSeenEventId,
            ...(sandboxId == null ? {} : { sandboxId }),
            stateDir: bridgeStateDir,
          },
        }),
      };
    },
    doDetach: async () => {
      if (stopped) {
        throw new Error(
          `${harnessId} ACP session ${sessionId} is stopped; cannot detach.`,
        );
      }
      if (turnInFlight) {
        throw new Error(
          `${harnessId} ACP session ${sessionId} has an in-flight turn; suspend it instead.`,
        );
      }
      stopped = true;
      const lastSeenEventId = await channel.suspend();
      return {
        type: 'resume-session',
        harnessId,
        specificationVersion: 'harness-v1',
        data: createLifecycleData({
          bridge: {
            port: bridgePort,
            token: bridgeToken,
            lastSeenEventId,
            ...(sandboxId == null ? {} : { sandboxId }),
            stateDir: bridgeStateDir,
          },
        }),

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Call suspendTurn instead of detach while a turn is in flight; it returns a continue-turn handle to resume the turn later.
  2. Wait for the current turn to finish (done event) before detaching.
  3. Abort the in-flight turn first, then detach once turnInFlight is false.
  4. If migrating mid-turn is the goal, design around suspend + resume rather than detach.

Example fix

// before
await session.detach(); // throws: turn in flight
// after
const handle = await session.suspendTurn(); // continue-turn handle
// later: resume and continue the turn
Defensive patterns

Strategy: validation

Validate before calling

if (turnInFlight) {
  const handle = await session.suspendTurn(); // continue-turn handle
} else {
  const handle = await session.detach(); // resume-session handle
}

Try / catch

try {
  await session.detach();
} catch (e) {
  if (e instanceof Error && e.message.includes('in-flight turn; suspend it instead')) {
    await session.suspendTurn();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling detach while a promptTurn is actively streaming (turnInFlight=true) — e.g. attempting to detach mid-turn to migrate the session, or a cleanup routine firing during an active turn.

Common situations: App shutdown while a long turn is still running; trying to hand the session to another process mid-turn via detach; a timeout handler detaching while the model is still generating.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/d9bd8c3a31b59c85. Report an issue: GitHub.