Hmbown/CodeWhale · error · Error
Runtime predecessor cursor does not match.
Error message
Runtime predecessor cursor does not match.
What it means
Runtime records optionally carry previous_seq, the cursor position of the immediately preceding record. If a record declares a previous_seq that doesn't equal the client's current cursor, a journal record was lost or skipped between the cursor and this record (sequence gaps are allowed, but the declared predecessor must match). followRuntime treats this as a data-loss violation and aborts.
Solutions
- Restart the follow from cursor 0 (drop sinceSeq) so the cursor tracks the journal from its true beginning.
- Check the Runtime server logs for journal rotation, compaction, or multiple writers on the same thread.
- If resuming from a saved cursor, re-derive the cursor from the last fully persisted record rather than an in-memory stale value.
- Upgrade Runtime if the server version has a known predecessor-ordering bug.
Example fix
// before: stale resumed cursor
followRuntime({ threadId, sinceSeq: savedCursor });
// after: re-derive from journal start when predecessor mismatch is suspected
followRuntime({ threadId, sinceSeq: 0 }); // or validate savedCursor against the journal's first record before resuming Defensive patterns
Strategy: validation
Validate before calling
function predecessorMatches(record, cursor) {
return record.previous_seq === undefined || record.previous_seq === cursor;
}
// before resuming: verify saved cursor against the journal's first record Type guard
const hasConsistentPredecessor = (r, cursor) => r.previous_seq === undefined || r.previous_seq === cursor;
Try / catch
try {
await followRuntime({ baseUrl, threadId, sinceSeq: savedCursor });
} catch (err) {
if (err.message === 'Runtime predecessor cursor does not match.') {
// journal changed under us: restart from the beginning
await followRuntime({ baseUrl, threadId, sinceSeq: 0 });
} else throw err;
} Prevention
- Re-derive resume cursors from the last persisted record, never from stale in-memory state.
- Ensure only one writer per thread journal on the server.
- Avoid resuming across journal rotation/compaction; restart from cursor 0 in that case.
When it happens
Trigger: A record arrives with record.previous_seq defined and !== cursor — e.g. the server skipped or compacted records since the client's sinceSeq position, the client resumed with a stale/partial cursor, or concurrent writers interleaved records.
Common situations: Resuming a follow with an old cursor against a journal that has been rotated/truncated; a server bug emitting records out of predecessor order; multiple Runtime processes writing the same thread journal concurrently; replay starting mid-stream after records were pruned.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- ${compactRuntimeError(response.status, body)}
- Invalid Runtime envelope.
- Invalid Runtime replay progress.
- Antigravity cloud-code is stream-only; blocking…
- Antigravity cloud-code stream ended without a text part
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/177fa5b8d5321f63.
Report an issue: GitHub.
Appendix: source
Thrown at pet/scripts/lib/pet-runtime.mjs:64
// while its idle body is still being read through the replacement below.
const body = response.body.pipeThrough(new TransformStream({ transform(chunk, controller) { refresh(); controller.enqueue(chunk); } }), { signal });
return new Response(body, { status: response.status, headers: response.headers });
};
try {
for await (const record of client.threadEvents(threadId, { sinceSeq: cursor, signal, includeProgress: true })) {
if (record?.event === 'stream.progress') {
if (record.thread_id !== threadId || record.seq !== cursor || !['live', 'replaying'].includes(record.state))
throw new Error('Invalid Runtime replay progress.');
connected = record.state === 'live';
continue;
}
if (!isCodewhaleRuntimeRecord(record) || record.thread_id !== threadId || !Number.isSafeInteger(record.seq) || record.seq < 0)
throw new Error('Invalid Runtime envelope.');
if (record.seq <= cursor) continue;
// Sequence numbers belong to Runtime, and need not be consecutive.
// Its predecessor cursor detects loss without inventing a new counter.
if (record.previous_seq !== undefined && record.previous_seq !== cursor)
throw new Error('Runtime predecessor cursor does not match.');
if (record.event !== 'item.delta') {
// The existing importer retains unfinished lifetimes and a recent
// recurrence window, not a second copy of the entire raw journal.
if (revision % 256 === 0) trace.prune(Date.now() - 16_000);
try { trace.append([redact(record)]); }
catch (error) { fatal = true; throw error; }
revision++;
}
cursor = record.seq; backoff = 250;
}
} catch (error) {
if ([400, 401, 403, 404, 405, 501].includes(error.status)) fatal = true;
if (!shutdown.signal.aborted) report(fatal
? 'Runtime input stopped: check the thread, authentication, SDK/Runtime replay-progress support, or retained input limit. Recording remains unobserved.'
: 'Runtime input interrupted; recording unobserved gaps while reconnecting from the last cursor.');
} finally {
connected = false; clearTimeout(idleTimer); client.fetchImpl = fetchImpl;
}View on GitHub (pinned to 433685b202)