Skip to content

Commit 7d1f297

Browse files
test(agents): add naming clash test and fix for custom-filename collision
When an existing agent file uses a custom filename (e.g. custom_name.jsonc for an agent named "support_agent"), and a new remote agent arrives whose default filename matches that custom file, writeAgents now throws an error instead of silently overwriting the first agent's file. - Detects clash by tracking claimed file paths before writing new agents - Adds with-agents-naming-clash fixture (custom_name.jsonc with name "support_agent") - Adds CLI test asserting the command fails and mentions the clashing name Co-authored-by: paveltarno <Paveltarno@users.noreply.github.com>
1 parent 6b0fa71 commit 7d1f297

File tree

5 files changed

+55
-3
lines changed

5 files changed

+55
-3
lines changed

src/core/resources/agent/config.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ export async function writeAgents(
9494
}
9595
}
9696

97+
const claimedPaths = new Set(
98+
[...nameToEntry.values()].map((e) => e.filePath),
99+
);
100+
97101
const written: string[] = [];
98102
for (const agent of remoteAgents) {
99103
const existing = nameToEntry.get(agent.name);
@@ -102,9 +106,18 @@ export async function writeAgents(
102106
continue;
103107
}
104108

105-
const filePath =
106-
existing?.filePath ??
107-
join(agentsDir, `${agent.name}.${CONFIG_FILE_EXTENSION}`);
109+
const defaultPath = join(
110+
agentsDir,
111+
`${agent.name}.${CONFIG_FILE_EXTENSION}`,
112+
);
113+
114+
if (!existing && claimedPaths.has(defaultPath)) {
115+
throw new Error(
116+
`Cannot write agent "${agent.name}": file "${defaultPath}" is already used by another agent`,
117+
);
118+
}
119+
120+
const filePath = existing?.filePath ?? defaultPath;
108121
await writeJsonFile(filePath, agent);
109122
written.push(agent.name);
110123
}

tests/cli/agents_pull.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,33 @@ describe("agents pull command", () => {
106106
expect(fileContent).toContain("// My support agent");
107107
});
108108

109+
it("fails when a new remote agent's default filename clashes with an existing custom-named file", async () => {
110+
// Local: custom_name.jsonc has name "support_agent" (custom filename)
111+
// Remote returns support_agent (maps to custom_name.jsonc) AND custom_name
112+
// (new agent whose default path custom_name.jsonc is already taken)
113+
await t.givenLoggedInWithProject(fixture("with-agents-naming-clash"));
114+
t.api.mockAgentsFetch({
115+
items: [
116+
{
117+
name: "support_agent",
118+
description: "Helps users",
119+
instructions: "Be helpful",
120+
},
121+
{
122+
name: "custom_name",
123+
description: "A new agent",
124+
instructions: "Do new things",
125+
},
126+
],
127+
total: 2,
128+
});
129+
130+
const result = await t.run("agents", "pull");
131+
132+
t.expectResult(result).toFail();
133+
t.expectResult(result).toContain("custom_name");
134+
});
135+
109136
it("updates agent file in-place when remote data changes", async () => {
110137
await t.givenLoggedInWithProject(fixture("with-agents-for-pull"));
111138
t.api.mockAgentsFetch({
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"id": "test-app-id"
3+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Agent stored with a custom filename (not matching its name slug)
2+
{
3+
"name": "support_agent",
4+
"description": "Helps users",
5+
"instructions": "Be helpful"
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "Agents Naming Clash Test Project"
3+
}

0 commit comments

Comments
 (0)