{"record":{"id":"b9eb013bed2d40d6","repo":"openclaw/openclaw","slug":"key-must-be-an-integer","errorCode":null,"errorMessage":"${key} must be an integer","messagePattern":"(.+?) must be an integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/codex/src/supervision-tools.ts","lineNumber":219,"sourceCode":"    if (!isRecord(entry)) {\n      throw new Error(`Codex thread/list returned an invalid entry at data[${index}]`);\n    }\n    readCompatThreadId(entry.id, \"thread/list\", index);\n    return entry;\n  });\n}\n\nfunction readBooleanParam(params: Record<string, unknown>, key: string): boolean {\n  return params[key] === true;\n}\n\nfunction readIntegerParam(params: Record<string, unknown>, key: string): number | undefined {\n  const value = params[key];\n  if (value === undefined) {\n    return undefined;\n  }\n  if (typeof value !== \"number\" || !Number.isInteger(value)) {\n    throw new Error(`${key} must be an integer`);\n  }\n  if (value < 1 || value > 1000) {\n    throw new Error(`${key} must be between 1 and 1000`);\n  }\n  return value;\n}\n\nfunction readModeParam(params: Record<string, unknown>): CodexSupervisorTurnMode | undefined {\n  const mode = readStringParam(params, \"mode\");\n  if (!mode) {\n    return undefined;\n  }\n  if (mode === \"auto\" || mode === \"start\" || mode === \"steer\") {\n    return mode;\n  }\n  throw new Error(\"mode must be auto, start, or steer\");\n}\n","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/codex/src/supervision-tools.ts#L201-L237","documentation":"Thrown by readIntegerParam when a value is present for an integer parameter but is not a number or fails Number.isInteger. The only caller is codex_sessions_list reading 'max_stored_sessions'. This is defense-in-depth behind the Typebox SessionsListParamsSchema which should already reject non-integers before execute() runs.","triggerScenarios":"Calling codex_sessions_list with max_stored_sessions:'100' (string), 1.5 (float), true, or an object reaches this throw only if the value bypassed the Typebox schema (raw internal invocation, test seam, or an older MCP path).","commonSituations":"Invoking the tool outside the schema-validated agent-tool path; passing JSON where the value is a quoted string; programmatic callers that skip validation.","solutions":["Pass max_stored_sessions as a JS number literal, not a string (e.g., 100, not \"100\").","Ensure tool calls go through the Typebox-validated surface so the schema rejects bad types first.","When calling via JSON RPC/MCP, encode the value as an unquoted JSON number."],"exampleFix":"// before\ncodex_sessions_list({ max_stored_sessions: \"100\" })\n// after\ncodex_sessions_list({ max_stored_sessions: 100 })","handlingStrategy":"validation","validationCode":"function asOptionalInt(v: unknown, key: string): number | undefined {\n  if (v === undefined || v === null) return undefined;\n  if (typeof v !== \"number\" || !Number.isInteger(v)) {\n    throw new Error(`${key} must be an integer`);\n  }\n  return v;\n}","typeGuard":"const isOptionalInteger = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isInteger(v);","tryCatchPattern":null,"preventionTips":["Always pass integers as JS numbers, not strings.","Let the Typebox schema validate inputs before tool execute runs.","For JSON transports, encode numbers without quotes."],"tags":["validation","parameter","types","codex"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}