thedotmack/claude-mem · error · Error

name must be 1-80 characters

Error message

name must be 1-80 characters

What it means

renameDevice() validates the new device name via normalizeDeviceName(name), which returns null when the trimmed name is empty or longer than 80 characters. The DO throws this error instead of silently accepting a bad rename, so device display names stay within the 1-80 character contract enforced everywhere else in the hub.

Solutions

  1. Trim the name and check 1 <= name.length <= 80 before calling renameDevice; return a user-facing validation message instead of calling the DO.
  2. Check the renameDevice return value flow: if the name is fine but the device doesn't exist, rowsWritten is 0 and it returns false (no throw), so distinguish validation failure from not-found.
  3. Render client-side maxlength=80 on the name input to prevent the error at the source.
  4. If names can legitimately be longer, store the long label elsewhere; the DO contract is fixed at 80.

Example fix

// before
await hub.renameDevice(deviceId, formData.get('name') as string);
// after
const name = String(formData.get('name') ?? '').trim();
if (name.length < 1 || name.length > 80) {
  return json({ error: 'device name must be 1-80 characters' }, 400);
}
await hub.renameDevice(deviceId, name);
Defensive patterns

Strategy: validation

Validate before calling

const name = rawName.trim();
if (name.length < 1 || name.length > 80) throw new RangeError('device name must be 1-80 characters');

Type guard

function isValidDeviceName(name: unknown): name is string {
  return typeof name === 'string' && name.trim().length >= 1 && name.trim().length <= 80;
}

Try / catch

try {
  ok = await hub.renameDevice(deviceId, name);
} catch (e) {
  if (String(e).includes('name must be 1-80')) return json({ error: 'invalid device name' }, 400);
  throw e;
}

Prevention

When it happens

Trigger: Calling renameDevice(deviceId, name) with an empty/whitespace-only name, a name longer than 80 characters after trimming, or a name that normalizeDeviceName rejects for any other length reason.

Common situations: Clients sending user-supplied device labels without client-side length checks; a UI form that allows blank submits; syncing legacy names from another system that allows 100+ character labels; truncation logic accidentally trimming to empty string.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/6c5d8342fed2c585. Report an issue: GitHub.

Appendix: source

Thrown at workers/sync-hub/src/do/SyncHub.ts:624

			};
		});
		return {
			protocol_version: 1,
			user_id: userId,
			epoch: this.meta("epoch"),
			head_seq: head,
			projected_seq: projected,
			projection_lag_ops: decimalLag(head, projected),
			sync_health: head === projected ? "healthy" : "projector_lagging",
			devices,
		};
	}

	renameDevice(deviceId: string, name: string): boolean {
		const normalizedId = deviceId.trim();
		const normalizedName = normalizeDeviceName(name);
		if (normalizedId.length === 0 || normalizedId.length > 128) throw invalid("device_id must be 1-128 characters");
		if (normalizedName === null) throw invalid("name must be 1-80 characters");
		return this.ctx.storage.sql.exec(
			"UPDATE devices SET name = ? WHERE device_id = ?",
			normalizedName,
			normalizedId,
		).rowsWritten > 0;
	}

	// ---------------------------------------------------------------------
	// Authoritative projection checkpoint and short per-user lease.
	// ---------------------------------------------------------------------

	acquireProjectionLease(targetSeq: string, now = Date.now()): ProjectionLease {
		const target = assertCanonicalDecimal(targetSeq);
		const head = this.headSeq();
		if (compareCanonicalDecimals(target, head) > 0) throw projectionError("target_seq exceeds head_seq");
		const projected = this.projectedSeq();
		const existingToken = this.metaOptional("projection_lease_token");
		const nowValue = this.leaseNow(now);

View on GitHub (pinned to d8bc9755e7)