Skip to content

Commit 3c403c4

Browse files
Fix config-location migrator eating the user config when project dir is the config home
A project-scope config-location migration could move the USER config aside and leave the user on schema defaults. The project legacy-source list includes a bare-root source <root>/magic-context.{jsonc,json} (Magic Context historically allowed a repo-root config). When a session's project directory IS the CortexKit user config home (e.g. opencode opened with cwd ~/.config/cortexkit), that bare-root source resolves to ~/.config/cortexkit/magic-context.jsonc — the user config itself. The project migration then 'migrated' it into <root>/.cortexkit/magic-context.jsonc and renamed the original to .MOVED_READPLEASE, so the user config path went empty and MC ran on defaults. (Same collision class for a project opened in ~/.config/opencode or ~/.pi/agent, where bare-root resolves to a user legacy config.) Fix: filter project-scope legacy sources against the full user-scope path set (the CortexKit user target in both extensions + the OpenCode/Pi user legacy paths). A project migration can never move a user-scope file. Pi inherits the fix automatically (shared resolveLegacyConfigSources from @magic-context/core). Added two regression tests covering the cortexkit-home and opencode-home cwds. No data is lost by the original bug (content is preserved at both the misplaced target and the .MOVED_READPLEASE marker), but the user silently ran on defaults. Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
1 parent 0aff604 commit 3c403c4

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

packages/plugin/src/config/migrate-config-location.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,48 @@ describe("resolveLegacyConfigSources", () => {
285285
true,
286286
);
287287
});
288+
289+
it("never lists a user-scope config as a project source when the project dir is the CortexKit config home", () => {
290+
// Regression: opencode opened with cwd = ~/.config/cortexkit made the
291+
// bare-root project source `<root>/magic-context.jsonc` collide with the
292+
// USER config, so the project migration ate the user config into
293+
// `<root>/.cortexkit/` and left the user on schema defaults.
294+
const prev = process.env.XDG_CONFIG_HOME;
295+
const home = tmp();
296+
try {
297+
process.env.XDG_CONFIG_HOME = home;
298+
const cortexkitHome = join(home, "cortexkit");
299+
const sources = resolveLegacyConfigSources(cortexkitHome);
300+
const projectPaths = sources.project.map((s) => s.path);
301+
// The user config path must NOT be a project migration source.
302+
expect(projectPaths).not.toContain(join(cortexkitHome, "magic-context.jsonc"));
303+
expect(projectPaths).not.toContain(join(cortexkitHome, "magic-context.json"));
304+
// The genuine project subdir sources are still present.
305+
expect(projectPaths).toContain(join(cortexkitHome, ".opencode", "magic-context.jsonc"));
306+
expect(projectPaths).toContain(join(cortexkitHome, ".pi", "magic-context.jsonc"));
307+
} finally {
308+
if (prev === undefined) delete process.env.XDG_CONFIG_HOME;
309+
else process.env.XDG_CONFIG_HOME = prev;
310+
rmSync(home, { recursive: true, force: true });
311+
}
312+
});
313+
314+
it("never lists the OpenCode/Pi user legacy paths as project sources when the project dir is the user config dir", () => {
315+
// Same collision class for a project opened directly in ~/.config/opencode
316+
// (bare-root would resolve to the OpenCode user legacy config).
317+
const prev = process.env.XDG_CONFIG_HOME;
318+
const home = tmp();
319+
try {
320+
process.env.XDG_CONFIG_HOME = home;
321+
const opencodeHome = join(home, "opencode");
322+
const sources = resolveLegacyConfigSources(opencodeHome);
323+
const projectPaths = sources.project.map((s) => s.path);
324+
expect(projectPaths).not.toContain(join(opencodeHome, "magic-context.jsonc"));
325+
expect(projectPaths).not.toContain(join(opencodeHome, "magic-context.json"));
326+
} finally {
327+
if (prev === undefined) delete process.env.XDG_CONFIG_HOME;
328+
else process.env.XDG_CONFIG_HOME = prev;
329+
rmSync(home, { recursive: true, force: true });
330+
}
331+
});
288332
});

packages/plugin/src/config/migrate-config-location.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,44 @@ function legacySourcesForBase(basePath: string, label: string): LegacyConfigSour
110110
];
111111
}
112112

113+
/**
114+
* Every user-scope config path the migrator knows: the CortexKit user target
115+
* plus the user legacy sources. A project-scope migration must NEVER move any of
116+
* these (they are user-scope files, not project files).
117+
*/
118+
function userScopeConfigPaths(): Set<string> {
119+
return new Set<string>([
120+
// The CortexKit user target, both extensions: the bare-root project
121+
// source produces `.jsonc` AND `.json`, so exclude both or a stray
122+
// `~/.config/cortexkit/magic-context.json` would still be eaten.
123+
`${cortexKitUserConfigBasePath()}.jsonc`,
124+
`${cortexKitUserConfigBasePath()}.json`,
125+
join(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.jsonc`),
126+
join(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.json`),
127+
join(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.jsonc`),
128+
join(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.json`),
129+
]);
130+
}
131+
113132
/**
114133
* The legacy config locations to migrate FROM, by scope. Each base produces a
115134
* `.jsonc` and a `.json` candidate; whichever exists migrates, target is always
116135
* `.jsonc`. The bare-root project source (`<root>/magic-context.*`) is unique to
117136
* Magic Context (AFT never had it) — omitting it would orphan repo-root configs.
137+
*
138+
* Project sources are filtered against the user-scope path set: when a session's
139+
* project directory IS the user config home (e.g. opencode opened in
140+
* `~/.config/cortexkit`), the bare-root project source `<root>/magic-context.jsonc`
141+
* resolves to the USER config path. Without this guard the project migration
142+
* would "migrate" the user's own config into `<root>/.cortexkit/` and rename the
143+
* original aside, leaving the user on schema defaults (the config-eats-itself
144+
* bug). A project migration must never touch a user-scope file.
118145
*/
119146
export function resolveLegacyConfigSources(directory: string): {
120147
user: LegacyConfigSource[];
121148
project: LegacyConfigSource[];
122149
} {
150+
const userPaths = userScopeConfigPaths();
123151
return {
124152
user: [
125153
...legacySourcesForBase(
@@ -138,7 +166,7 @@ export function resolveLegacyConfigSources(directory: string): {
138166
"OpenCode project",
139167
),
140168
...legacySourcesForBase(join(directory, ".pi", CONFIG_FILE_BASENAME), "Pi project"),
141-
],
169+
].filter((source) => !userPaths.has(source.path)),
142170
};
143171
}
144172

0 commit comments

Comments
 (0)