{"record":{"id":"ec31696bc8331dd5","repo":"moeru-ai/airi","slug":"invalid-duration-format-duration","errorCode":null,"errorMessage":"Invalid duration format: ${duration}","messagePattern":"Invalid duration format: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"integrations/minecraft/src/cognitive/perception/rules/temporal-detector.ts","lineNumber":229,"sourceCode":"export function processEvent(\n  state: DetectorState,\n  input: ProcessDetectorInput,\n): readonly [boolean, DetectorState] {\n  if (input.mode === 'tumbling') {\n    return processTumblingEvent(state, input)\n  }\n\n  return processSlidingEvent(state, input)\n}\n\n/**\n * Parse window duration string to milliseconds\n * Supports: '2s', '500ms', '1m', '100'\n */\nexport function parseWindowDuration(duration: string): number {\n  const match = duration.match(/^(\\d+(?:\\.\\d+)?)(ms|s|m)?$/)\n  if (!match) {\n    throw new Error(`Invalid duration format: ${duration}`)\n  }\n\n  const value = Number.parseFloat(match[1])\n  const unit = match[2] || 'ms'\n\n  switch (unit) {\n    case 'ms': return value\n    case 's': return value * 1000\n    case 'm': return value * 60 * 1000\n    default: return value\n  }\n}\n\n/**\n * Calculate number of slots for a given window duration\n */\nexport function calculateWindowSlots(\n  windowMs: number,","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/minecraft/src/cognitive/perception/rules/temporal-detector.ts#L211-L247","documentation":"Thrown by parseWindowDuration() in the temporal perception detector. It parses a duration string into milliseconds using the regex /^(\\d+(?:\\.\\d+)?)(ms|s|m)?$/. Supported formats: bare number (treated as ms), 'NNms', 'NNs', 'NNm'. Anything that does not match — letters without a number, unsupported units (h, d), spaces, negative numbers, multiple units — throws with the offending input echoed.","triggerScenarios":"Passing '1h', '2d', '-5s', '5 seconds', 'abc', '5s5', '5 s', or a value with a unit the regex does not recognise; passing an empty string; passing a value with a trailing space or newline that breaks the anchored regex.","commonSituations":"Config typo in a detector rule window; user-supplied duration from a chat command parsed without normalisation; unit confusion (caller assumes '100' means seconds but the default unit is ms); copying a duration from a tool that uses 'h'/'d'.","solutions":["Use one of the supported formats: '500ms', '2s', '1m', or a bare number (milliseconds).","Normalise user/config input to the supported units before calling parseWindowDuration.","If you need hours/days, convert to minutes or milliseconds upstream (e.g. 2h -> 120m).","Trim whitespace and validate non-empty before parsing."],"exampleFix":"// before\n//   parseWindowDuration('1h')    // throws\n//   parseWindowDuration('5 s')   // throws\n//\n// after\n//   parseWindowDuration('60m')   // ok -> 3600000\n//   parseWindowDuration('5s')    // ok -> 5000","handlingStrategy":"validation","validationCode":"import { parseWindowDuration } from './temporal-detector'\n\nconst DURATION_RE = /^(\\d+(?:\\.\\d+)?)(ms|s|m)?$/\nexport function safeParseDuration(input: string): number | null {\n  const s = input.trim()\n  if (!DURATION_RE.test(s)) return null\n  return parseWindowDuration(s)\n}\n\n// const ms = safeParseDuration(userInput)\n// if (ms === null) { /* reject the config value */ }","typeGuard":"function isDurationString(v: unknown): v is string {\n  return typeof v === 'string' && /^\\d+(?:\\.d+)?(?:ms|s|m)?$/.test(v.trim())\n}","tryCatchPattern":null,"preventionTips":["Restrict durations to 'NNms', 'NNs', 'NNm', or a bare millisecond number.","Trim and reject empty input before parsing.","Convert hours/days to minutes or ms upstream; this parser does not accept h/d.","Validate config-supplied durations at load time, not at first event."],"tags":["temporal-detector","parsing","config","duration"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}