{"record":{"id":"66dbd142580fd0fe","repo":"can1357/oh-my-pi","slug":"hit-count-must-be-0-got-hitcount","errorCode":null,"errorMessage":"hit_count must be >= 0, got ${hitCount}","messagePattern":"hit_count must be >= 0, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/recall-diagnostics.ts","lineNumber":69,"sourceCode":"\n\tconstructor() {\n\t\tthis.tierStats = newTierStats();\n\t\tthis.totalCalls = 0;\n\t\tthis.callsUsingWmFallback = 0;\n\t\tthis.callsUsingEmFallback = 0;\n\t\tthis.callsTrulyEmpty = 0;\n\t\tthis.createdAt = new Date().toISOString();\n\t}\n\n\tprivate static validateTier(tier: string): asserts tier is RecallTier {\n\t\tif (!isRecallTier(tier)) {\n\t\t\tthrow new Error(`unknown recall tier ${JSON.stringify(tier)}; valid tiers: ${JSON.stringify(RECALL_TIERS)}`);\n\t\t}\n\t}\n\n\trecordTierHits(tier: RecallTier | string, hitCount: number): void {\n\t\tRecallDiagnostics.validateTier(tier);\n\t\tif (hitCount < 0) throw new Error(`hit_count must be >= 0, got ${hitCount}`);\n\t\tconst stats = this.tierStats[tier];\n\t\tif (hitCount > 0) stats.callsWithHits++;\n\t\tstats.totalHits += hitCount;\n\t}\n\trecordFallbackUsed(options: { readonly wm?: boolean; readonly em?: boolean } = {}): void {\n\t\tif (options.wm === true) this.callsUsingWmFallback++;\n\t\tif (options.em === true) this.callsUsingEmFallback++;\n\t}\n\trecordCall(options: { readonly trulyEmpty?: boolean; readonly truly_empty?: boolean } = {}): void {\n\t\tthis.totalCalls++;\n\t\tif (options.trulyEmpty === true || options.truly_empty === true) this.callsTrulyEmpty++;\n\t}\n\tfallbackRate(): { readonly wm: number; readonly em: number } {\n\t\tif (this.totalCalls === 0) return { wm: 0.0, em: 0.0 };\n\t\treturn {\n\t\t\twm: Math.min(1.0, this.callsUsingWmFallback / this.totalCalls),\n\t\t\tem: Math.min(1.0, this.callsUsingEmFallback / this.totalCalls),\n\t\t};","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/recall-diagnostics.ts#L51-L87","documentation":"recordTierHits rejects negative hit counts because a count of hits cannot logically be below zero; doing so would corrupt cumulative statistics (totalHits, callsWithHits).","triggerScenarios":"Calling recordTierHits(tier, -1) — typically from subtracting counters or an arithmetic bug producing negative deltas.","commonSituations":"Computing hit counts as `after - before` when counters reset between reads; aggregating external stats that already normalized negatives to zero elsewhere; integer underflow in custom scoring.","solutions":["Clamp negative values to 0 before recording: Math.max(0, hitCount)","Fix the upstream computation that produced a negative count","Skip recording when the computed count is negative and log the anomaly"],"exampleFix":"// before\ndiag.recordTierHits(tier, after - before); // may be negative\n// after\ndiag.recordTierHits(tier, Math.max(0, after - before));","handlingStrategy":"validation","validationCode":"if (Number.isInteger(hitCount) && hitCount >= 0) {\n  diag.recordTierHits(tier, hitCount);\n}","typeGuard":null,"tryCatchPattern":"try {\n  diag.recordTierHits(tier, hitCount);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"hit_count must be >= 0\")) {\n    logger.warn(\"negative hit count computed; clamping to 0\", { tier, hitCount });\n    diag.recordTierHits(tier, 0);\n    return;\n  }\n  throw err;\n}","preventionTips":["Clamp computed deltas with Math.max(0, delta)","Watch for counter resets when computing before/after differences","Sanitize external metrics before recording"],"tags":["validation","diagnostics","arguments"],"backgroundTag":"invalid-argument-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}