nexu-io/open-design · error · WorkspaceBillingInterestError
invalid_generation
invalid_generation
Error message
invalid_generation
What it means
Thrown by WorkspaceBillingRuntimeCoordinator.setClientInterests when the clientGeneration string is empty or does not match the canonical non-negative-integer regex /^(?:0|[1-9]\d*)$/. The generation is an opaque monotonically increasing token the client uses for optimistic concurrency on its interest set. An invalid generation means the client sent malformed protocol data — empty string, leading zeros, negative numbers, decimals, or non-numeric text.
Source
Thrown at apps/daemon/src/collab/workspace-billing-runtime.ts:260
);
this.pollTimer = this.scheduler.setInterval(() => {
this.refreshAll('poll-floor');
}, this.pollIntervalMs);
this.pollTimer.unref?.();
this.interestSweepTimer = this.scheduler.setInterval(() => {
this.sweepExpiredInterests();
}, Math.max(1, options.interestSweepIntervalMs ?? DEFAULT_INTEREST_SWEEP_INTERVAL_MS));
this.interestSweepTimer.unref?.();
}
setClientInterests(
input: WorkspaceBillingRuntimeInterestSet,
): WorkspaceBillingRuntimeInterestLease {
this.assertUsable();
const clientId = input.clientId.trim();
const generationText = input.clientGeneration.trim();
if (!clientId || !/^(?:0|[1-9]\d*)$/.test(generationText)) {
throw new WorkspaceBillingInterestError('invalid_generation');
}
const generation = BigInt(generationText);
const current = this.clients.get(clientId);
if (current && generation < current.generation) {
throw new WorkspaceBillingInterestError(
'stale_generation',
current.generation.toString(),
);
}
const keys = new Map<string, WorkspaceBillingRuntimeKey>();
for (const interest of input.interests) {
const key = normalizeKey(interest);
keys.set(runtimeKey(key), key);
}
if (keys.size > this.maxInterestsPerClient) {
throw new WorkspaceBillingInterestError('interest_capacity_exceeded');
}
if (current && generation === current.generation) {View on GitHub (pinned to 5be4028344)
Solutions
- Ensure clientGeneration is always a stringified non-negative integer with no leading zeros (e.g., '0', '1', '42').
- Initialize the client-side generation counter to '0' on first connect and increment it as a string on every interest-set change.
- Validate the generation format client-side before sending: if (!/^(?:0|[1-9]\d*)$/.test(gen)) reset the session.
- If the error persists, check that the SSE/HTTP transport layer is not corrupting or truncating the generation field.
Example fix
// before — sending an uninitialized generation
runtime.setClientInterests({
clientId,
clientGeneration: '', // bug: not initialized
interests: keys,
});
// after — always send a valid generation string
let generation = 0;
function nextGeneration() { generation += 1; return String(generation); }
runtime.setClientInterests({
clientId,
clientGeneration: String(generation),
interests: keys,
}); Defensive patterns
Strategy: validation
Validate before calling
const GENERATION_RE = /^(?:0|[1-9]\d*)$/;
function isValidGeneration(generation: string | undefined): generation is string {
return typeof generation === 'string' && GENERATION_RE.test(generation.trim());
}
// Before calling setClientInterests:
if (!isValidGeneration(input.clientGeneration)) {
throw new Error(`clientGeneration must be a non-negative integer string, got: ${input.clientGeneration}`);
}
if (!input.clientId?.trim()) {
throw new Error('clientId is required');
} Try / catch
try {
const lease = await coordinator.setClientInterests({
clientId,
clientGeneration: String(generation),
interests,
});
} catch (error) {
if (error instanceof WorkspaceBillingInterestError && error.code === 'invalid_generation') {
// Reset client session — protocol data is corrupted
generation = 0n;
await coordinator.setClientInterests({
clientId,
clientGeneration: '0',
interests,
});
} else throw error;
} Prevention
- Always stringify BigInt generations with .toString() before sending.
- Initialize the generation counter on client connect, never leave it undefined.
- Validate the generation format on the client side before transmitting.
- Never pass NaN, undefined, or floating-point numbers as generation.
When it happens
Trigger: Calling setClientInterests({ clientId, clientGeneration, interests }) where clientGeneration is '', 'abc', '-1', '01', '1.5', or undefined-after-trim. The check at line 258 first trims both fields, then rejects if clientId is empty or generationText fails the regex.
Common situations: A web client sends its first interest registration before initializing its generation counter (sends empty string). A client bug produces NaN.toString() or undefined. A protocol version mismatch where an older client sends a different generation format.
Related errors
- interest_capacity_exceeded
- generation_payload_mismatch
- Unsupported protocol: ${protocol}
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/659f7f2652eed912.
Report an issue: GitHub.