ruvnet/ruflo · error · Error
run completedAt precedes startedAt
Error message
run completedAt precedes startedAt
What it means
After both timestamps parse successfully, validateRun requires Date.parse(completedAt) >= Date.parse(startedAt); a strictly earlier completion time throws. Equal timestamps are allowed. The ordering guarantee protects receipt semantics: a run cannot finish before it starts on the recording clock.
Source
Thrown at v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts:32
function timestamp(value: string, label: string): number {
const parsed = Date.parse(value);
if (!Number.isFinite(parsed)) throw new Error(`${label} must be an ISO timestamp`);
return parsed;
}
function validateRun(run: RunEvidence): void {
if (!run.executionId.trim() || !run.sessionId.trim() || !run.workloadId.trim()) {
throw new Error('run execution, session, and workload identity are required');
}
if (!DIGEST.test(run.sourceState.sourceStateId)) throw new Error('run sourceStateId is invalid');
if (!DIGEST.test(run.commandDigest) || !DIGEST.test(run.evidenceDigest)) {
throw new Error('run command and evidence digests must be canonical sha256 values');
}
if (!Number.isSafeInteger(run.exitCode)) throw new Error('run exitCode must be a safe integer');
const started = timestamp(run.startedAt, 'startedAt');
const completed = timestamp(run.completedAt, 'completedAt');
if (completed < started) throw new Error('run completedAt precedes startedAt');
if (
run.buildEvidence !== undefined
&& run.buildEvidence.sourceStateId !== run.sourceState.sourceStateId
) {
throw new Error('build evidence belongs to a different source state');
}
}
/**
* Unsigned, non-durable, content-addressed in-memory conformance ledger.
*
* 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>();View on GitHub (pinned to fa13ee4ad6)
Solutions
- Capture startedAt immediately before launching the command and completedAt immediately after it exits, on the same host
- If clocks may skew, reconcile offsets (NTP/PTP or a shared authority) before recording
- Pre-validate Date.parse(completedAt) >= Date.parse(startedAt) and fail fast locally with context
Example fix
// before: timestamps captured in different code paths
const startedAt = queueEnqueueTime; // earlier, from another host
const completedAt = new Date().toISOString();
// after: both stamped around execution on one clock
const startedAt = new Date().toISOString();
const completedAt = new Date().toISOString();
if (Date.parse(completedAt) < Date.parse(startedAt)) throw new Error('clock moved backwards'); Defensive patterns
Strategy: validation
Validate before calling
function assertRunInterval(run: { startedAt: string; completedAt: string }): void {
const started = Date.parse(run.startedAt);
const completed = Date.parse(run.completedAt);
if (!(Number.isFinite(started) && Number.isFinite(completed) && completed >= started)) {
throw new RangeError(`completedAt ${run.completedAt} precedes startedAt ${run.startedAt}`);
}
} Try / catch
try {
ledger.recordRun(run);
} catch (error) {
if (error instanceof Error && error.message === 'run completedAt precedes startedAt') {
// skew detected: re-stamp both from local clock preserving true duration
const duration = measuredDurationMs;
const end = Date.now();
run.completedAt = new Date(end).toISOString();
run.startedAt = new Date(end - duration).toISOString();
ledger.recordRun(run);
} else throw error;
} Prevention
- Stamp startedAt just before launching the command and completedAt just after it exits, on one host
- Keep machines time-synchronized (NTP) when evidence is assembled across hosts
- Assert completedAt >= startedAt locally before submission
When it happens
Trigger: startedAt and completedAt captured on different hosts whose clocks skew; completedAt copied from a previous run's record; async code that stamps completedAt before startedAt (misordered awaits); startedAt refreshed after a queue delay while completedAt stayed stale.
Common situations: Distributed runners where each machine stamps its own time; replaying recorded events out of order; timestamps taken from file mtimes or log lines written at different pipeline stages.
Related errors
- ${label} must be an ISO timestamp
- Invalid timestamp format: "${value}". Expected ISO 8601.
- run execution, session, and workload identity are required
- run sourceStateId is invalid
- run command and evidence digests must be canonical sha256 va
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/06db42f01db52339.
Report an issue: GitHub.