{"record":{"id":"8482ccfe5aed5e22","repo":"JuliusBrussee/caveman","slug":"token-budget-must-be-positive-zero-default-or","errorCode":null,"errorMessage":"token budget must be positive, zero (default), or UnlimitedTokenBudget","messagePattern":"token budget must be positive, zero \\(default\\), or UnlimitedTokenBudget","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":425,"sourceCode":"// Recall returns the memories most relevant to query, ranked by BM25, filtered\n// by the threshold, and compressed for injection. It fails toward no recall: a\n// query with no term overlap (or that clears nothing above the threshold)\n// returns an empty slice, never a guess.\nfunc (s *Store) Recall(query string, opts RecallOptions) ([]Hit, error) {\n\tlimit := opts.Limit\n\tif limit <= 0 {\n\t\tlimit = DefaultLimit\n\t}\n\tthreshold := opts.Threshold\n\tif threshold <= 0 {\n\t\tthreshold = DefaultThreshold\n\t}\n\tbudget := opts.TokenBudget\n\tunlimited := budget == UnlimitedTokenBudget\n\tif budget == 0 {\n\t\tbudget = DefaultTokenBudget\n\t} else if budget < 0 && !unlimited {\n\t\treturn nil, fmt.Errorf(\"token budget must be positive, zero (default), or UnlimitedTokenBudget\")\n\t}\n\n\tall, err := s.all()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscores := bm25Scores(query, all)\n\n\ttype scored struct {\n\t\tmem   Memory\n\t\tscore float64\n\t}\n\tranked := make([]scored, 0, len(all))\n\tfor _, m := range all {\n\t\tif sc := scores[m.ID]; sc >= threshold {\n\t\t\tranked = append(ranked, scored{mem: m, score: sc})\n\t\t}\n\t}","sourceCodeStart":407,"sourceCodeEnd":443,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/mem/store.go#L407-L443","documentation":"Recall validates RecallOptions.TokenBudget before running: it accepts a positive budget, 0 (meaning the 2000-token DefaultTokenBudget), or the sentinel UnlimitedTokenBudget (-1). Any other negative number is rejected because it is neither a real cap nor the explicit unlimited sentinel — the store refuses to guess whether you meant bounded or unbounded.","triggerScenarios":"Passing RecallOptions{TokenBudget: -5} or any negative value other than -1. Commonly from computing a budget by subtraction (remaining - used) that underflows below -1, or misreading the API and using an arbitrary negative as 'no limit' instead of mem.UnlimitedTokenBudget.","commonSituations":"Adapter code mapping external 'unlimited' flags to a hardcoded -2; arithmetic on budgets going negative on small requests; version drift where a caller predates the sentinel and used any negative for unlimited.","solutions":["Use mem.UnlimitedTokenBudget (-1) explicitly when you want no cap, or 0/omitted for the 2000-token default","Clamp computed budgets: if budget < 0 { budget = mem.UnlimitedTokenBudget } (or 0) at the call site","If the value comes from user config, validate/normalize it at load time so the store never sees garbage"],"exampleFix":"// before\nres, err := store.Recall(q, mem.RecallOptions{TokenBudget: remaining - requested}) // can be < -1\n\n// after\nbudget := remaining - requested\nif budget < 0 {\n    budget = mem.UnlimitedTokenBudget // or 0 for the safe default\n}\nres, err := store.Recall(q, mem.RecallOptions{TokenBudget: budget})","handlingStrategy":"validation","validationCode":"if opts.TokenBudget < 0 && opts.TokenBudget != mem.UnlimitedTokenBudget {\n    opts.TokenBudget = 0 // or mem.UnlimitedTokenBudget, per your intent\n}\nres, err := store.Recall(q, opts)","typeGuard":"func validTokenBudget(b int) bool {\n    return b > 0 || b == 0 || b == mem.UnlimitedTokenBudget\n}","tryCatchPattern":null,"preventionTips":["Use the sentinels: 0 for default, mem.UnlimitedTokenBudget (-1) for unlimited — never other negatives","Clamp arithmetic budgets (remaining - used) before passing them","Normalize external config values to the three legal forms at load time"],"tags":["go","validation","api-misuse","configuration"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}