thedotmack/claude-mem · error · Error
invalid_ops: deviceId must be 1-128 characters
Error message
invalid_ops: deviceId must be 1-128 characters
What it means
invalid("deviceId must be 1-128 characters") is thrown by SyncHub.normalizeDeviceId when the trimmed deviceId is empty or longer than 128 characters. Device IDs are primary-key-like identifiers in the devices table, so the DO rejects any value outside the 1-128 character range before touching storage.
Solutions
- Ensure the client sends a non-empty deviceId of 1-128 characters after trimming.
- Generate IDs within bounds, e.g. crypto.randomUUID() (36 chars) instead of long composite strings.
- Trim/validate on the client before the call and show a field-level error.
- If legacy IDs are too long, hash them (e.g. SHA-256 hex) to fit under 128 chars.
Example fix
// before
const deviceId = `${userId}-${browser}-${window.location.href}`; // often > 128 chars
// after
const deviceId = crypto.randomUUID(); // 36 chars, always valid Defensive patterns
Strategy: validation
Validate before calling
function assertValidDeviceId(deviceId: string): string {
const id = deviceId.trim();
if (id.length === 0 || id.length > 128) {
throw new RangeError(`deviceId must be 1-128 characters, got ${id.length}`);
}
return id;
} Type guard
function isValidDeviceId(v: unknown): v is string {
return typeof v === "string" && v.trim().length >= 1 && v.trim().length <= 128;
} Try / catch
try {
await registerDevice(deviceId, name);
} catch (e) {
if (e instanceof Error && e.message.includes("deviceId must be 1-128 characters")) {
showFieldError("deviceId", "Must be 1-128 characters");
} else throw e;
} Prevention
- Generate IDs with crypto.randomUUID() instead of concatenating long strings.
- Validate and trim IDs at the client boundary before network calls.
- Never pass null/undefined into a string ID slot; check loading state first.
- Document the 1-128 limit wherever device IDs are produced.
When it happens
Trigger: Registering or touching a device with deviceId "" or only whitespace (trims to empty); a deviceId longer than 128 chars (e.g. a full URL, JWT, or concatenated UUID used as an ID); null/undefined coerced into a string form that fails the length check.
Common situations: Clients sending generated IDs from UUID+hostname+timestamp concatenation; framework code passing an empty string when the ID failed to load; copying an identifier with trailing whitespace and assuming trim is done server-side (it is, but emptiness after trim fails); migrating legacy IDs that exceed the cap.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- invalid_ops: device name must be at most 80 characters
- device_id must be 1-128 characters
- server job ID must not contain
- SyncApply: invalid op seq=
- SyncApply: invalid op seq=
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/0ec4e4ef32a727d7.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/do/SyncHub.ts:857
if (result.rowsWritten === 0) throw deviceLimitError();
}
private touchExistingDevice(deviceId: string, name: string | null, now = Date.now()): void {
const normalizedId = this.normalizeDeviceId(deviceId);
this.ctx.storage.sql.exec(
`UPDATE devices
SET name = COALESCE(name, ?), last_seen = ?
WHERE device_id = ?`,
name,
now,
normalizedId,
);
}
private normalizeDeviceId(deviceId: string): string {
const normalizedId = deviceId.trim();
if (normalizedId.length === 0 || normalizedId.length > 128) {
throw invalid("deviceId must be 1-128 characters");
}
return normalizedId;
}
private meta(key: string): string {
const row = this.ctx.storage.sql.exec<{ v: string }>("SELECT v FROM meta WHERE k = ?", key).toArray()[0];
if (!row) throw new Error(`sync-hub invariant: missing meta ${key}`);
return row.v;
}
private metaOptional(key: string): string | null {
return this.ctx.storage.sql.exec<{ v: string }>("SELECT v FROM meta WHERE k = ?", key).toArray()[0]?.v ?? null;
}
private setMeta(key: string, value: string): void {
this.ctx.storage.sql.exec(
"INSERT INTO meta (k, v) VALUES (?, ?) ON CONFLICT(k) DO UPDATE SET v=excluded.v",
key,View on GitHub (pinned to d8bc9755e7)