{"record":{"id":"0965f198253717c5","repo":"paperclipai/paperclip","slug":"a-different-semantic-result-was-already-committed","errorCode":null,"errorMessage":"A different semantic result was already committed","messagePattern":"A different semantic result was already committed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts","lineNumber":911,"sourceCode":"        (tool === PRP_BLOCK_TOOL_NAME &&\n          validation.result.reportedWorkDisposition !== \"blocked\") ||\n        (tool === PRP_COMPLETION_TOOL_NAME &&\n          validation.result.reportedWorkDisposition === \"blocked\")\n      )\n        throw new Error(\n          \"Semantic result disposition does not match the terminal tool\",\n        );\n      if (\n        validation.result.completionClaim.contractRevision !==\n        this.#taskEnvelope.completionContract.revision\n      ) {\n        throw new Error(\n          \"Semantic result completion contract revision does not match\",\n        );\n      }\n      const fingerprint = canonicalJson(validation.result);\n      if (this.#resultFingerprint && this.#resultFingerprint !== fingerprint)\n        throw new Error(\"A different semantic result was already committed\");\n      if (!this.#resultFingerprint) {\n        this.#result = structuredClone(validation.result);\n        this.#resultFingerprint = fingerprint;\n        this.#resultCallId = call.callId;\n        this.#resultTurnId = turnId;\n        this.#semanticResultTextBoundary = this.#completedTextParts.length;\n        this.#emit(\"run.result.proposed\", validation.result, {\n          turnId,\n          itemId: call.callId,\n        });\n      }\n      this.#emit(\n        \"item.completed\",\n        {\n          kind: \"dynamicToolCall\",\n          item: {\n            type: \"tool_result\",\n            id: call.callId,","sourceCodeStart":893,"sourceCodeEnd":929,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts#L893-L929","documentation":"Once a semantic result is accepted, its canonical JSON fingerprint is stored (`#resultFingerprint`). A later terminal tool call in the same run must produce an identical result; a differing result throws this error. The first committed result wins, so the agent cannot change its final answer after a result has been proposed (`run.result.proposed`).","triggerScenarios":"A second `prp_completion`/`prp_block` dispatch on the same run whose validated result differs in any field from the already-committed one — e.g. the agent re-emits its result with an updated summary, different timestamps, or altered claim fields after the first was accepted.","commonSituations":"Model natively retries the terminal tool call after a transient error and rewords the summary; nondeterministic fields (timestamps, token counts) differ between attempts; a resumed/replayed event stream re-delivers the result with regenerated volatile fields.","solutions":["Make the agent's semantic result deterministic: exclude or fix volatile fields (timestamps, usage numbers) so re-emissions are byte-identical after canonical JSON.","If the agent genuinely needs to change its result, start a new run (new `attachRun`) rather than re-committing in the same run — `attachRun` resets the fingerprint state.","Inspect the first committed result via `await session.snapshot()` (`semanticResult`) and compare with the new call's arguments to see which field diverged.","Identical repeat calls are fine: they return `{ accepted: true }` without throwing — only differing results conflict, so ensure retries echo the exact original arguments."],"exampleFix":"// before: second call changes the summary\nprp_completion({ summary: \"done, also fixed lint\", completionClaim: {...} }) // conflicts\n\n// after: replay must match the committed fingerprint exactly\nprp_completion({ summary: \"done\", completionClaim: {...} }) // identical -> accepted","handlingStrategy":"try-catch","validationCode":"const snap = await session.snapshot();\nconst committed = snap.semanticResult;\nif (committed && canonicalJson(newResult) !== committed.fingerprint) throw new ResultConflictError();","typeGuard":"function matchesCommitted(snap, result) { return !snap.semanticResult || canonicalJson(result) === snap.semanticResult.fingerprint; }","tryCatchPattern":"try {\n  await session.dispatchTool({ tool, callId, arguments });\n} catch (e) {\n  if (e.message === 'A different semantic result was already committed') {\n    // keep the first committed result (snapshot().semanticResult); ignore the divergent retry\n  } else throw e;\n}","preventionTips":["Make semantic results deterministic — exclude volatile fields like timestamps.","Retries must echo the exact original terminal arguments.","Use a new run (`attachRun`) instead of re-committing results in the same run.","Compare candidate results against `snapshot().semanticResult` before dispatch."],"tags":["semantic-result","conflict","idempotency","opencode"],"backgroundTag":"conflicting-update","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}