{"record":{"id":"89e6edc07ee6166d","repo":"Yeachan-Heo/oh-my-codex","slug":"mode-must-be-a-string","errorCode":null,"errorMessage":"mode must be a string","messagePattern":"mode must be a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mcp/state-paths.ts","lineNumber":132,"sourceCode":"  const normalized = sessionId.trim();\n  return SESSION_ID_PATTERN.test(normalized) ? normalized : undefined;\n}\n\nexport function validateSessionId(sessionId: unknown): string | undefined {\n  if (sessionId == null) return undefined;\n  if (typeof sessionId !== 'string') {\n    throw new Error('session_id must be a string');\n  }\n  if (!SESSION_ID_PATTERN.test(sessionId)) {\n    throw new Error('session_id must match ^[A-Za-z0-9_-]{1,64}$');\n  }\n  return sessionId;\n}\n\n\nexport function validateStateModeSegment(mode: unknown): string {\n  if (typeof mode !== 'string') {\n    throw new Error('mode must be a string');\n  }\n  const normalized = mode.trim();\n  if (!normalized) {\n    throw new Error('mode must be a non-empty string');\n  }\n  if (normalized.includes('..')) {\n    throw new Error('mode must not contain \"..\"');\n  }\n  if (normalized.includes('/') || normalized.includes('\\\\')) {\n    throw new Error('mode must not contain path separators');\n  }\n  if (!STATE_MODE_SEGMENT_PATTERN.test(normalized)) {\n    throw new Error('mode must match ^[A-Za-z0-9_-]{1,64}$');\n  }\n  return normalized;\n}\n\nexport function getStateFilename(mode: string): string {","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/mcp/state-paths.ts#L114-L150","documentation":"validateStateModeSegment throws when the mode argument is not a string. Mode segments are embedded into state filenames, so the type is enforced before any pattern check.","triggerScenarios":"Passing mode: 123, mode: null-as-value, or an array/object from untyped JSON. (undefined is not special-cased here — typeof check throws for anything non-string.)","commonSituations":"Config parsed from YAML where a bare mode: 42 becomes a number; defaulted mode left undefined; forwarding query params without coercion.","solutions":["Coerce to string at the boundary: String(mode) or template literal","Default the mode explicitly, e.g. mode ?? \"default\"","Type the config schema so mode must be a string"],"exampleFix":"// before\ngetStateFilename(42);\n// after\ngetStateFilename(String(42)); // \"42.json\"","handlingStrategy":"type-guard","validationCode":"if (typeof mode !== 'string') mode = String(mode);","typeGuard":"function isModeString(v: unknown): v is string { return typeof v === 'string'; }","tryCatchPattern":null,"preventionTips":["Type mode as string in config schemas","Default mode: mode ?? 'default'"],"tags":["type-validation","state","mcp"],"backgroundTag":"wrong-argument-type","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}