From f1b2e77f5b13b2a199b1052cb79c235451afe7d7 Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Fri, 9 Oct 2026 09:40:08 +0200 Subject: [PATCH] fix(coding-agent): load symlinked AGENTS.md in nested git worktrees The nested worktree dedup compared the context file's realpath with the main repo's file, so a worktree AGENTS.md symlinked to the main repo's copy was skipped along with it. Compare the walked directory's realpath plus the file name instead. fixes #10681 --- packages/coding-agent/CHANGELOG.md | 1 + .../coding-agent/src/core/resource-loader.ts | 6 ++++- .../coding-agent/test/resource-loader.test.ts | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 6bc19cb08..3ace5dd79 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixed - Fixed Mistral streams being aborted mid-generation (e.g. during long thinking) after the request timeout; the timeout now applies only to waiting for response headers ([#10609](https://github.com/earendil-works/pi/issues/10609)) +- Fixed a symlinked `AGENTS.md` in a git worktree nested inside its main repo causing no project context file to load ([#10681](https://github.com/earendil-works/pi/issues/10681)) ## [1.1.0] - 2026-10-07 diff --git a/packages/coding-agent/src/core/resource-loader.ts b/packages/coding-agent/src/core/resource-loader.ts index b45b21299..3d59416af 100644 --- a/packages/coding-agent/src/core/resource-loader.ts +++ b/packages/coding-agent/src/core/resource-loader.ts @@ -252,8 +252,12 @@ export function loadProjectContextFiles(options: { while (true) { const contextFile = loadContextFileFromDir(currentDir); + // Compare the directory's real path, not the file's: a worktree AGENTS.md that + // symlinks to the main repo's file resolves to the shadowed path itself. const isShadowed = - shadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? "") === shadowedContextFile; + shadowedContextFile !== undefined && + contextFile !== null && + join(canonicalizePath(currentDir), basename(contextFile.path)) === shadowedContextFile; if (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) { ancestorContextFiles.unshift(contextFile); seenPaths.add(contextFile.path); diff --git a/packages/coding-agent/test/resource-loader.test.ts b/packages/coding-agent/test/resource-loader.test.ts index 3c2a7be87..bba3fc95a 100644 --- a/packages/coding-agent/test/resource-loader.test.ts +++ b/packages/coding-agent/test/resource-loader.test.ts @@ -1243,6 +1243,29 @@ export default function(pi: ExtensionAPI) { expect(files.map((f) => f.content)).toEqual(["worktree instructions"]); }); + // https://github.com/earendil-works/pi/issues/10681 + it("should load the worktree's context once when it symlinks to the main repo's file", () => { + const { main, worktree, worktreeSrc } = setupNestedWorktree(); + writeFileSync(join(main, "AGENTS.md"), "main repo instructions"); + symlinkSync(join(main, "AGENTS.md"), join(worktree, "AGENTS.md")); + + const files = loadProjectContextFiles({ cwd: worktreeSrc, agentDir }); + + expect(files.map((f) => f.path)).toEqual([join(worktree, "AGENTS.md")]); + }); + + it("should skip the main repo's duplicate when the main repo's file is a symlink", () => { + const { outer, main, worktree, worktreeSrc } = setupNestedWorktree(); + const shared = join(outer, "shared-agents.md"); + writeFileSync(shared, "shared instructions"); + symlinkSync(shared, join(main, "AGENTS.md")); + symlinkSync(shared, join(worktree, "AGENTS.md")); + + const files = loadProjectContextFiles({ cwd: worktreeSrc, agentDir }); + + expect(files.map((f) => f.path)).toEqual([join(worktree, "AGENTS.md")]); + }); + it("should still inherit the main repo's context when the worktree root has none", () => { const { main, worktreeSrc } = setupNestedWorktree(); writeFileSync(join(main, "AGENTS.md"), "main repo instructions");