vercel/ai · error

A host tool relay turn is already active.

Error message

A host tool relay turn is already active.

What it means

startHostToolRelay's returned relay can only be bound to a single active ACP prompt turn at a time. bindTurn throws this plain Error when another turn is currently bound and the incoming turn is a different one. Re-binding the same turn is allowed, and unbindTurn clears the slot, so the guard enforces one-turn-at-a-time semantics for the shared local HTTP relay.

Source

Thrown at packages/harness-acp/src/v1/bridge/host-tool-relay.ts:114

    } catch (error) {
      const status = error instanceof RelayRequestError ? error.status : 500;
      response.writeHead(status, { 'content-type': 'application/json' });
      response.end(
        JSON.stringify({
          error: error instanceof Error ? error.message : String(error),
        }),
      );
    }
  });
  await listen({ server });
  const address = server.address() as AddressInfo;

  return {
    url: `http://127.0.0.1:${address.port}/invoke`,
    credential,
    bindTurn: ({ turn }) => {
      if (activeTurn != null && activeTurn !== turn) {
        throw new Error('A host tool relay turn is already active.');
      }
      activeTurn = turn;
    },
    unbindTurn: ({ turn }) => {
      if (activeTurn === turn) activeTurn = undefined;
    },
    updateCatalog: ({ tools: nextTools }) => {
      const fingerprint = catalogFingerprint({ tools: nextTools });
      if (fingerprint === state.fingerprint) {
        return { changed: false, revision: state.revision };
      }
      state.tools = [...nextTools];
      state.fingerprint = fingerprint;
      state.revision += 1;
      resolveCatalogChanges({ state });
      return { changed: true, revision: state.revision };
    },
    waitForCatalogRefresh: ({ revision, timeoutMs }) =>

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Always call relay.unbindTurn({ turn }) in a finally block when a turn ends, so the slot is freed.
  2. Check whether a turn is already active and wait for it to complete (or abort it) before binding a new turn.
  3. Create a separate relay via startHostToolRelay for each concurrent session/turn instead of sharing one.
  4. If re-binding the same turn, note the guard permits it — ensure you pass the same HostToolRelayTurn object, not an equal-looking new one.

Example fix

// before
session.onTurnStart(turn => {
  relay.bindTurn({ turn });
});
// after
session.onTurnStart(turn => {
  relay.bindTurn({ turn });
});
session.onTurnEnd(turn => {
  relay.unbindTurn({ turn }); // always release, even on error
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Track the currently bound turn before binding
let boundTurn: HostToolRelayTurn | undefined;
function safeBind(relay: HostToolRelay, turn: HostToolRelayTurn) {
  if (boundTurn != null && boundTurn !== turn) {
    throw new Error('Cannot bind: another relay turn is already active.');
  }
  relay.bindTurn({ turn });
  boundTurn = turn;
}

Type guard

function canBind(activeTurn: HostToolRelayTurn | undefined, next: HostToolRelayTurn): boolean {
  return activeTurn == null || activeTurn === next;
}

Try / catch

try {
  relay.bindTurn({ turn });
  await runPromptTurn(turn);
} catch (error) {
  if (error instanceof Error && error.message === 'A host tool relay turn is already active.') {
    await waitForActiveTurnToEnd(); // or abort the active turn
    relay.bindTurn({ turn });
  } else {
    throw error;
  }
} finally {
  relay.unbindTurn({ turn });
}

Prevention

When it happens

Trigger: Calling relay.bindTurn({ turn }) while activeTurn is set to a different HostToolRelayTurn — e.g. binding a second ACP session's turn before the first session unbinds, or binding a new turn in a turn-start hook while a previous turn was never unbound (an earlier turn errored or was aborted without cleanup).

Common situations: Running multiple concurrent ACP prompt turns against one shared relay; a crashed or aborted turn leaking its binding because unbindTurn was never called; forgetting to unbind in a finally block when bridging host tool calls; reusing a single relay across several sessions instead of one relay per session.

Related errors


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