thedotmack/claude-mem · warning
cloud sync unavailable; refusing an unreplicated delete
Error message
cloud sync unavailable; refusing an unreplicated delete
What it means
HTTP 503 from the same DataRoutes delete handler, on the opposite branch: cloud sync is NOT configured, but the row has an entry in sync_entity_heads — proof another device has already acknowledged it. Deleting it now would strand replicas that still reference the entity, so the worker refuses the unreplicated delete. This is a data-integrity guard, not a transient error.
Source
Thrown at src/services/worker/http/routes/DataRoutes.ts:373
}
const cloudSync = this.dbManager.getCloudSync();
let entityRev: string | null = null;
if (cloudSync?.isConfigured()) {
if (!cloudSync.status().deviceId) {
res.status(503).json({ error: 'cloud sync identity unavailable; refusing an unreplicated delete' });
return;
}
entityRev = cloudSync.queueDelete(kind, originLocalId);
} else {
// A row with an acknowledged entity head must never be silently deleted
// while its sync identity is unavailable: that would strand replicas.
const acknowledged = store.db.prepare(`
SELECT 1 AS found FROM sync_entity_heads
WHERE kind = ? AND origin_local_id = ? LIMIT 1
`).get(kind, originLocalId) as { found: number } | undefined;
if (acknowledged) {
res.status(503).json({ error: 'cloud sync unavailable; refusing an unreplicated delete' });
return;
}
store.db.prepare(
`DELETE FROM ${table} WHERE id = ? AND origin_device_id IS NULL`
).run(originLocalId);
}
res.json({ success: true, id: originLocalId, kind, entity_rev: entityRev });
}
private handleGetStats = this.wrapHandler((req: Request, res: Response): void => {
const db = this.dbManager.getSessionStore().db;
const packageRoot = getPackageRoot();
const packageJsonPath = path.join(packageRoot, 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const version = packageJson.version;
View on GitHub (pinned to 8bc631a71a)
Solutions
- Reconfigure cloud sync so the delete replicates through queueDelete (the intended path)
- If going local-only permanently is deliberate, clear the stale sync_entity_heads rows for that entity or wipe the sync metadata — accepting other replicas keep their copy
- Verify you actually want the delete replicated: the guard exists to prevent silent divergence
Example fix
-- before: delete refused with 503 because entity head exists
DELETE /api/data/session/42
-- after: re-enable sync first, then delete replicates
-- (worker settings) cloudSync.configure({...})
DELETE /api/data/session/42 --> { success: true, entity_rev: "..." } Defensive patterns
Strategy: retry
Validate before calling
async function deleteWillReplicate(base: string, kind: string, id: number): Promise<boolean> {
// rough client-side check: sync configured OR no acknowledged heads
const s = await (await fetch(`${base}/api/settings`)).json();
return Boolean(s.cloudSync?.configured);
} Type guard
function isUnreplicatedDeleteRefused(body: unknown, status: number): boolean {
return status === 503 && typeof body === 'object' && body !== null &&
String((body as { error?: string }).error).includes('unreplicated delete');
} Try / catch
const res = await del(url);
if (res.status === 503 && (await res.json()).error.includes('cloud sync unavailable')) {
// deliberate policy decision required: reconfigure sync, or purge sync_entity_heads
await notifyUser('delete blocked to protect replicas');
} Prevention
- Keep cloud sync configured on any machine whose data has ever replicated
- When decommissioning sync intentionally, plan the sync_entity_heads cleanup as part of the runbook
- Never auto-retry this 503 — it encodes a policy refusal, not a transient condition
When it happens
Trigger: DELETE on a local-origin row that previously replicated (an entity head exists) after cloud sync was disabled, deconfigured, or its credentials expired; deleting data on a secondary machine whose sync setup was removed but whose database still carries acknowledged heads.
Common situations: User turns cloud sync off to 'go local' then tries to prune old sessions; sync token revoked server-side so isConfigured() is false while sync_entity_heads still holds rows; switching a machine between sync accounts.
Related errors
- cloud sync identity unavailable; refusing an unreplicated de
- cloud sync canonical payload: ${name} must be a non-negative
- SyncApply: sequence gap (expected ${incrementCanonicalDecima
- cloud sync canonical payload: ${name} must be stored JSON te
- cloud sync canonical payload: ${name} is not valid JSON
AI-assisted analysis of thedotmack/claude-mem@8bc631a71a (2026-08-20).
Data as JSON: /api/errors/7962282ab6a60f38.
Report an issue: GitHub.