nexu-io/open-design · warning · WorkspaceBillingInterestError
interest_capacity_exceeded
interest_capacity_exceeded
Error message
interest_capacity_exceeded
What it means
Thrown by setClientInterests when a single client's deduplicated interest set exceeds maxInterestsPerClient (default 16). The check occurs after normalizeKey/dedup via the Map at line 270-273, so duplicate keys count once. This cap prevents one client from monopolizing the coordinator's per-key refresh budget.
Source
Thrown at apps/daemon/src/collab/workspace-billing-runtime.ts:276
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) {
if (!sameStringSet(current.keys, new Set(keys.keys()))) {
throw new WorkspaceBillingInterestError(
'generation_payload_mismatch',
current.generation.toString(),
);
}
current.expiresAt = this.scheduler.now() + this.interestLeaseMs;
return this.interestLease(clientId, current);
}
if (keys.size === 0) {
if (current) {
this.clients.delete(clientId);
this.handleInterestMutation(current.keys, new Set());
}
return {
clientId,View on GitHub (pinned to 5be4028344)
Solutions
- Reduce the interests array to at most maxInterestsPerClient entries, prioritizing the currently focused workspaces.
- Check the configured maxInterestsPerClient if you customized the coordinator options.
- Implement client-side eviction: drop least-recently-used interests when approaching the cap.
- If the cap is genuinely too low for your use case, raise maxInterestsPerClient in the coordinator constructor options.
Example fix
// before — sending all visible workspaces
const interests = allWorkspaces.map(w => ({ workspaceId: w.id, workspaceMemberId: w.memberId }));
runtime.setClientInterests({ clientId, clientGeneration, interests });
// after — cap to the focused set
const MAX = 16;
const interests = focusedWorkspaces
.slice(0, MAX)
.map(w => ({ workspaceId: w.id, workspaceMemberId: w.memberId }));
runtime.setClientInterests({ clientId, clientGeneration, interests }); Defensive patterns
Strategy: validation
Validate before calling
const MAX_INTERESTS_PER_CLIENT = 16; // must match coordinator config
function sanitizeInterests(
interests: WorkspaceBillingRuntimeKey[],
max: number = MAX_INTERESTS_PER_CLIENT,
): WorkspaceBillingRuntimeKey[] {
// Dedup by workspaceId+workspaceMemberId
const seen = new Set<string>();
const deduped = interests.filter(k => {
const id = `${k.workspaceId}:${k.workspaceMemberId}`;
if (seen.has(id)) return false;
seen.add(id);
return true;
});
if (deduped.length > max) {
throw new Error(
`Interest set of ${deduped.length} exceeds per-client cap of ${max}. ` +
`Reduce to the most important workspaces.`
);
}
return deduped;
}
// Before calling setClientInterests:
const safeInterests = sanitizeInterests(interests, coordinator.maxInterestsPerClient); Try / catch
try {
await coordinator.setClientInterests({ clientId, clientGeneration: String(gen), interests });
} catch (error) {
if (error instanceof WorkspaceBillingInterestError &&
error.code === 'interest_capacity_exceeded') {
// Trim to the cap, prioritizing focused workspaces
const trimmed = interests.slice(0, MAX_INTERESTS_PER_CLIENT);
await coordinator.setClientInterests({ clientId, clientGeneration: String(gen), interests: trimmed });
} else throw error;
} Prevention
- Deduplicate interests client-side before sending — duplicate keys waste budget.
- Implement LRU eviction for client-side interest sets.
- Only watch workspaces the user is actively viewing, not every workspace they can see.
- Check the configured maxInterestsPerClient if you customized the coordinator.
When it happens
Trigger: Calling setClientInterests with an interests array that, after key normalization and dedup, produces more than maxInterestsPerClient distinct {workspaceId, workspaceMemberId} pairs. The default cap is 16.
Common situations: A dashboard client tries to watch billing for all 30 workspaces it can see. A bug in client-side interest logic sends every workspace/member pair instead of the focused set. The client doesn't prune stale interests before adding new ones.
Related errors
- invalid_generation
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
- ARTIFACT_MANIFEST_INVALID
- ARTIFACT_MANIFEST_REQUIRED
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/1840d7619ecc72ab.
Report an issue: GitHub.