fix(#2840): exclude runtime from defaults.json spread into project config (#2985)

* test(#2840): add regression for runtime poisoning from defaults.json

* fix(#2840): exclude runtime from defaults.json spread into project config

runtime is host-specific (written by whichever installer ran last). On a
machine with 2+ runtimes, it poisons every new project config — e.g. a Codex
install's runtime:'codex' leaks into Claude Code projects. Now excluded from
the userDefaults spread, mirroring the resolve_model_ids guard (#2297).

* chore(#2840): add changeset fragment

* fix(#2840): add new test file to lint-test-file-count allowlist

* chore(#2840): backfill changeset PR number 2985

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-01 16:33:43 -04:00
committed by GitHub
parent 33fd203ccd
commit b62589b73f
4 changed files with 92 additions and 1 deletions

View File

@@ -329,10 +329,20 @@ function buildNewProjectConfig(userChoices: Record<string, unknown>): Record<str
const ch = choices as Record<string, Record<string, unknown>>;
const hd = hardcoded as Record<string, Record<string, unknown>>;
// #2840: `runtime` is host-specific (written by the installer for whichever
// runtime's install ran last). On a machine with 2+ runtimes, it poisons every
// new project config — e.g. a Codex install's `runtime:"codex"` leaks into
// Claude Code projects, resolving agents to wrong model IDs. `resolve_model_ids`
// already has a per-install guard (#2297); `runtime` gets the same treatment
// by excluding it from the defaults spread. Projects detect the runtime from
// the install path / .gsd-runtime marker, not from a copied config key.
const safeDefaults = { ...userDefaults };
delete safeDefaults['runtime'];
// Three-level deep merge: hardcoded <- userDefaults <- choices
const config: Record<string, unknown> = {
...hardcoded,
...userDefaults,
...safeDefaults,
...choices,
git: {
...hd['git'],