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
- Run the diagnostic for the column named in the message: SELECT id FROM <table> WHERE typeof(<col>) <> 'integer' OR <col> < 0
- 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
- Re-run the sync push — it drains from where it stopped
- 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
- Write these columns only through code that coerces to non-negative integers
- Add typeof() checks to migrations and seed scripts
- Never hand-edit numeric sync columns in SQLite
- Run the pre-sync diagnostic query before enabling sync on an old DB
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
- cloud sync canonical payload: ${name} must be stored JSON te
- cloud sync canonical payload: ${name} must decode to an arra
- cloud sync canonical payload: ${name} is not valid JSON
- cloud sync canonical payload: ${name} must decode to an obje
- cloud sync unavailable; refusing an unreplicated delete
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/d47cf41298560496.
Report an issue: GitHub.