thedotmack/claude-mem · error · Error

cloud sync canonical payload: ${name} must be a non-negative

Error message

cloud sync canonical payload: ${name} must be a non-negative safe integer

What it means

decimalPayload builds canonical decimal-string fields of the sync op body from local SQLite columns (prompt_number, discovery_tokens, created_at_epoch). It throws when a value is present but is not a JS number, is not a safe integer, or is negative — the hub's wire format requires non-negative integer decimal strings, so a bad value means a local row drifted from the schema contract and the push aborts rather than emitting a malformed op.

Source

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

   * Drain SELECT: unsynced NATIVE rows only. Replica rows (origin_device_id
   * NOT NULL) are another device's corpus — pushing them under this device's
   * identity would fork origin attribution, so they are excluded here even
   * if something re-nulls their synced_at.
   */
  selectSql: string;
  /** Exact current-row read used by ack drift reconciliation. */
  selectOneSql: string;
  /** Op body per the SyncApply BODY FIELD MAPPING — values exactly as stored. */
  toBody: (r: LocalRow) => OpBody;
}

function decimalPayload(value: unknown, name: string, nullable = false): string | null {
  if (value === null || value === undefined) {
    if (nullable) return null;
    return '0';
  }
  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
    throw new Error(`cloud sync canonical payload: ${name} must be a non-negative safe integer`);
  }
  return String(value);
}

function jsonPayloadColumn(value: unknown, name: string, expected: 'array' | 'object'): unknown {
  if (value === null || value === undefined) return null;
  if (typeof value !== 'string') {
    throw new Error(`cloud sync canonical payload: ${name} must be stored JSON text`);
  }
  let parsed: unknown;
  try { parsed = JSON.parse(value); } catch {
    throw new Error(`cloud sync canonical payload: ${name} is not valid JSON`);
  }
  if (expected === 'array' && !Array.isArray(parsed)) {
    throw new Error(`cloud sync canonical payload: ${name} must decode to an array`);
  }
  if (expected === 'object' && (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))) {
    throw new Error(`cloud sync canonical payload: ${name} must decode to an object`);

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Run the diagnostic for the column named in the message: SELECT id FROM <table> WHERE typeof(<col>) <> 'integer' OR <col> < 0
  2. Repair the rows: UPDATE <table> SET <col> = CAST(<col> AS INTEGER) WHERE typeof(<col>) <> 'integer', and fix any negative values to their intended non-negative form
  3. Re-run the sync push — it drains from where it stopped
  4. Fix the writer that produced floats/text/negatives so it stores non-negative integers

Example fix

-- before
SELECT id FROM observations WHERE typeof(created_at_epoch) <> 'integer' OR created_at_epoch < 0;
-- after
UPDATE observations SET created_at_epoch = CAST(created_at_epoch AS INTEGER)
WHERE typeof(created_at_epoch) <> 'integer' AND created_at_epoch >= 0;
Defensive patterns

Strategy: validation

Validate before calling

-- Pre-sync check: no numeric-contract violations
SELECT 'observations' AS tbl, COUNT(*) AS bad FROM observations
 WHERE (discovery_tokens IS NOT NULL AND (typeof(discovery_tokens) <> 'integer' OR discovery_tokens < 0))
    OR (created_at_epoch IS NOT NULL AND (typeof(created_at_epoch) <> 'integer' OR created_at_epoch < 0));
-- bad = 0 before enabling cloud sync

Type guard

const isNonNegativeSafeInteger = (v: unknown): v is number =>
  typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;

Try / catch

try {
  await cloudSync.push();
} catch (e) {
  if (e instanceof Error && e.message.includes('non-negative safe integer')) {
    // message names the column; quarantine/repair those rows, then re-push
  } else throw e;
}

Prevention

When it happens

Trigger: A column such as created_at_epoch stored as TEXT or REAL (schema drift, manual edit, different writer version); a negative prompt_number inserted by a bug or hand-written SQL; a fractional epoch written by an older build.

Common situations: DB migrated or copied between versions with different column affinities; hand-edited SQLite; an older writer storing epoch in fractional seconds; a downstream tool writing raw values without validation.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/d47cf41298560496. Report an issue: GitHub.