{"record":{"id":"a7179bd47aa78226","repo":"thedotmack/claude-mem","slug":"deviceid-must-be-non-empty","errorCode":null,"errorMessage":"deviceId must be non-empty","messagePattern":"deviceId must be non-empty","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workers/sync-hub/src/do/SyncHub.ts","lineNumber":376,"sourceCode":"\t\t\t\t\tconst attachment = ws.deserializeAttachment() as { device_id?: unknown } | null;\n\t\t\t\t\tattachedDevice = typeof attachment?.device_id === \"string\" ? attachment.device_id : null;\n\t\t\t\t} catch {}\n\t\t\t\tif (attachedDevice === originDeviceId) continue;\n\t\t\t\ttry { ws.send(frame); } catch {}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"sync-hub fan-out failed (advisory; push unaffected):\", error);\n\t\t}\n\t}\n\n\t// ---------------------------------------------------------------------\n\t// Canonical append path and client cursor reads.\n\t// ---------------------------------------------------------------------\n\n\tasync pushOps(deviceId: string, ops: PushOp[], deviceName: string | null = null): Promise<PushOutcome> {\n\t\tlet rows: ValidatedOp[];\n\t\ttry {\n\t\t\tif (typeof deviceId !== \"string\" || deviceId.length === 0) throw invalid(\"deviceId must be non-empty\");\n\t\t\tif (!Array.isArray(ops)) throw invalid(\"ops must be an array\");\n\t\t\trows = await Promise.all(ops.map(async (op, index) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = await parseCanonicalOperation(op);\n\t\t\t\t\tif (parsed.body.origin_device_id !== deviceId) {\n\t\t\t\t\t\tthrow new Error(\"origin_device_id does not match authenticated X-Device-Id\");\n\t\t\t\t\t}\n\t\t\t\t\treturn parsed;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow invalid(`ops[${index}] ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && error.message.startsWith(INVALID_OPS_PREFIX)) {\n\t\t\t\treturn { refused: true, error: error.message };\n\t\t\t}\n\t\t\tif (isDeviceLimitError(error)) return { refused: true, error: DEVICE_LIMIT_ERROR };\n\t\t\tthrow error;","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/workers/sync-hub/src/do/SyncHub.ts#L358-L394","documentation":"Validation guard in SyncHubDO.pushOps: the authenticated deviceId must be a non-empty string. pushOps appends canonical operations to the per-user sync hub, and every op is attributed to this device, so an empty/absent device identity cannot be accepted. The error is wrapped as an invalid outcome and returned as a refusal rather than crashing the DO.","triggerScenarios":"Calling pushOps with deviceId = '' or null coerced to string; HTTP POST /ops missing or blank X-Device-Id header; client that never persisted its device id before first sync.","commonSituations":"Fresh install where device registration was skipped, header name typo (x-deviceid vs x-device-id), client-side state wipe clearing the stored device id.","solutions":["Ensure the client registers a device and sends a non-empty X-Device-Id header on every push","Generate and persist a device id (e.g. crypto.randomUUID()) at app init before any sync","Return a clear 4xx to the client prompting re-registration instead of retrying the same call"],"exampleFix":"// before\nawait hub.push('', ops);\n// after\nif (!deviceId) deviceId = crypto.randomUUID();\nawait hub.push(deviceId, ops);","handlingStrategy":"validation","validationCode":"function assertDeviceId(id: unknown): string {\n  if (typeof id !== 'string' || id.length === 0) throw new Error('deviceId must be non-empty');\n  return id;\n}","typeGuard":"const isValidDeviceId = (id: unknown): id is string => typeof id === 'string' && id.length > 0;","tryCatchPattern":"try {\n  return await hub.push(deviceId, ops);\n} catch (e) {\n  if (String(e).includes('deviceId must be non-empty')) return registerDeviceAndRetry();\n  throw e;\n}","preventionTips":["Generate and persist a device id at first launch before any sync","Use one canonical header name (X-Device-Id) across client code","Never push before device registration completes"],"tags":["validation","sync","input-validation"],"backgroundTag":"empty-required-field","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"}