{"record":{"id":"21d8689f3bbb7a83","repo":"thedotmack/claude-mem","slug":"cloud-sync-must-be-configured-before-queuedelete","errorCode":null,"errorMessage":"cloud sync must be configured before queueDelete","messagePattern":"cloud sync must be configured before queueDelete","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":712,"sourceCode":"      const raw = error instanceof Error ? error.message : String(error);\n      const safe = this.token === '' ? raw : raw.split(this.token).join('[REDACTED]');\n      this.hubStatus = {\n        checkedAt,\n        reachable: false,\n        epoch: null,\n        headSeq: null,\n        projectedSeq: null,\n        error: safe,\n      };\n    }\n  }\n\n  /**\n   * Queue and apply a local content deletion atomically. The tombstone stays\n   * durable in sync_content_outbox until Hub projection acknowledges it.\n   */\n  queueDelete(kind: RowKind, originLocalId: string, deletedAt = new Date().toISOString()): string {\n    if (!this.isActive()) throw new Error('cloud sync must be configured before queueDelete');\n    assertCanonicalDecimal(originLocalId);\n    const entityId = stableDocumentId(kind, this.deviceId, originLocalId);\n    const table = TABLE_BY_KIND[kind];\n    let entityRev = '0';\n    const tx = this.db.transaction(() => {\n      const revisions: string[] = [];\n      const local = this.db.prepare(\n        `SELECT CAST(sync_rev AS TEXT) AS entity_rev\n         FROM ${table} WHERE id = ? AND origin_device_id IS NULL`\n      ).get(originLocalId) as { entity_rev: string } | undefined;\n      if (local) revisions.push(local.entity_rev);\n      const head = this.db.prepare(\n        'SELECT entity_rev FROM sync_entity_heads WHERE entity_id = ?'\n      ).get(entityId) as { entity_rev: string } | undefined;\n      if (head) revisions.push(head.entity_rev);\n      const pending = this.db.prepare(\n        'SELECT entity_rev FROM sync_content_outbox WHERE entity_id = ?'\n      ).all(entityId) as Array<{ entity_rev: string }>;","sourceCodeStart":694,"sourceCodeEnd":730,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/CloudSync.ts#L694-L730","documentation":"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.","triggerScenarios":"Calling queueDelete(kind, originLocalId, deletedAt?) on a CloudSync instance before configure()/activation has completed, or after sync was deactivated/torn down.","commonSituations":"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.","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."],"exampleFix":"// before\nsync.queueDelete('observation', localId);\n// after\nif (!sync.isActive()) await sync.configureAndStart();\nsync.queueDelete('observation', localId);","handlingStrategy":"validation","validationCode":"if (sync.isActive()) sync.queueDelete(kind, originLocalId);","typeGuard":null,"tryCatchPattern":"try { sync.queueDelete(kind, id); } catch (e) { if (e.message.includes('must be configured')) await sync.configureAndStart(); }","preventionTips":["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."],"tags":["sync","lifecycle","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}