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

  1. Make the agent's semantic result deterministic: exclude or fix volatile fields (timestamps, usage numbers) so re-emissions are byte-identical after canonical JSON.
  2. 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.
  3. Inspect the first committed result via `await session.snapshot()` (`semanticResult`) and compare with the new call's arguments to see which field diverged.
  4. 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

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


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