{"record":{"id":"87a29798ed518351","repo":"Yeachan-Heo/oh-my-codex","slug":"allowed-modes-must-be-a-non-empty-string-array","errorCode":null,"errorMessage":"`allowed_modes` must be a non-empty string array","messagePattern":"`allowed_modes` must be a non-empty string array","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/tmux-hook.ts","lineNumber":131,"sourceCode":"  const parsed = raw as Record<string, unknown>;\n  if (parsed.enabled !== true && parsed.enabled !== false) {\n    throw new Error('`enabled` must be boolean');\n  }\n  const target = parsed.target;\n  if (!target || typeof target !== 'object') {\n    throw new Error('`target` is required');\n  }\n  const targetObj = target as Record<string, unknown>;\n  if (targetObj.type !== 'session' && targetObj.type !== 'pane') {\n    throw new Error('`target.type` must be \"session\" or \"pane\"');\n  }\n  if (typeof targetObj.value !== 'string' || targetObj.value.trim() === '') {\n    throw new Error('`target.value` must be a non-empty string');\n  }\n\n  const allowedModes = parsed.allowed_modes;\n  if (!Array.isArray(allowedModes) || allowedModes.length === 0 || allowedModes.some(v => typeof v !== 'string')) {\n    throw new Error('`allowed_modes` must be a non-empty string array');\n  }\n\n  const cooldown = parsed.cooldown_ms;\n  const maxInjections = parsed.max_injections_per_session;\n  if (typeof cooldown !== 'number' || cooldown < 0 || !Number.isFinite(cooldown)) {\n    throw new Error('`cooldown_ms` must be a non-negative number');\n  }\n  if (typeof maxInjections !== 'number' || maxInjections < 1 || !Number.isFinite(maxInjections)) {\n    throw new Error('`max_injections_per_session` must be >= 1');\n  }\n\n  const promptTemplate = parsed.prompt_template;\n  const marker = parsed.marker;\n  if (typeof promptTemplate !== 'string' || promptTemplate.trim() === '') {\n    throw new Error('`prompt_template` must be a non-empty string');\n  }\n  if (typeof marker !== 'string' || marker.trim() === '') {\n    throw new Error('`marker` must be a non-empty string');","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/tmux-hook.ts#L113-L149","documentation":"The config's allowed_modes field must be an array containing at least one string; it controls which tmux modes (e.g. vi/copy mode) permit injection. Empty arrays, non-arrays, or arrays with non-string entries are rejected.","triggerScenarios":"allowed_modes omitted (undefined), set to a single string \"vi\" instead of [\"vi\"], an empty array [], or [1, 2].","commonSituations":"Users writing a scalar where an array is expected; commenting out all modes; templating engines emitting empty lists.","solutions":["Set \"allowed_modes\": [\"vi\"] (array of strings, at least one)","Check supported mode names in the docs/help","Regenerate config via omx tmux-hook init"],"exampleFix":"// before\n{\"allowed_modes\": \"vi\"}\n// after\n{\"allowed_modes\": [\"vi\"]}","handlingStrategy":"type-guard","validationCode":"const m = cfg.allowed_modes;\nif (!Array.isArray(m) || m.length === 0 || m.some(x => typeof x !== 'string')) throw new Error('bad allowed_modes');","typeGuard":"const isStringArray = (v: unknown): v is string[] => Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string');","tryCatchPattern":"try { await loadConfig(); } catch (e) { if (e instanceof Error && e.message.includes('allowed_modes')) fixModes(); }","preventionTips":["Always use an array with at least one string, e.g. [\"vi\"]","Avoid scalar shorthand for array fields"],"tags":["config","validation","array","tmux-hook"],"backgroundTag":"config-schema-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}