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

  1. Ensure clientGeneration is always a stringified non-negative integer with no leading zeros (e.g., '0', '1', '42').
  2. Initialize the client-side generation counter to '0' on first connect and increment it as a string on every interest-set change.
  3. Validate the generation format client-side before sending: if (!/^(?:0|[1-9]\d*)$/.test(gen)) reset the session.
  4. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/659f7f2652eed912. Report an issue: GitHub.