thedotmack/claude-mem · error · Error
cloud sync must be configured before queueDelete
Error message
cloud sync must be configured before queueDelete
What it means
CloudSync.queueDelete refuses to run when the sync engine is not in an active, fully configured state (isActive() is false). Deleting content is a two-phase operation — a durable tombstone written to sync_content_outbox followed by hub projection — so it requires a configured cloud sync connection. The library fails closed rather than silently dropping a deletion that would never propagate.
Solutions
- Ensure cloud sync is configured (hub URL, token, device id) and activated before any queueDelete call.
- Check CLAUDE_MEM_CLOUD_SYNC_HUB_URL and related settings in ~/.claude-mem/settings.json or ~/.claude-mem/.env.
- Gate deletion UI/logic on the sync engine's active/ready state instead of calling blindly.
- If sync should run, reinitialize or restart the CloudSync instance so isActive() returns true.
Example fix
// before
sync.queueDelete('observation', localId);
// after
if (!sync.isActive()) await sync.configureAndStart();
sync.queueDelete('observation', localId); Defensive patterns
Strategy: validation
Validate before calling
if (sync.isActive()) sync.queueDelete(kind, originLocalId);
Try / catch
try { sync.queueDelete(kind, id); } catch (e) { if (e.message.includes('must be configured')) await sync.configureAndStart(); } Prevention
- Await sync activation at app startup before exposing delete operations.
- Centralize all sync writes behind a helper that checks isActive() first.
- Disable delete UI when sync is not configured.
When it happens
Trigger: Calling queueDelete(kind, originLocalId, deletedAt?) on a CloudSync instance before configure()/activation has completed, or after sync was deactivated/torn down.
Common situations: App code invokes deletion helpers at startup before the sync config (hub URL, token, device id) is loaded; sync is disabled by missing env vars like CLAUDE_MEM_CLOUD_SYNC_HUB_URL; the sync worker was stopped but a deletion path still fires.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Backfill failed
- Cannot process observations: memorySessionId not yet…
- canonical content
- chroma-mcp connection cancelled during shutdown
- Chroma unavailable before write; leaving documents unsynced
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/21d8689f3bbb7a83.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:712
const raw = error instanceof Error ? error.message : String(error);
const safe = this.token === '' ? raw : raw.split(this.token).join('[REDACTED]');
this.hubStatus = {
checkedAt,
reachable: false,
epoch: null,
headSeq: null,
projectedSeq: null,
error: safe,
};
}
}
/**
* Queue and apply a local content deletion atomically. The tombstone stays
* durable in sync_content_outbox until Hub projection acknowledges it.
*/
queueDelete(kind: RowKind, originLocalId: string, deletedAt = new Date().toISOString()): string {
if (!this.isActive()) throw new Error('cloud sync must be configured before queueDelete');
assertCanonicalDecimal(originLocalId);
const entityId = stableDocumentId(kind, this.deviceId, originLocalId);
const table = TABLE_BY_KIND[kind];
let entityRev = '0';
const tx = this.db.transaction(() => {
const revisions: string[] = [];
const local = this.db.prepare(
`SELECT CAST(sync_rev AS TEXT) AS entity_rev
FROM ${table} WHERE id = ? AND origin_device_id IS NULL`
).get(originLocalId) as { entity_rev: string } | undefined;
if (local) revisions.push(local.entity_rev);
const head = this.db.prepare(
'SELECT entity_rev FROM sync_entity_heads WHERE entity_id = ?'
).get(entityId) as { entity_rev: string } | undefined;
if (head) revisions.push(head.entity_rev);
const pending = this.db.prepare(
'SELECT entity_rev FROM sync_content_outbox WHERE entity_id = ?'
).all(entityId) as Array<{ entity_rev: string }>;View on GitHub (pinned to d8bc9755e7)