thedotmack/claude-mem · error · Error

sync hub status: response requires decimal-string…

Error message

sync hub status: response requires decimal-string epoch/head_seq/projected_seq

What it means

The protocol requires epoch, head_seq and projected_seq to be strings containing canonical decimal integers (arbitrary precision). probeHubStatus throws 'sync hub status: response requires decimal-string epoch/head_seq/projected_seq' when any field is missing, not a string, or not a canonical decimal, before further arithmetic via assertCanonicalDecimal/compareCanonicalDecimals.

Solutions

  1. Fix the hub to serialize epoch/head_seq/projected_seq as decimal strings, e.g. String(seq).
  2. Reject/handle numeric sequences at the client edge by normalizing before the probe if you control both sides.
  3. Validate payloads in hub tests with assertCanonicalDecimal to catch non-canonical forms early.
  4. Confirm field names are exactly epoch, head_seq, projected_seq (lowercase, snake_case).

Example fix

// before (hub)
res.json({ protocol_version: 2, epoch: 1726000000, head_seq: 42, projected_seq: 42 });
// after
res.json({ protocol_version: 2, epoch: '1726000000', head_seq: '42', projected_seq: '42' });
Defensive patterns

Strategy: validation

Validate before calling

function isCanonicalDecimal(v: unknown): v is string {
  return typeof v === 'string' && /^\d+$/.test(v) && (v === '0' || !v.startsWith('0'));
}
if (!isCanonicalDecimal(body.epoch) || !isCanonicalDecimal(body.head_seq) || !isCanonicalDecimal(body.projected_seq)) {
  throw new Error('hub seq fields must be canonical decimal strings');
}

Type guard

function isDecimalStringSeq(v: unknown): v is string {
  return typeof v === 'string' && /^\d+$/.test(v);
}

Try / catch

try {
  const status = await cloudSync.statusWithHubProbe();
} catch (e) {
  if (e.message.includes('decimal-string epoch/head_seq/projected_seq')) {
    logger.error('hub serializes seq counters in wrong format (numbers/non-canonical strings)', { raw: lastRawBody });
  } else throw e;
}

Prevention

When it happens

Trigger: Hub returns these fields as numbers instead of strings, omits one of them, uses non-canonical forms (leading '+', leading zeros, whitespace, empty string), or sends null.

Common situations: A hub implementation serializing seq counters as JSON numbers, a hand-rolled hub using different field names or formats, JSON fixtures written by hand with numeric epochs.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/services/sync/CloudSync.ts:677

      let parsed: unknown;
      try {
        parsed = await response.json();
      } catch {
        throw new Error('sync hub status: response is not JSON');
      }
      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error('sync hub status: response must be an object');
      }
      const record = parsed as Record<string, unknown>;
      if (record.protocol_version !== 2) {
        throw new Error('sync hub status: response requires protocol_version 2');
      }
      if (
        typeof record.epoch !== 'string'
        || typeof record.head_seq !== 'string'
        || typeof record.projected_seq !== 'string'
      ) {
        throw new Error('sync hub status: response requires decimal-string epoch/head_seq/projected_seq');
      }
      const epoch = assertCanonicalDecimal(record.epoch, { positive: true });
      const headSeq = assertCanonicalDecimal(record.head_seq);
      const projectedSeq = assertCanonicalDecimal(record.projected_seq);
      if (compareCanonicalDecimals(projectedSeq, headSeq) > 0) {
        throw new Error('sync hub status: projected_seq exceeds head_seq');
      }
      this.hubStatus = {
        checkedAt,
        reachable: true,
        epoch,
        headSeq,
        projectedSeq,
        error: null,
      };
    } catch (error) {
      const raw = error instanceof Error ? error.message : String(error);
      const safe = this.token === '' ? raw : raw.split(this.token).join('[REDACTED]');

View on GitHub (pinned to d8bc9755e7)