Skip to content

Commit 5eb373a

Browse files
committed
Fail closed when a named profile file is missing
1 parent 60a0501 commit 5eb373a

3 files changed

Lines changed: 39 additions & 6 deletions

File tree

docs/IMPLEMENTATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ prerequisites; Ollama installation remains outside this flow.
337337
Profiles supply per-project or named-profile overrides for `model` and `systemPromptExtensions` (the only allowed keys; any other key is rejected on load).
338338

339339
- Project profile: `.corbits/profile.json` in the repo root — committed, credential-free.
340-
- Named profiles: `~/.corbits/profiles/<name>.json` — user-level overrides, inherited via the `profile` key or the `--profile` flag.
340+
- Named profiles: `~/.corbits/profiles/<name>.json` — user-level overrides, inherited via the `profile` key or the `--profile` flag. A missing named file fails closed. A missing project `profile.json` overlay is optional.
341341

342342
```json
343343
{

src/config/profiles.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,19 @@ export async function loadProfile(path: string): Promise<ProfileConfig | null> {
7474
export async function resolveProfile(
7575
cwd: string,
7676
profileName?: string,
77+
home: string = homedir(),
7778
): Promise<ProfileConfig> {
7879
const projectProfile = await loadProfile(projectProfilePath(cwd));
7980

8081
const namedProfileName = profileName ?? projectProfile?.profile;
8182

8283
let namedProfile: ProfileConfig | null = null;
8384
if (namedProfileName !== undefined) {
84-
const namedPath = join(profilesDir(), `${namedProfileName}.json`);
85+
const namedPath = join(profilesDir(home), `${namedProfileName}.json`);
8586
namedProfile = await loadProfile(namedPath);
87+
if (namedProfile === null) {
88+
throw new Error(`Profile not found: ${namedPath}`);
89+
}
8690
}
8791

8892
// Merge: project profile fields override named profile fields.

src/profiles.test.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,31 @@ test("resolveProfile returns empty object when no profile files exist", async ()
8585
expect(result).toEqual({});
8686
});
8787

88+
test("resolveProfile throws when --profile names a missing file", async () => {
89+
const home = makeTmp();
90+
const cwd = makeTmp();
91+
await mkdir(cwd, { recursive: true });
92+
const name = "does-not-exist";
93+
const missingPath = join(profilesDir(home), `${name}.json`);
94+
expect(await loadProfile(missingPath)).toBeNull();
95+
await expect(resolveProfile(cwd, name, home)).rejects.toThrow(missingPath);
96+
});
97+
98+
test("resolveProfile loads a valid named profile", async () => {
99+
const home = makeTmp();
100+
const cwd = makeTmp();
101+
await mkdir(cwd, { recursive: true });
102+
const namedDir = join(home, ".corbits", "profiles");
103+
await mkdir(namedDir, { recursive: true });
104+
await writeFile(
105+
join(namedDir, "work.json"),
106+
JSON.stringify({ model: "named-model" }),
107+
);
108+
const result = await resolveProfile(cwd, "work", home);
109+
expect(result.model).toBe("named-model");
110+
expect(result.profile).toBe("work");
111+
});
112+
88113
test("resolveProfile applies project profile fields", async () => {
89114
const cwd = makeTmp();
90115
const dir = join(cwd, ".corbits");
@@ -101,16 +126,20 @@ test("resolveProfile applies project profile fields", async () => {
101126
expect(result.systemPromptExtensions).toEqual(["ext1"]);
102127
});
103128

104-
test("resolveProfile surfaces profile name when set", async () => {
129+
test("resolveProfile throws when a named profile key points at a missing file", async () => {
130+
const home = makeTmp();
105131
const cwd = makeTmp();
106132
const dir = join(cwd, ".corbits");
107133
await mkdir(dir, { recursive: true });
134+
const name = "no-such-named-profile";
108135
await writeFile(
109136
join(dir, "profile.json"),
110-
JSON.stringify({ profile: "work" }),
137+
JSON.stringify({ profile: name }),
138+
);
139+
const missingPath = join(profilesDir(home), `${name}.json`);
140+
await expect(resolveProfile(cwd, undefined, home)).rejects.toThrow(
141+
missingPath,
111142
);
112-
const result = await resolveProfile(cwd);
113-
expect(result.profile).toBe("work");
114143
});
115144

116145
test("resolveProfile: project profile fields override named profile fields", async () => {

0 commit comments

Comments
 (0)