ruvnet/ruflo · error · Error
executionId ${run.executionId} already records different evi
Error message
executionId ${run.executionId} already records different evidence What it means
The ledger is content-addressed: receiptId = sha256(canonicalJson(run)), and one executionId may map to exactly one receipt. recordRun throws when the same executionId arrives with a different canonical form — even a one-millisecond difference in startedAt yields a new receiptId and trips the guard. Exact retries converge on the existing receipt; changed evidence is refused rather than rewriting history.
Source
Thrown at v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts:61
*
* Exact retries converge on one receipt. Reusing an execution ID with changed
* evidence is refused rather than rewriting history. These receipts are local
* debugging evidence and cannot authorize enforce mode or release.
*/
export class InMemoryRunReceiptReference {
readonly referenceOnly = true;
private readonly receipts = new Map<string, RunReceipt>();
private readonly executionReceipts = new Map<string, string>();
constructor(private readonly now: () => number = Date.now) {}
recordRun(run: RunEvidence): RunReceipt {
validateRun(run);
const canonical = canonicalJson(run);
const receiptId = sha256(canonical);
const existingExecution = this.executionReceipts.get(run.executionId);
if (existingExecution !== undefined && existingExecution !== receiptId) {
throw new Error(`executionId ${run.executionId} already records different evidence`);
}
const existing = this.receipts.get(receiptId);
if (existing) return copy(existing);
const receipt: RunReceipt = {
...copy(run),
receiptId,
recordedAt: new Date(this.now()).toISOString(),
};
this.receipts.set(receiptId, receipt);
this.executionReceipts.set(run.executionId, receiptId);
return copy(receipt);
}
get(receiptId: string): RunReceipt | undefined {
const receipt = this.receipts.get(receiptId);
return receipt ? copy(receipt) : undefined;
}View on GitHub (pinned to fa13ee4ad6)
Solutions
- On retry/redelivery, replay the byte-identical RunEvidence — cache the first attempt's object and resend it
- For a genuinely new attempt or re-run, mint a fresh executionId (crypto.randomUUID())
- Treat the thrown conflict as 'already recorded' only after comparing the prior receipt's digests to confirm divergence vs. duplication
Example fix
// before: evidence rebuilt on every retry (new startedAt each time)
async function record(retry) {
const run = buildRunEvidence(); // fresh timestamps => different digest
await ledger.recordRun(run);
}
// after: build once, replay the identical object on retry
const run = buildRunEvidence();
await withRetry(() => ledger.recordRun(run)); Defensive patterns
Strategy: try-catch
Validate before calling
const firstAttempt = new Map<string, RunEvidence>();
function replayStableEvidence(run: RunEvidence): RunEvidence {
const prior = firstAttempt.get(run.executionId);
if (prior) return prior; // byte-identical retry keeps one receipt
firstAttempt.set(run.executionId, run);
return run;
} Try / catch
try {
ledger.recordRun(run);
} catch (error) {
if (error instanceof Error && error.message.includes('already records different evidence')) {
// same executionId with different evidence: either a duplicate re-run or a bug
const existing = ledger.getReceipt?.(run.executionId);
if (existing && existing.commandDigest === run.commandDigest) return existing; // benign duplicate
throw new Error(`executionId ${run.executionId} diverged between attempts; mint a new executionId`);
}
throw error;
} Prevention
- Build the RunEvidence object once per attempt and replay it verbatim on retries
- Mint a fresh executionId (crypto.randomUUID()) for every genuine re-execution
- Do not seed executionIds from counters that reset across restarts
When it happens
Trigger: Retrying recordRun after a transient failure while rebuilding the RunEvidence object (fresh timestamps each attempt); changing exitCode or digests for the 'same' execution; two workers recording different results under one executionId.
Common situations: Retry wrappers that regenerate timestamps instead of replaying the original payload; duplicate message delivery causing a real re-execution that reuses the executionId; executionIds seeded from counters that reset across process restarts.
Related errors
- Concurrent write detected on aggregate '${aggregateId}'. Res
- No pending handoff for issue ${issueId}
- Config file already exists: ${targetPath}. Use --force to ov
- ${label} must be an ISO timestamp
- run execution, session, and workload identity are required
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/334bf19cc7080e21.
Report an issue: GitHub.