{"record":{"id":"e9819253259ce7db","repo":"deepseek-ai/deepseek-harness","slug":"client-modules-subject-field-must-be-a-stri","errorCode":null,"errorMessage":"client-modules: ${subject} ${field} must be a string array","messagePattern":"client-modules: (.+?) (.+?) must be a string array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/client/modules/src/client/manifest.ts","lineNumber":122,"sourceCode":"  /** Rows as the module table consumes them. */\n  modules: BootModuleRow[]\n  /** Rows as entry composition consumes them. */\n  plugins: BootPluginRow[]\n}\n\n/**\n * Validate an optional string-array field read from a `dsh.client` declaration\n * or from the boot wire.\n * @param subject - diagnostic prefix naming the package or the wire row.\n * @param field - field name as it appears in the diagnostic.\n * @param value - the raw field value.\n * @returns the validated array, or undefined when the field is absent.\n * @throws {Error} when the value is present but is not an array of strings.\n */\nexport function optionalStringArray(subject: string, field: string, value: unknown): string[] | undefined {\n  if (value === undefined) return undefined\n  if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {\n    throw new Error(`client-modules: ${subject} ${field} must be a string array`)\n  }\n  return value as string[]\n}\n\n/**\n * Normalize a module specifier onto the graph row that owns it: a plugin bundle\n * IS its package's client half, so `<id>/client` (the exports subpath external\n * bundles emit) and the bare package name resolve to the same exports. Both the\n * require path and graph composition normalize here, which is what lets each\n * importing package request the subpath its own code imports.\n * @param spec - module specifier as a bundle requires it or a declaration spells it.\n * @returns the specifier with a trailing `/client` removed.\n */\nexport function stripClientSuffix(spec: string): string {\n  return spec.endsWith('/client') ? spec.slice(0, -'/client'.length) : spec\n}\n\n/**","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/modules/src/client/manifest.ts#L104-L140","documentation":"optionalStringArray validates the optional array fields (inject, external) read from dsh.client declarations in package.json and from boot-wire rows. An absent field is valid; a present field must be an array containing only strings. Anything else — a bare string, an object, numbers inside the array — fails the scan with the subject (package or wire row) and field named in the message.","triggerScenarios":"A package.json declaring dsh.client.inject as a bare string ('runtime') or dsh.client.external containing a non-string entry ([7]); the same malformed shapes arriving on a boot-graph row.","commonSituations":"Hand-writing a dsh.client manifest and abbreviating a one-element list as a string; generators emitting numbers into external; copy-pasting manifest shapes from a different version's docs.","solutions":["Make the field an array of strings (\"inject\": [\"runtime\"]) or remove it — omission is valid","Use the subject and field named in the message to locate the offending package.json or wire row","Rebuild the client bundle / boot graph after fixing the manifest"],"exampleFix":"// before\n\"dsh\": { \"client\": { \"platform\": \"web\", \"inject\": \"runtime\", \"external\": [7] } }\n\n// after\n\"dsh\": { \"client\": { \"platform\": \"web\", \"inject\": [\"runtime\"] } }","handlingStrategy":"type-guard","validationCode":"function assertOptionalStringArray(field: string, value: unknown): void {\n  if (value === undefined) return\n  if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {\n    throw new Error(`${field} must be a string array`)\n  }\n}\n\nassertOptionalStringArray('inject', pkg.dsh?.client?.inject)\nassertOptionalStringArray('external', pkg.dsh?.client?.external)","typeGuard":"function isStringArray(value: unknown): value is string[] {\n  return Array.isArray(value) && value.every(item => typeof item === 'string')\n}","tryCatchPattern":null,"preventionTips":["Always write inject/external as arrays, even with one element","Validate dsh.client manifests with a JSON schema before publishing","Never hand-edit generated boot graphs"],"tags":["manifest","validation","package-json","client-modules"],"backgroundTag":"schema-validation-failed","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}