{"record":{"id":"71f577c2f556a7b4","repo":"thedotmack/claude-mem","slug":"cloud-sync-identity-unavailable-refusing-an-unrep","errorCode":null,"errorMessage":"cloud sync identity unavailable; refusing an unreplicated delete","messagePattern":"cloud sync identity unavailable; refusing an unreplicated delete","errorType":"http","errorClass":null,"httpStatus":503,"severity":"warning","filePath":"src/services/worker/http/routes/DataRoutes.ts","lineNumber":361,"sourceCode":"      this.badRequest(res, 'id must be a positive canonical decimal string');\n      return;\n    }\n\n    const store = this.dbManager.getSessionStore();\n    const row = store.db.prepare(`\n      SELECT CAST(id AS TEXT) AS id FROM ${table}\n      WHERE id = ? AND origin_device_id IS NULL\n    `).get(originLocalId) as { id: string } | undefined;\n    if (!row) {\n      this.notFound(res, `${kind} #${originLocalId} not found`);\n      return;\n    }\n\n    const cloudSync = this.dbManager.getCloudSync();\n    let entityRev: string | null = null;\n    if (cloudSync?.isConfigured()) {\n      if (!cloudSync.status().deviceId) {\n        res.status(503).json({ error: 'cloud sync identity unavailable; refusing an unreplicated delete' });\n        return;\n      }\n      entityRev = cloudSync.queueDelete(kind, originLocalId);\n    } else {\n      // A row with an acknowledged entity head must never be silently deleted\n      // while its sync identity is unavailable: that would strand replicas.\n      const acknowledged = store.db.prepare(`\n        SELECT 1 AS found FROM sync_entity_heads\n        WHERE kind = ? AND origin_local_id = ? LIMIT 1\n      `).get(kind, originLocalId) as { found: number } | undefined;\n      if (acknowledged) {\n        res.status(503).json({ error: 'cloud sync unavailable; refusing an unreplicated delete' });\n        return;\n      }\n      store.db.prepare(\n        `DELETE FROM ${table} WHERE id = ? AND origin_device_id IS NULL`\n      ).run(originLocalId);\n    }","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/8bc631a71a487424b866756e43a6efa4574cc66b/src/services/worker/http/routes/DataRoutes.ts#L343-L379","documentation":"HTTP 503 returned by the DataRoutes delete handler when cloud sync IS configured but has no deviceId yet — the device identity that tags replicated mutations has not been established. Deleting a local-origin row without that identity would emit a delete other replicas could not attribute, so the worker refuses and asks the client to retry later.","triggerScenarios":"DELETE on a local-origin session/observation immediately after configuring cloud sync, before the first successful sync exchange provisions the device identity; or after an identity reset where status().deviceId is null again.","commonSituations":"Fresh installs where sync credentials were entered but the initial handshake failed (network down, bad endpoint); race between the settings API enabling sync and the sync engine registering; wiping local identity state under ~/.claude-mem.","solutions":["Retry the delete after the first successful sync run establishes the deviceId","Check sync status (cloudSync.status()) — if deviceId stays null, inspect sync credentials/connectivity","If sync was enabled by mistake, disable cloud sync so the delete takes the local-only path"],"exampleFix":"// before\nawait fetch(`${base}/api/data/session/${id}`, { method: 'DELETE' }); // 503 identity unavailable\n\n// after\nawait waitForSyncIdentity(); // poll status endpoint until deviceId present\nawait fetch(`${base}/api/data/session/${id}`, { method: 'DELETE' });","handlingStrategy":"retry","validationCode":"async function syncIdentityReady(base: string): Promise<boolean> {\n  // before deleting replicated data, confirm the device identity exists\n  const s = await (await fetch(`${base}/api/settings`)).json();\n  return Boolean(s.cloudSync?.deviceId);\n}","typeGuard":"function isSyncIdentityUnavailable(body: unknown, status: number): boolean {\n  return status === 503 && typeof body === 'object' && body !== null &&\n    String((body as { error?: string }).error).includes('identity unavailable');\n}","tryCatchPattern":"for (let i = 0; i < 5; i++) {\n  const res = await del(`${base}/api/data/${kind}/${id}`);\n  if (res.status !== 503) return handle(res);\n  await sleep(2000); // first sync exchange provisions deviceId\n}\nthrow new Error('sync identity never became available');","preventionTips":["After enabling cloud sync, wait for the first successful sync before issuing deletes","Monitor cloudSync.status().deviceId and alert when it stays null with sync configured","Automate testing of the delete path only on post-first-sync states"],"tags":["http-503","cloud-sync","delete","device-identity","replication"],"backgroundTag":"sync-identity-unavailable","analyzedSha":"8bc631a71a487424b866756e43a6efa4574cc66b","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}