{"record":{"id":"ce78ed60e1ee05d1","repo":"different-ai/openwork","slug":"discovery-catalogbudget-must-be-a-non-negative-saf","errorCode":null,"errorMessage":"discovery.catalogBudget must be a non-negative safe integer","messagePattern":"discovery\\.catalogBudget must be a non-negative safe integer","errorType":"exception","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"packages/codemode/src/tool-runtime.ts","lineNumber":497,"sourceCode":"  if (Object.hasOwn(tools, reservedNamespace)) {\n    throw new Error(`Tool namespace '${reservedNamespace}' is reserved for CodeMode discovery tools.`)\n  }\n}\n\n/**\n * Budgeted catalog: every namespace is always listed with its tool count; full call\n * signatures are inlined against the `catalogBudget` (estimated tokens,\n * chars/4) round-robin across namespaces - in each round (namespaces alphabetical), every\n * namespace still holding un-inlined tools attempts to place its next-cheapest line, and\n * a namespace whose next line does not fit is done while the others keep going - so every\n * namespace gets some representation before any namespace gets everything. The section\n * states exactly how comprehensive it is - overall (COMPLETE vs PARTIAL) and per\n * namespace. Namespace stub lines are never budgeted: every namespace appears with its\n * tool count even at budget 0.\n */\nexport const prepare = <R>(tools: HostTools<R>, catalogBudget = defaultCatalogBudget): DiscoveryPlan => {\n  if (!Number.isSafeInteger(catalogBudget) || catalogBudget < 0) {\n    throw new RangeError(\"discovery.catalogBudget must be a non-negative safe integer\")\n  }\n  const visible = visibleDefinitions(tools)\n  const described = visible.map(({ description }) => description)\n\n  const namespaces = new Map<string, Array<ToolDescription>>()\n  for (const tool of described) {\n    const [namespace = tool.path] = tool.path.split(\".\")\n    const group = namespaces.get(namespace) ?? []\n    group.push(tool)\n    namespaces.set(namespace, group)\n  }\n  const ordered = [...namespaces].sort(([left], [right]) => left.localeCompare(right))\n\n  // Select which signatures fit the budget before emitting, so the list can state\n  // exactly how comprehensive it is. Round-robin fairness: in each round (namespaces\n  // alphabetical), every namespace still holding un-inlined tools tries to place its\n  // next-cheapest line against the shared budget; a namespace whose next line does not\n  // fit is done - the others keep going - so every namespace gets some representation","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/codemode/src/tool-runtime.ts#L479-L515","documentation":"Error \"discovery.catalogBudget must be a non-negative safe integer\" thrown in different-ai/openwork.","triggerScenarios":"Thrown at packages/codemode/src/tool-runtime.ts:497 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":[],"exampleFix":null,"handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}