{"record":{"id":"2eb174c67fac6b24","repo":"laurent22/joplin","slug":"errorprefix-key-must-be-an-array","errorCode":null,"errorMessage":"${errorPrefix}: ${key} must be an array","messagePattern":"(.+?): (.+?) must be an array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/services/voiceTyping/whisper.ts","lineNumber":44,"sourceCode":"\t\t\t\tthrow new Error(`${errorPrefix}: Field \"prompts\" is not an object`);\n\t\t\t}\n\n\t\t\tfor (const [key, value] of Object.entries(json.prompts)) {\n\t\t\t\tif (typeof value !== 'string') {\n\t\t\t\t\tthrow new Error(`${errorPrefix}: Value for key ${key} is ${typeof value}, not string.`);\n\t\t\t\t}\n\t\t\t\tthis.prompts.set(key, value);\n\t\t\t}\n\t\t};\n\t\tconst processOutputSettings = () => {\n\t\t\tif (!('output' in json)) return;\n\t\t\tif (typeof json.output !== 'object') {\n\t\t\t\tthrow new Error(`${errorPrefix}: Field \"output\" is not an object`);\n\t\t\t}\n\n\t\t\tconst getReplacements = (key: string, value: unknown) => {\n\t\t\t\tif (!Array.isArray(value)) {\n\t\t\t\t\tthrow new Error(`${errorPrefix}: ${key} must be an array`);\n\t\t\t\t}\n\n\t\t\t\tconst results: [string, string][] = [];\n\t\t\t\tfor (const replacement of value) {\n\t\t\t\t\tif (!Array.isArray(replacement)) {\n\t\t\t\t\t\tthrow new Error(`${errorPrefix}: values for ${key} must be arrays`);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof replacement[0] !== 'string' || typeof replacement[1] !== 'string') {\n\t\t\t\t\t\tthrow new Error(`${errorPrefix}: values for ${key} must be pairs of strings`);\n\t\t\t\t\t}\n\n\t\t\t\t\tresults.push([replacement[0], replacement[1]]);\n\t\t\t\t}\n\t\t\t\treturn results;\n\t\t\t};\n\n\t\t\tif ('stringReplacements' in json.output) {\n\t\t\t\tthis.stringReplacements = getReplacements('stringReplacements', json.output.stringReplacements);","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/services/voiceTyping/whisper.ts#L26-L62","documentation":"Thrown inside `getReplacements` (used by `processOutputSettings`) when one of the replacement fields (`stringReplacements` or `regexReplacements`) is present but not an array. Each such field must be an array of `[pattern, replacement]` pairs.","triggerScenarios":"Config like `{ \"output\": { \"stringReplacements\": \"foo\" } }` where the field value is not an array. The interpolated `${key}` is the offending field name.","commonSituations":"Hand-edited config; misunderstanding the format and supplying a single pair instead of an array of pairs; migration typo.","solutions":["Wrap the value in an array: `[ [\"foo\", \"bar\"] ]`.","Re-download the model bundle.","Schema-validate that replacement fields are arrays of string pairs."],"exampleFix":"// before\n\"stringReplacements\": [\"foo\", \"bar\"]\n\n// after\n\"stringReplacements\": [[\"foo\", \"bar\"]]","handlingStrategy":"validation","validationCode":"for (const key of ['stringReplacements', 'regexReplacements']) {\n  const v = (config.output ?? {})[key];\n  if (v !== undefined && !Array.isArray(v)) {\n    throw new Error(`Whisper config: ${key} must be an array`);\n  }\n}","typeGuard":"const isReplacementArray = (v: unknown): v is unknown[] => Array.isArray(v);","tryCatchPattern":"null","preventionTips":["Wrap replacement sets in arrays.","Schema-validate items as arrays.","Re-download the bundle on validation failure."],"tags":["voice-typing","whisper","config","validation"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}