Skip to content

fix(installer): deep-merge object config defaults in settings merger#57

Merged
arzafran merged 1 commit into
mainfrom
feat/installer-deep-merge-defaults
Jun 19, 2026
Merged

fix(installer): deep-merge object config defaults in settings merger#57
arzafran merged 1 commit into
mainfrom
feat/installer-deep-merge-defaults

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

When you re-run the installer, a new nested setting that a sync added — like attribution.sessionUrl in v11.27.0 — now actually lands in your ~/.claude/settings.json. Before this, the installer kept your existing attribution block exactly as-is and quietly dropped the new sub-key, so the version number went up but the setting never arrived. (That's exactly what happened on the v11.27.0 install — it had to be patched by hand.)

Summary

  • The merger's default strategy (userWinsScalarStrategy, used for any key without a dedicated handler: attribution, sandbox, spinnerVerbs, …) previously did { ...team, ...user } — the user's whole object shadowed the team's, so team-only sub-keys were lost.
  • New recursive deepMergeUserWins helper: plain objects deep-merge (team-only sub-keys land; user's customized sub-keys win on conflict). Arrays and object↔scalar shape mismatches keep user-wins-whole — a replaced array or retyped field is a deliberate override, not a partial one.
  • New MergeAccounting.defaultsAdded counter + install line ("Added N new team default(s) into existing settings block(s)") so a landing default is visible instead of silent.
  • Dedicated strategies (permissions, hooks, env, statusLine, mcpServers) are untouched.

Root cause is also recorded in the team-knowledge repo: cc-settings-installer-skips-nested-config-defaults.

Test Plan

  • bun run typecheck clean
  • bun test — 539 pass / 0 fail (6 new merger tests)
  • bun run lint — my files clean (3 pre-existing warnings in tests/mcp.test.ts unrelated)
  • New tests cover: team-only sub-key lands (+accounting); user sub-key wins on conflict; depth > 1 recursion; arrays stay whole; object↔scalar mismatch; end-to-end attribution.sessionUrl regression lock
  • Existing golden-migration fixtures unaffected (they exercise only dedicated-strategy keys)

The setup.sh merger preserved a user's existing settings.json object blocks
whole, so a new nested config default added by a sync never reached existing
installs. Surfaced installing v11.27.0: attribution.sessionUrl showed in
`bun run compose` but not in the merged settings.json — the user's existing
attribution block shadowed the team's, version sentinel bumped, setting never
landed.

userWinsScalarStrategy now deep-merges plain objects (new recursive
deepMergeUserWins helper): team-only sub-keys land while user customizations
win on conflict. Arrays and object/scalar mismatches stay user-wins-whole.
New defaultsAdded accounting surfaces a landing default instead of silence.

6 tests added; 539 pass / 0 fail.
@arzafran arzafran merged commit bf9bfdd into main Jun 19, 2026
15 checks passed
@arzafran arzafran deleted the feat/installer-deep-merge-defaults branch June 19, 2026 13:01
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.

1 participant