{"record":{"id":"3b3813543933f587","repo":"deepseek-ai/deepseek-harness","slug":"name-retainratio-and-retaintokens-are-mutually","errorCode":null,"errorMessage":"${name}: retainRatio and retainTokens are mutually exclusive","messagePattern":"(.+?): retainRatio and retainTokens are mutually exclusive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/compaction/compaction-basic/src/config.ts","lineNumber":241,"sourceCode":"  validatePolicy(source, name)\n}\n\n/** Validate the fields common to defaults and exact-target partial overrides. */\nfunction validatePolicy(\n  config: CompactionPolicyConfig | Record<string, unknown>,\n  name: string,\n): void {\n  const thresholdRatio = config.thresholdRatio\n  const retainRatio = config.retainRatio\n  const retainTokens = config.retainTokens\n  const maxTokens = config.maxTokens\n  const compactionRetries = config.compactionRetries\n  const maxOverflowRetries = config.maxOverflowRetries\n  if (thresholdRatio !== undefined) assertRatio(`${name}.thresholdRatio`, thresholdRatio)\n  if (retainRatio !== undefined) assertRatio(`${name}.retainRatio`, retainRatio)\n  if (retainTokens !== undefined) assertNonNegativeInteger(`${name}.retainTokens`, retainTokens)\n  if (retainRatio !== undefined && retainTokens !== undefined) {\n    throw new Error(`${name}: retainRatio and retainTokens are mutually exclusive`)\n  }\n  if (maxTokens !== undefined) assertPositiveInteger(`${name}.maxTokens`, maxTokens)\n  if (compactionRetries !== undefined) {\n    assertNonNegativeInteger(`${name}.compactionRetries`, compactionRetries)\n  }\n  if (maxOverflowRetries !== undefined) {\n    assertNonNegativeInteger(`${name}.maxOverflowRetries`, maxOverflowRetries)\n  }\n\n  validateSummarizationPair(config, name)\n}\n\n/** Require one scope to omit, clear, or replace the summarization target as a pair. */\nfunction validateSummarizationPair(\n  config: CompactionPolicyConfig | Record<string, unknown>,\n  name: string,\n): void {\n  const provider = config.summarizationProvider","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/compaction/compaction-basic/src/config.ts#L223-L259","documentation":"Retention (the verbatim tail kept after compaction) has exactly one form per config scope: a fraction of the model's context window (retainRatio) or an absolute token count (retainTokens). resolveRetention() prefers retainTokens when both are present, so validatePolicy() rejects the combination at load instead of silently ignoring one setting. The rule applies to top-level defaults and to each modelPolicies entry separately.","triggerScenarios":"Both retainRatio and retainTokens set in the same scope: the top-level config block, or one modelPolicies entry. Cross-scope mixing is legal — an entry may set retainTokens while the top level sets retainRatio; the entry replaces the inherited retention.","commonSituations":"Tuning compaction and forgetting an old retainRatio is still present; copying a per-model entry that had retainTokens into a top-level block that already sets retainRatio; assuming the more specific value wins silently.","solutions":["Pick one retention form per scope: delete retainRatio or delete retainTokens","For absolute budgets keep retainTokens and remove retainRatio; for window-relative budgets do the opposite","If you want per-model absolutes plus a global ratio, keep them in different scopes — entry-level retainTokens overriding top-level retainRatio is valid"],"exampleFix":"# before — both forms in the same scope\nretainRatio: 0.1\nretainTokens: 4000\n\n# after — absolute budget only\nretainTokens: 4000","handlingStrategy":"validation","validationCode":"for (const scope of [config, ...(config.modelPolicies ?? [])]) {\n  if (scope.retainRatio !== undefined && scope.retainTokens !== undefined) {\n    throw new Error('choose retainRatio OR retainTokens per config scope')\n  }\n}\nresolveConfig(config)","typeGuard":null,"tryCatchPattern":"try {\n  resolveConfig(config)\n} catch (err) {\n  if ((err as Error).message.includes('mutually exclusive')) {\n    // The message names the scope: top-level 'BasicCompactionConfig' or 'modelPolicies[i]'.\n    throw new Error(\n      'retention conflict: keep exactly one of retainRatio / retainTokens in the named scope',\n      { cause: err },\n    )\n  }\n  throw err\n}","preventionTips":["Treat retention as one knob per block: fraction OR absolute tokens","State the chosen form in your config template so editors do not add the second key","A modelPolicies entry overriding the top-level form is legal — only same-scope duplicates fail"],"tags":["config","validation","mutually-exclusive","compaction"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}