{"record":{"id":"659f7f2652eed912","repo":"nexu-io/open-design","slug":"invalid-generation","errorCode":"invalid_generation","errorMessage":"invalid_generation","messagePattern":"invalid_generation","errorType":"exception","errorClass":"WorkspaceBillingInterestError","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/collab/workspace-billing-runtime.ts","lineNumber":260,"sourceCode":"    );\n    this.pollTimer = this.scheduler.setInterval(() => {\n      this.refreshAll('poll-floor');\n    }, this.pollIntervalMs);\n    this.pollTimer.unref?.();\n    this.interestSweepTimer = this.scheduler.setInterval(() => {\n      this.sweepExpiredInterests();\n    }, Math.max(1, options.interestSweepIntervalMs ?? DEFAULT_INTEREST_SWEEP_INTERVAL_MS));\n    this.interestSweepTimer.unref?.();\n  }\n\n  setClientInterests(\n    input: WorkspaceBillingRuntimeInterestSet,\n  ): WorkspaceBillingRuntimeInterestLease {\n    this.assertUsable();\n    const clientId = input.clientId.trim();\n    const generationText = input.clientGeneration.trim();\n    if (!clientId || !/^(?:0|[1-9]\\d*)$/.test(generationText)) {\n      throw new WorkspaceBillingInterestError('invalid_generation');\n    }\n    const generation = BigInt(generationText);\n    const current = this.clients.get(clientId);\n    if (current && generation < current.generation) {\n      throw new WorkspaceBillingInterestError(\n        'stale_generation',\n        current.generation.toString(),\n      );\n    }\n    const keys = new Map<string, WorkspaceBillingRuntimeKey>();\n    for (const interest of input.interests) {\n      const key = normalizeKey(interest);\n      keys.set(runtimeKey(key), key);\n    }\n    if (keys.size > this.maxInterestsPerClient) {\n      throw new WorkspaceBillingInterestError('interest_capacity_exceeded');\n    }\n    if (current && generation === current.generation) {","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/collab/workspace-billing-runtime.ts#L242-L278","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — sending an uninitialized generation\nruntime.setClientInterests({\n  clientId,\n  clientGeneration: '',  // bug: not initialized\n  interests: keys,\n});\n\n// after — always send a valid generation string\nlet generation = 0;\nfunction nextGeneration() { generation += 1; return String(generation); }\nruntime.setClientInterests({\n  clientId,\n  clientGeneration: String(generation),\n  interests: keys,\n});","handlingStrategy":"validation","validationCode":"const GENERATION_RE = /^(?:0|[1-9]\\d*)$/;\n\nfunction isValidGeneration(generation: string | undefined): generation is string {\n  return typeof generation === 'string' && GENERATION_RE.test(generation.trim());\n}\n\n// Before calling setClientInterests:\nif (!isValidGeneration(input.clientGeneration)) {\n  throw new Error(`clientGeneration must be a non-negative integer string, got: ${input.clientGeneration}`);\n}\nif (!input.clientId?.trim()) {\n  throw new Error('clientId is required');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const lease = await coordinator.setClientInterests({\n    clientId,\n    clientGeneration: String(generation),\n    interests,\n  });\n} catch (error) {\n  if (error instanceof WorkspaceBillingInterestError && error.code === 'invalid_generation') {\n    // Reset client session — protocol data is corrupted\n    generation = 0n;\n    await coordinator.setClientInterests({\n      clientId,\n      clientGeneration: '0',\n      interests,\n    });\n  } else throw error;\n}","preventionTips":["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."],"tags":["billing","validation","protocol","typescript"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}