{"record":{"id":"c797688df498b85e","repo":"Yeachan-Heo/oh-my-codex","slug":"name-must-be-a-string","errorCode":null,"errorMessage":"${name} must be a string","messagePattern":"(.+?) must be a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mcp/hermes-bridge.ts","lineNumber":139,"sourceCode":"const OMX_INSTANCE_OPTION = \"@omx_instance_id\";\n\nfunction jsonResult<T extends Record<string, unknown>>(data: T): HermesBridgeResult<T> {\n  return { ok: true, data };\n}\n\nfunction failure<T extends Record<string, unknown> = Record<string, unknown>>(\n  code: HermesBridgeFailureCode,\n  error: string,\n): HermesBridgeResult<T> {\n  return { ok: false, code, error };\n}\n\nfunction normalizeString(value: unknown, name: string, options: { required?: boolean } = {}): string | undefined {\n  if (value == null) {\n    if (options.required) throw new Error(`${name} is required`);\n    return undefined;\n  }\n  if (typeof value !== \"string\") throw new Error(`${name} must be a string`);\n  const trimmed = value.trim();\n  if (!trimmed && options.required) throw new Error(`${name} must be non-empty`);\n  return trimmed || undefined;\n}\n\nfunction requireMutation(args: Record<string, unknown>): void {\n  if (args.allow_mutation !== true) {\n    throw new Error(\"mutating Hermes bridge tools require allow_mutation: true\");\n  }\n}\n\nfunction normalizePositiveInteger(value: unknown, fallback: number, max: number): number {\n  if (value == null) return fallback;\n  const parsed = typeof value === \"number\" ? value : Number.parseInt(String(value), 10);\n  if (!Number.isInteger(parsed) || parsed <= 0) return fallback;\n  return Math.min(parsed, max);\n}\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/mcp/hermes-bridge.ts#L121-L157","documentation":"normalizeString in the Hermes bridge throws 'X must be a string' when an argument is present but has a non-string JSON type (number, boolean, object, array). This guards downstream string operations from type confusion in untyped MCP input.","triggerScenarios":"Passing e.g. session_id: 12345, status: true, or cwd: {\"path\":\"/x\"} to a Hermes bridge tool.","commonSituations":"Client sending numbers for ids, booleans for flags expected as strings, or nested objects where a string is expected — often from auto-generated clients mapping types incorrectly.","solutions":["Convert the value to a string in the client before sending (String(sessionId))","Align the client's type definitions with the tool schema","Avoid wrapping scalars in objects/arrays"],"exampleFix":"// before\n{ session_id: 12345 }\n// after\n{ session_id: \"12345\" }","handlingStrategy":"type-guard","validationCode":"Object.entries(args).forEach(([k,v]) => { if (v != null && typeof v !== 'string') args[k] = String(v); });","typeGuard":"function isStringArgs(a: Record<string, unknown>): a is Record<string, string | undefined> {\n  return Object.values(a).every(v => v == null || typeof v === 'string');\n}","tryCatchPattern":null,"preventionTips":["Serialize ids as strings at the source","Use the tool's generated types in the client","Never send numbers where ids are documented as strings"],"tags":["mcp","hermes-bridge","type-validation"],"backgroundTag":"invalid-argument-type","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}