deepseek-ai/deepseek-harness · error

client-modules: ${subject} ${field} must be a string array

Error message

client-modules: ${subject} ${field} must be a string array

What it means

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.

Source

Thrown at packages/client/modules/src/client/manifest.ts:122

  /** Rows as the module table consumes them. */
  modules: BootModuleRow[]
  /** Rows as entry composition consumes them. */
  plugins: BootPluginRow[]
}

/**
 * Validate an optional string-array field read from a `dsh.client` declaration
 * or from the boot wire.
 * @param subject - diagnostic prefix naming the package or the wire row.
 * @param field - field name as it appears in the diagnostic.
 * @param value - the raw field value.
 * @returns the validated array, or undefined when the field is absent.
 * @throws {Error} when the value is present but is not an array of strings.
 */
export function optionalStringArray(subject: string, field: string, value: unknown): string[] | undefined {
  if (value === undefined) return undefined
  if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
    throw new Error(`client-modules: ${subject} ${field} must be a string array`)
  }
  return value as string[]
}

/**
 * Normalize a module specifier onto the graph row that owns it: a plugin bundle
 * IS its package's client half, so `<id>/client` (the exports subpath external
 * bundles emit) and the bare package name resolve to the same exports. Both the
 * require path and graph composition normalize here, which is what lets each
 * importing package request the subpath its own code imports.
 * @param spec - module specifier as a bundle requires it or a declaration spells it.
 * @returns the specifier with a trailing `/client` removed.
 */
export function stripClientSuffix(spec: string): string {
  return spec.endsWith('/client') ? spec.slice(0, -'/client'.length) : spec
}

/**

View on GitHub (pinned to b150a551b8)

Solutions

  1. Make the field an array of strings ("inject": ["runtime"]) or remove it — omission is valid
  2. Use the subject and field named in the message to locate the offending package.json or wire row
  3. Rebuild the client bundle / boot graph after fixing the manifest

Example fix

// before
"dsh": { "client": { "platform": "web", "inject": "runtime", "external": [7] } }

// after
"dsh": { "client": { "platform": "web", "inject": ["runtime"] } }
Defensive patterns

Strategy: type-guard

Validate before calling

function assertOptionalStringArray(field: string, value: unknown): void {
  if (value === undefined) return
  if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
    throw new Error(`${field} must be a string array`)
  }
}

assertOptionalStringArray('inject', pkg.dsh?.client?.inject)
assertOptionalStringArray('external', pkg.dsh?.client?.external)

Type guard

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === 'string')
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24). Data as JSON: /api/errors/e9819253259ce7db. Report an issue: GitHub.