{"record":{"id":"f08d244da9d47c69","repo":"rohitg00/agentmemory","slug":"mem-search-token-budget-must-be-a-positive-integ","errorCode":null,"errorMessage":"mem::search: token_budget must be a positive integer","messagePattern":"mem::search: token_budget must be a positive integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/functions/search.ts","lineNumber":440,"sourceCode":"        !wildcardAgent &&\n        !explicitAgentId &&\n        !envAgentId\n      ) {\n        throw new Error(\n          \"mem::search: AGENTMEMORY_AGENT_SCOPE=isolated is set but no \" +\n            \"agent id is available (env AGENT_ID unset and no explicit \" +\n            \"agentId in the call). Refusing to read cross-agent rows. \" +\n            'Pass agentId: \"*\" to opt in to a wildcard read.',\n        );\n      }\n      const format = typeof data.format === 'string' ? data.format : 'full'\n      if (!['full', 'compact', 'narrative'].includes(format)) {\n        throw new Error(\"mem::search: format must be one of 'full', 'compact', or 'narrative'\")\n      }\n      let tokenBudget: number | undefined\n      if (data.token_budget !== undefined) {\n        if (!Number.isInteger(data.token_budget) || data.token_budget < 1) {\n          throw new Error('mem::search: token_budget must be a positive integer')\n        }\n        tokenBudget = data.token_budget\n      }\n\n      if (idx.size === 0) {\n        // Share one rebuild across concurrent cold-start queries so they\n        // don't each walk the whole corpus and saturate the pool.\n        if (!rebuildPromise) {\n          rebuildPromise = rebuildIndex(kv)\n            .then((count) => {\n              logger.info('Search index rebuilt', { entries: count })\n              return count\n            })\n            .catch((err) => {\n              logger.warn('Index rebuild failed', {\n                error: err instanceof Error ? err.message : String(err),\n              })\n              return 0","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/rohitg00/agentmemory/blob/e04ba88819c365c9acf9d6661ea802143e728bd6/src/functions/search.ts#L422-L458","documentation":"The mem::search function validates every argument at the system boundary. token_budget is an optional input that caps the token cost of the formatted search result; if supplied it must be an integer >= 1 (Number.isInteger check plus < 1 rejection). Passing a float, 0, a negative number, or a non-number type makes the function throw immediately rather than silently mis-budgeting the response.","triggerScenarios":"Calling sdk.trigger({ function_id: 'mem::search', payload: { query, token_budget: X } }) where X is 0, negative, a non-integer (e.g. 512.5), a numeric string like \"512\", null (as an explicit value is still defined), or any non-number type.","commonSituations":"Parsing the budget from a config file or CLI flag without Number() coercion, LLM-generated tool-call arguments arriving as strings, dividing a budget across calls producing fractions, or a client defaulting the field to 0 meaning 'unset'.","solutions":["Pass token_budget as a whole number >= 1, or omit the field entirely to use the default budget.","Coerce string inputs with Number(value) and round with Math.round/Math.floor before the call.","Treat 0 or negative values as 'unset' by deleting the property instead of sending it."],"exampleFix":"// before\nawait sdk.trigger({ function_id: \"mem::search\", payload: { query, token_budget: req.query.budget } });\n// after\nconst raw = req.query.budget;\nconst token_budget = raw ? Math.max(1, Math.round(Number(raw))) : undefined;\nawait sdk.trigger({ function_id: \"mem::search\", payload: { query, ...(token_budget !== undefined ? { token_budget } : {}) } });","handlingStrategy":"validation","validationCode":"function validTokenBudget(v) {\n  return v === undefined || (Number.isInteger(v) && v >= 1);\n}\n// call only if validTokenBudget(payload.token_budget)","typeGuard":"function isTokenBudget(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isInteger(v) && v >= 1;\n}","tryCatchPattern":"try {\n  result = await sdk.trigger({ function_id: \"mem::search\", payload });\n} catch (e) {\n  if (String(e.message).includes(\"token_budget must be a positive integer\")) {\n    delete payload.token_budget; // retry with default budget\n    result = await sdk.trigger({ function_id: \"mem::search\", payload });\n  } else throw e;\n}","preventionTips":["Never send token_budget: 0 as a sentinel for 'unset' — delete the key instead.","Coerce and round user/config input with Math.round(Number(x)) before passing.","Whitelist tool-call args from LLMs and validate numeric fields before triggering."],"tags":["validation","arguments","mcp"],"backgroundTag":"invalid-argument-value","analyzedSha":"e04ba88819c365c9acf9d6661ea802143e728bd6","analyzedAt":"2026-08-30T01:07:40.754Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}