paperclipai/paperclip · error
A different semantic result was already committed
Error message
A different semantic result was already committed
What it means
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`).
Source
Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:911
(tool === PRP_BLOCK_TOOL_NAME &&
validation.result.reportedWorkDisposition !== "blocked") ||
(tool === PRP_COMPLETION_TOOL_NAME &&
validation.result.reportedWorkDisposition === "blocked")
)
throw new Error(
"Semantic result disposition does not match the terminal tool",
);
if (
validation.result.completionClaim.contractRevision !==
this.#taskEnvelope.completionContract.revision
) {
throw new Error(
"Semantic result completion contract revision does not match",
);
}
const fingerprint = canonicalJson(validation.result);
if (this.#resultFingerprint && this.#resultFingerprint !== fingerprint)
throw new Error("A different semantic result was already committed");
if (!this.#resultFingerprint) {
this.#result = structuredClone(validation.result);
this.#resultFingerprint = fingerprint;
this.#resultCallId = call.callId;
this.#resultTurnId = turnId;
this.#semanticResultTextBoundary = this.#completedTextParts.length;
this.#emit("run.result.proposed", validation.result, {
turnId,
itemId: call.callId,
});
}
this.#emit(
"item.completed",
{
kind: "dynamicToolCall",
item: {
type: "tool_result",
id: call.callId,View on GitHub (pinned to 01ad858492)
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.
Example fix
// before: second call changes the summary
prp_completion({ summary: "done, also fixed lint", completionClaim: {...} }) // conflicts
// after: replay must match the committed fingerprint exactly
prp_completion({ summary: "done", completionClaim: {...} }) // identical -> accepted Defensive patterns
Strategy: try-catch
Validate before calling
const snap = await session.snapshot(); const committed = snap.semanticResult; if (committed && canonicalJson(newResult) !== committed.fingerprint) throw new ResultConflictError();
Type guard
function matchesCommitted(snap, result) { return !snap.semanticResult || canonicalJson(result) === snap.semanticResult.fingerprint; } Try / catch
try {
await session.dispatchTool({ tool, callId, arguments });
} catch (e) {
if (e.message === 'A different semantic result was already committed') {
// keep the first committed result (snapshot().semanticResult); ignore the divergent retry
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OpenCode request ${input.requestId} is no longer pending
- Invalid semantic result
- Semantic result disposition does not match the terminal tool
- Semantic result completion contract revision does not match
- idempotency_key_conflict
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/0965f198253717c5.
Report an issue: GitHub.