{"record":{"id":"fd63a5871134ad2e","repo":"paperclipai/paperclip","slug":"opencode-request-input-requestid-is-already-set","errorCode":null,"errorMessage":"OpenCode request ${input.requestId} is already settling","messagePattern":"OpenCode request (.+?) is already settling","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts","lineNumber":643,"sourceCode":"    if (!pending)\n      throw new Error(\n        `OpenCode request ${input.requestId} is no longer pending`,\n      );\n    if (\n      pending.request.turnId !== input.turnId ||\n      this.#activeTurnId !== input.turnId\n    ) {\n      throw new Error(\n        `OpenCode request ${input.requestId} belongs to a stale turn`,\n      );\n    }\n    const resolution = parseHarnessRuntimeRequestResolution(\n      pending.request.requestKind,\n      input.resolution,\n      pending.request.input,\n    );\n    if (pending.settling)\n      throw new Error(\n        `OpenCode request ${input.requestId} is already settling`,\n      );\n    pending.settling = true;\n    const submit = async (operation: Promise<unknown>) => {\n      try {\n        await operation;\n      } catch (error) {\n        if (this.#pendingRuntimeRequests.get(input.requestId) === pending)\n          pending.settling = false;\n        throw error;\n      }\n    };\n    const workspace = `directory=${encodeURIComponent(this.#workingDirectory)}`;\n    if (pending.request.requestKind === \"permission_approval\") {\n      const action =\n        resolution.action === \"accept\" ||\n        resolution.action === \"accept_for_session\"\n          ? resolution.action","sourceCodeStart":625,"sourceCodeEnd":661,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts#L625-L661","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Serialize resolutions per requestId (single-flight: keep a map of in-flight promises and reuse the same promise).","On catching this error, await the original in-flight resolution instead of re-calling; the first call will emit `runtime_request.resolved`.","Wait for the `runtime_request.resolved` (or `.expired`/`.cancelled`) event for the requestId before considering it resolvable again.","If the original submit threw, `settling` resets to false — retry only after observing the thrown error from the original caller."],"exampleFix":"// before\nawait Promise.all([\n  session.resolveRuntimeRequest({ requestId, turnId, resolution }),\n  session.resolveRuntimeRequest({ requestId, turnId, resolution }), // throws 'already settling'\n]);\n\n// after\nconst inflight = new Map();\nfunction resolveOnce(input) {\n  if (!inflight.has(input.requestId)) {\n    inflight.set(input.requestId,\n      session.resolveRuntimeRequest(input).finally(() => inflight.delete(input.requestId)));\n  }\n  return inflight.get(input.requestId);\n}\nawait Promise.all([resolveOnce({ requestId, turnId, resolution }), resolveOnce({ requestId, turnId, resolution })]);","handlingStrategy":"try-catch","validationCode":"if (inflightResolutions.has(requestId)) return inflightResolutions.get(requestId);","typeGuard":"function isSettling(session, requestId) {\n  return session.pendingRuntimeRequests().length > 0; // cannot observe settling directly; use single-flight instead\n}","tryCatchPattern":"try {\n  await session.resolveRuntimeRequest({ requestId, turnId, resolution });\n} catch (e) {\n  if (e.message.includes('is already settling')) {\n    await inflightResolutions.get(requestId); // await the original attempt\n  } else throw e;\n}","preventionTips":["Serialize resolutions with a single-flight promise map per requestId.","Disable UI controls once a resolution is submitted.","Wait for `runtime_request.resolved` events instead of polling.","Avoid timeout-based retries that overlap an in-flight reply."],"tags":["runtime-request","concurrency","double-submit","opencode"],"backgroundTag":"double-submit","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}