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
This commit is contained in:
David Brailovsky 2026-10-09 09:40:08 +02:00
parent 6fb2e78151
commit f1b2e77f5b
3 changed files with 29 additions and 1 deletions

View file

@ -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

View file

@ -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);

View file

@ -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");