thedotmack/claude-mem · error · Error

projection_error: invalid projected through_seq

Error message

projection_error: invalid projected through_seq

What it means

advanceProjectionCheckpoint() validates that throughSeq is a legal new checkpoint: it must not regress below the current projected_seq and must not exceed the log's head_seq. A through value outside [projected, head] would either rewind durable progress or checkpoint operations that were never appended, so the DO throws this projectionError.

Solutions

  1. Always pass the through_seq exactly as returned by getProjectionPage (page.through_seq) rather than a locally computed value.
  2. Before advancing, compare against getProjectionState(): skip the call if projected_seq >= your through (already done), and re-page if your page is stale.
  3. Use the through_seq echoed by the current lease/page cycle; discard any through values from before the last epoch change.
  4. If through > head persists, your page target was invalid — re-clamp target to head_seq and re-acquire the lease.

Example fix

// before
await hub.advanceProjectionCheckpoint(token, epoch, from, myLocalAppliedSeq);
// after
const st = await hub.getProjectionState();
if (st.projected_seq >= page.through_seq) return; // idempotent skip
await hub.advanceProjectionCheckpoint(token, epoch, page.from_seq_exclusive, page.through_seq);
Defensive patterns

Strategy: validation

Validate before calling

const st = await hub.getProjectionState();
if (throughSeq < st.projected_seq || throughSeq > st.head_seq) {
  throw new RangeError('through_seq outside [projected, head]');
}

Type guard

function isLegalThrough(through: string, projected: string, head: string): boolean {
  return compareCanonicalDecimals(through, projected) >= 0 &&
         compareCanonicalDecimals(through, head) <= 0;
}

Try / catch

try {
  await hub.advanceProjectionCheckpoint(token, epoch, from, through);
} catch (e) {
  if (String(e).includes('invalid projected through_seq')) {
    const st = await hub.getProjectionState();
    if (st.projected_seq >= through) return; // already advanced; skip
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling advanceProjectionCheckpoint with throughSeq < current projected_seq (replayed/stale page) or throughSeq > headSeq() (caller invented a target ahead of the log); also a through from a previous epoch's numbering.

Common situations: Projector retrying a page after checkpoint already advanced (regression case); caller computing through from its own local applied-op counter that drifted from hub head; epoch rotation making old sequence numbering invalid.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/6483bc120534034e. Report an issue: GitHub.

Appendix: source

Thrown at workers/sync-hub/src/do/SyncHub.ts:758

	advanceProjectionCheckpoint(
		leaseToken: string,
		epoch: string,
		fromSeqExclusive: string,
		throughSeq: string,
		now = Date.now(),
	): ProjectionState {
		this.ctx.storage.transactionSync(() => {
			// Token check and checkpoint compare-and-set are fenced in the same
			// synchronous transaction. A timed-out predecessor can never replay
			// after a successor has acquired a fresh token.
			this.assertLease(leaseToken, now);
			if (epoch !== this.meta("epoch")) throw projectionError("epoch mismatch");
			const expected = assertCanonicalDecimal(fromSeqExclusive);
			const through = assertCanonicalDecimal(throughSeq);
			const current = this.projectedSeq();
			if (current !== expected) throw projectionError("checkpoint compare-and-set mismatch");
			if (compareCanonicalDecimals(through, current) < 0 || compareCanonicalDecimals(through, this.headSeq()) > 0) {
				throw projectionError("invalid projected through_seq");
			}
			this.setMeta("projected_seq", through);
			this.setMeta("projection_lease_expires_at", this.leaseExpiry(this.leaseNow(now)));
		});
		return this.getProjectionState();
	}

	releaseProjectionLease(leaseToken: string): void {
		if (this.metaOptional("projection_lease_token") !== leaseToken) return;
		this.ctx.storage.transactionSync(() => {
			this.deleteMeta("projection_lease_token");
			this.deleteMeta("projection_lease_expires_at");
		});
	}

	getProjectionState(): ProjectionState {
		return {
			protocol_version: 1,

View on GitHub (pinned to d8bc9755e7)