paperclipai/paperclip · warning

OpenCode request ${input.requestId} is already settling

Error message

OpenCode request ${input.requestId} is already settling

What it means

Once `resolveRuntimeRequest` starts submitting a reply/reject to OpenCode, it sets `pending.settling = true`; a second concurrent resolution attempt for the same requestId while the first HTTP call is still in flight throws this error. This guards against racing duplicate replies (OpenCode's question/permission reply APIs are not idempotent). The flag is reset only if the submitting operation itself throws.

Source

Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:643

    if (!pending)
      throw new Error(
        `OpenCode request ${input.requestId} is no longer pending`,
      );
    if (
      pending.request.turnId !== input.turnId ||
      this.#activeTurnId !== input.turnId
    ) {
      throw new Error(
        `OpenCode request ${input.requestId} belongs to a stale turn`,
      );
    }
    const resolution = parseHarnessRuntimeRequestResolution(
      pending.request.requestKind,
      input.resolution,
      pending.request.input,
    );
    if (pending.settling)
      throw new Error(
        `OpenCode request ${input.requestId} is already settling`,
      );
    pending.settling = true;
    const submit = async (operation: Promise<unknown>) => {
      try {
        await operation;
      } catch (error) {
        if (this.#pendingRuntimeRequests.get(input.requestId) === pending)
          pending.settling = false;
        throw error;
      }
    };
    const workspace = `directory=${encodeURIComponent(this.#workingDirectory)}`;
    if (pending.request.requestKind === "permission_approval") {
      const action =
        resolution.action === "accept" ||
        resolution.action === "accept_for_session"
          ? resolution.action

View on GitHub (pinned to 01ad858492)

Solutions

  1. Serialize resolutions per requestId (single-flight: keep a map of in-flight promises and reuse the same promise).
  2. On catching this error, await the original in-flight resolution instead of re-calling; the first call will emit `runtime_request.resolved`.
  3. Wait for the `runtime_request.resolved` (or `.expired`/`.cancelled`) event for the requestId before considering it resolvable again.
  4. If the original submit threw, `settling` resets to false — retry only after observing the thrown error from the original caller.

Example fix

// before
await Promise.all([
  session.resolveRuntimeRequest({ requestId, turnId, resolution }),
  session.resolveRuntimeRequest({ requestId, turnId, resolution }), // throws 'already settling'
]);

// after
const inflight = new Map();
function resolveOnce(input) {
  if (!inflight.has(input.requestId)) {
    inflight.set(input.requestId,
      session.resolveRuntimeRequest(input).finally(() => inflight.delete(input.requestId)));
  }
  return inflight.get(input.requestId);
}
await Promise.all([resolveOnce({ requestId, turnId, resolution }), resolveOnce({ requestId, turnId, resolution })]);
Defensive patterns

Strategy: try-catch

Validate before calling

if (inflightResolutions.has(requestId)) return inflightResolutions.get(requestId);

Type guard

function isSettling(session, requestId) {
  return session.pendingRuntimeRequests().length > 0; // cannot observe settling directly; use single-flight instead
}

Try / catch

try {
  await session.resolveRuntimeRequest({ requestId, turnId, resolution });
} catch (e) {
  if (e.message.includes('is already settling')) {
    await inflightResolutions.get(requestId); // await the original attempt
  } else throw e;
}

Prevention

When it happens

Trigger: Two concurrent `resolveRuntimeRequest` calls for the same requestId — e.g. user double-clicks Approve, a retry timer fires while the first request awaits `api(...)`, or an auto-resolver and a manual resolver race.

Common situations: Slow OpenCode server makes the first reply take seconds, inviting a timeout-based retry that collides; parallel promise chains both responding to the same native question; a supervisor and the UI both answering a permission prompt.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/fd63a5871134ad2e. Report an issue: GitHub.