diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index c51136d198..327dea464f 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -49,7 +49,13 @@ const log = Log.create({ service: "config" }) // Custom merge function that concatenates array fields instead of replacing them // Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here. function mergeConfig(target: Info, source: Info): Info { - return mergeDeep(target, source) as Info + const merged = mergeDeep(target, source) as Info + if (!target.tool_output || !source.tool_output) return merged + if (target.tool_output.truncate !== false && source.tool_output.truncate !== false) return merged + + // Disabled truncation and custom limits are separate config modes; the later layer selects the mode. + merged.tool_output = source.tool_output + return merged } function mergeConfigConcatArrays(target: Info, source: Info): Info { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 913ad049e1..a2ac219c15 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1307,6 +1307,42 @@ test("tool_output only accepts thresholds when truncation is enabled", () => { ).toThrow() }) +it.effect("project tool_output limits replace disabled global truncation", () => + withConfigTree( + { + global: { tool_output: { truncate: false } }, + project: { tool_output: { max_lines: 200 } }, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).tool_output).toEqual({ max_lines: 200 }) + }), + ), +) + +it.effect("project disabled tool_output replaces global limits", () => + withConfigTree( + { + global: { tool_output: { max_lines: 200, max_bytes: 8192 } }, + project: { tool_output: { truncate: false } }, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).tool_output).toEqual({ truncate: false }) + }), + ), +) + +it.effect("enabled tool_output limits still merge across layers", () => + withConfigTree( + { + global: { tool_output: { max_bytes: 8192 } }, + project: { tool_output: { max_lines: 200 } }, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).tool_output).toEqual({ max_bytes: 8192, max_lines: 200 }) + }), + ), +) + // MCP config merging tests it.instance("project config can override MCP server enabled status", () =>