paperclipai/paperclip · error
${label} must be a positive finite number
Error message
${label} must be a positive finite number What it means
assertTtl validates that a TTL value is a finite number greater than zero. acquireLock, extendLock, and expiryFromTtl call it; a NaN, Infinity, 0, or negative TTL would produce unusable expiry math, so it is rejected at the boundary.
Source
Thrown at server/src/services/chat-sdk-state.ts:80
export interface PaperclipChatSdkStateOptions extends ChatSdkStateScope {
maxValueBytes?: number;
now?: () => Date;
persistence: ChatSdkStatePersistence;
}
function assertBoundedIdentifier(
label: string,
value: string,
maximum: number,
): void {
if (!value || value.length > maximum) {
throw new Error(`${label} must be between 1 and ${maximum} characters`);
}
}
function assertTtl(label: string, ttlMs: number): void {
if (!(Number.isFinite(ttlMs) && ttlMs > 0)) {
throw new Error(`${label} must be a positive finite number`);
}
}
function storageKey(kind: StateKind, logicalKey: string): string {
assertBoundedIdentifier(
"Chat SDK state key",
logicalKey,
MAX_LOGICAL_KEY_LENGTH,
);
const digest = createHash("sha256").update(logicalKey).digest("hex");
return `${kind}:${digest}`;
}
function decodeEnvelope(
record: ChatSdkStateRecord,
expectedKind: StateKind,
): unknown {
const envelope = record.value as Partial<StateEnvelope> | null;View on GitHub (pinned to 01ad858492)
Solutions
- Validate ttlMs is a positive finite number before the call: Number.isFinite(ttlMs) && ttlMs > 0
- Fix the source computation (e.g. Math.max(minTtl, deadline - Date.now()))
- Coerce string config values with Number() and check for NaN
Example fix
// before
await state.acquireLock(key, ttlFromConfig);
// after
const ttl = Number(ttlFromConfig);
if (!Number.isFinite(ttl) || ttl <= 0) throw new Error(`bad ttl: ${ttlFromConfig}`);
await state.acquireLock(key, ttl); Defensive patterns
Strategy: validation
Validate before calling
function validTtl(ttlMs: unknown): ttlMs is number { return typeof ttlMs === 'number' && Number.isFinite(ttlMs) && ttlMs > 0; } Type guard
function isPositiveFiniteNumber(v: unknown): v is number { return typeof v === 'number' && Number.isFinite(v) && v > 0; } Try / catch
try { await state.acquireLock(key, ttlMs); } catch (err) { if (/must be a positive finite number/.test((err as Error).message)) { ttlMs = DEFAULT_TTL_MS; return state.acquireLock(key, ttlMs); } throw err; } Prevention
- Clamp computed TTLs: Math.max(1000, deadline - Date.now())
- Coerce config values with Number() and validate on load
- Guard against expired-deadline diffs yielding <= 0
When it happens
Trigger: Calling acquireLock/extendLock with ttlMs = 0, negative values, NaN, or Infinity; computing ttlMs from a Date diff that is <= 0 (already expired) or from a misparsed config string.
Common situations: Config parsing where '30' (string) is not converted to a number; Date.now() - startedAt yielding 0 or negative on clock skew; a lock already expired so remaining TTL computes <= 0.
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
- Durable PRP bootstrap TTL is invalid.
- ${label} must be between 1 and ${maximum} characters
- List maxLength must be a positive safe integer
- Queue maxSize must be a positive safe integer
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/6eb9767d810a5c31.
Report an issue: GitHub.