{"record":{"id":"1ed021803bc2c1eb","repo":"deepseek-ai/deepseek-harness","slug":"basiccompactionconfig-modelpolicies-must-be-an-ar","errorCode":null,"errorMessage":"BasicCompactionConfig: modelPolicies must be an array","messagePattern":"BasicCompactionConfig: modelPolicies must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/compaction/compaction-basic/src/config.ts","lineNumber":197,"sourceCode":"/** Reject a capacity-independent retention conflict at plugin load. */\nfunction validateRatioRetention(\n  thresholdRatio: number,\n  retention: ResolvedRetention,\n  name: string,\n): void {\n  if (retention.retainRatio !== undefined && retention.retainRatio >= thresholdRatio) {\n    throw new Error(\n      `${name}: retainRatio (${retention.retainRatio}) must be less than `\n      + `the resolved thresholdRatio (${thresholdRatio})`,\n    )\n  }\n}\n\n/** Validate, detach, and reject duplicate exact-target policies. */\nfunction resolveModelPolicies(configured: unknown): ModelCompactPolicyConfig[] {\n  if (configured === undefined) return []\n  if (!Array.isArray(configured)) {\n    throw new Error('BasicCompactionConfig: modelPolicies must be an array')\n  }\n  const seen = new Set<string>()\n  return configured.map((source: unknown, index) => {\n    const name = `BasicCompactionConfig: modelPolicies[${index}]`\n    assertModelPolicy(source, name)\n    const key = `${source.provider}\\u0000${source.model}`\n    if (seen.has(key)) {\n      throw new Error(\n        `BasicCompactionConfig: duplicate model policy for ${source.provider}/${source.model}`,\n      )\n    }\n    seen.add(key)\n    return { ...source }\n  })\n}\n\n/** Validate one untrusted exact-target override and narrow its public type. */\nfunction assertModelPolicy(","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/compaction/compaction-basic/src/config.ts#L179-L215","documentation":"compaction-basic validates its whole plugin configuration eagerly at load time in resolveConfig(). modelPolicies holds exact provider/model override objects and must be an Array; resolveModelPolicies() rejects any other type before defaults are applied, so a malformed override table cannot silently match no policy.","triggerScenarios":"A cordis.yml compaction-basic config block (or a programmatic resolveConfig(config) call) supplies modelPolicies as a YAML/JSON mapping instead of a sequence (e.g. modelPolicies: { deepseek: ... }), a scalar, or a single bare object. YAML is the usual culprit: keyed entries under modelPolicies: parse as an object, not a list.","commonSituations":"Hand-editing cordis.yml and keying policies by provider name (natural for maps elsewhere in the file); copy-pasting a single { provider, model } object without wrapping it in a list; config pipelines re-serializing the array as an object.","solutions":["Rewrite modelPolicies as a YAML block list: each entry starts with '- ' and carries provider, model, and override fields","If you keyed entries by provider (modelPolicies: { deepseek: { model: chat, ... } }), convert each group into list items that state both provider and model explicitly","If no per-model overrides are needed, delete the modelPolicies key entirely — top-level defaults then apply to every routed model","Type the field as ModelCompactPolicyConfig[] in programmatic config so the compiler rejects non-array shapes"],"exampleFix":"# cordis.yml — before (parses as an object)\nplugins:\n  '@deepseek-ai/dsh-compaction-basic':\n    config:\n      modelPolicies:\n        deepseek:\n          model: deepseek-chat\n          maxTokens: 16384\n\n# after (array of exact-target overrides)\nplugins:\n  '@deepseek-ai/dsh-compaction-basic':\n    config:\n      modelPolicies:\n        - provider: deepseek\n          model: deepseek-chat\n          maxTokens: 16384","handlingStrategy":"validation","validationCode":"// Pre-flight before booting the plugin:\nif (config.modelPolicies !== undefined && !Array.isArray(config.modelPolicies)) {\n  throw new TypeError(\n    `modelPolicies must be an array, got ${typeof config.modelPolicies}`,\n  )\n}\nconst resolved = resolveConfig(config)","typeGuard":null,"tryCatchPattern":"try {\n  resolveConfig(config)\n} catch (err) {\n  // Static config failure: report the source and stop; never retry —\n  // the same config produces the same throw.\n  throw new Error(\n    `cordis.yml compaction-basic config invalid: ${(err as Error).message}`,\n    { cause: err },\n  )\n}","preventionTips":["Type modelPolicies as ModelCompactPolicyConfig[] in any code that builds the config","In YAML, start every entry with '- provider:' and confirm the block parses as a sequence, not a mapping","Run the repo's config verification (verify-cordis-config / doc-sync gates) after editing cordis.yml"],"tags":["config","validation","yaml","plugin-config","compaction"],"backgroundTag":"config-type-mismatch","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}