From 08bdc3d28e163c86bf3f6aa5cf25041eb3e927f9 Mon Sep 17 00:00:00 2001 From: dotta Date: Mon, 23 Mar 2026 20:56:34 -0500 Subject: [PATCH] Handle nested imported AGENTS edge case Co-Authored-By: Paperclip --- .../src/__tests__/company-portability.test.ts | 70 +++++++++++++++++++ server/src/services/company-portability.ts | 2 +- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index d8d14a77..a3410df6 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -2112,4 +2112,74 @@ describe("company portability", () => { expect(materializedFiles["AGENTS.md"]).not.toMatch(/^---\n/); expect(materializedFiles["AGENTS.md"]).not.toContain('name: "ClaudeCoder"'); }); + + it("strips root AGENTS frontmatter when importing a nested agent entry path", async () => { + const portability = companyPortabilityService({} as any); + + companySvc.create.mockResolvedValue({ + id: "company-imported", + name: "Imported Paperclip", + }); + accessSvc.ensureMembership.mockResolvedValue(undefined); + agentSvc.create.mockResolvedValue({ + id: "agent-created", + name: "ClaudeCoder", + }); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + }); + const originalAgentsMarkdown = exported.files["agents/claudecoder/AGENTS.md"]; + expect(typeof originalAgentsMarkdown).toBe("string"); + + const files = { + ...exported.files, + "agents/claudecoder/nested/AGENTS.md": originalAgentsMarkdown!, + }; + + agentSvc.list.mockResolvedValue([]); + + await portability.importBundle({ + source: { + type: "inline", + rootPath: exported.rootPath, + files, + }, + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + target: { + mode: "new_company", + newCompanyName: "Imported Paperclip", + }, + agents: ["claudecoder"], + collisionStrategy: "rename", + adapterOverrides: { + claudecoder: { + adapterType: "codex_local", + adapterConfig: { + dangerouslyBypassApprovalsAndSandbox: true, + }, + }, + }, + }, "user-1"); + + const nestedMaterializedFiles = agentInstructionsSvc.materializeManagedBundle.mock.calls + .map(([, filesArg]) => filesArg as Record) + .find((filesArg) => typeof filesArg["nested/AGENTS.md"] === "string"); + + expect(nestedMaterializedFiles).toBeDefined(); + expect(nestedMaterializedFiles?.["nested/AGENTS.md"]).toContain("You are ClaudeCoder."); + expect(nestedMaterializedFiles?.["AGENTS.md"]).toContain("You are ClaudeCoder."); + expect(nestedMaterializedFiles?.["AGENTS.md"]).not.toMatch(/^---\n/); + expect(nestedMaterializedFiles?.["AGENTS.md"]).not.toContain('name: "ClaudeCoder"'); + }); }); diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index a8c408e1..7cfe8ffa 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -3870,7 +3870,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { if (typeof markdownRaw === "string") { const importedInstructionsBody = parseFrontmatterMarkdown(markdownRaw).body; bundleFiles[entryRelativePath] = importedInstructionsBody; - if (entryRelativePath !== "AGENTS.md" && !bundleFiles["AGENTS.md"]) { + if (entryRelativePath !== "AGENTS.md") { bundleFiles["AGENTS.md"] = importedInstructionsBody; } }