Skip to content

fix(config): strip Zod defaults from per-layer config before merge#16977

Open
Altman-conquer wants to merge 2 commits intoanomalyco:devfrom
Altman-conquer:fix/config-layer-preserves-input-keys
Open

fix(config): strip Zod defaults from per-layer config before merge#16977
Altman-conquer wants to merge 2 commits intoanomalyco:devfrom
Altman-conquer:fix/config-layer-preserves-input-keys

Conversation

@Altman-conquer
Copy link

@Altman-conquer Altman-conquer commented Mar 11, 2026

Issue for this PR

Closes #16897

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When loading a config file, each layer is parsed through Info.safeParse(normalized). The problem is that Zod's .default() values fill in every missing field with a default. These defaulted keys then go into mergeDeep, which means a higher-priority layer (e.g. project config) silently overwrites settings from lower-priority layers (e.g. global config) for fields the user never actually set.

The fix strips any key from the parsed result that was not present in the original input object before Zod touched it. Only keys the user explicitly wrote in their config file participate in the merge. The $schema key is excluded from removal since it is injected by the code, not the user.

How did you verify your code works?

Traced through the config load path manually. The fix is minimal — it only removes keys that Zod added; it does not change keys that were already there. Matches exactly what the issue describes.

Screenshots / recordings

N/A — no UI changes

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

When Info.safeParse(normalized) runs on a config layer it applies any
.default() values defined in the schema to fields that are absent in that
layer's input.  During mergeDeep these Zod-injected defaults then
overwrite explicitly-set values from lower-priority layers (e.g. the
global user config), which violates the documented config hierarchy.

Fix: after parsing each file layer, delete any top-level key from the
parsed result that was not present in the raw input object.  The one
exception is $schema, which is a synthetic key added by code (not by
Zod defaults) and must always be preserved.

This means contributors can now add config fields with meaningful
.default() values without silently breaking the merge semantics.

Fixes anomalyco#16897
@github-actions github-actions bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Mar 11, 2026
@github-actions
Copy link
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@amelvil2-ford
Copy link

amelvil2-ford commented Mar 11, 2026

Thanks for looking into this!

I mentioned this in the issue, but just FYI, this may be a breaking change (users might notice a change in behavior with their settings).

E.g. it's possible that somebody is expecting the broken behavior where defaults are applied after loading project settings files, but whether or not this is a "big deal", I'll leave that up to the leaders of the opencode project.

For my setup I'd prefer it to follow the hierarchy given by the docs (as in, defaults, then user config, then project level config).

I'm not sure if it merits a new schema version or some other indication that the parsing algorithm has changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

opencode will use default values instead of user config values when a project level config exists

2 participants