Skip to content

compile --clean deletes tracked AGENTS.md inside nested git worktrees #2436

Description

@Ives7

Summary

apm compile --clean treats AGENTS.md files belonging to other git worktrees as orphans and deletes them, when those worktrees live in a directory under the repository root (a common layout, e.g. <repo>/.worktrees/<branch>/).

In our repo the deleted files are tracked in git, so every affected worktree silently gains uncommitted deletions:

+ Removed .worktrees/fix-tsconfig-jsonc/e2e/AGENTS.md
+ Removed .worktrees/fix-tsconfig-jsonc/packages/cli/AGENTS.md
+ Removed .worktrees/fix-tsconfig-jsonc/e2e/fixtures/application/AGENTS.md
$ cd .worktrees/fix-tsconfig-jsonc && git status --short
 D e2e/AGENTS.md
 D e2e/fixtures/application/AGENTS.md
 D packages/cli/AGENTS.md

This is not a one-off: with several worktrees checked out in parallel, any apm compile --clean from the main working tree wipes all of them, and an agent working inside one can commit the deletions without noticing.

Cause

src/apm_cli/compilation/distributed_compiler.py:817 (identical in v0.26.0 and v0.27.0):

for agents_file in self.base_dir.rglob("AGENTS.md"):
    relative_path = agents_file.resolve().relative_to(self.base_dir.resolve())
    skip_dirs = {".git", ".apm", "node_modules", "__pycache__", ".pytest_cache", "apm_modules"}
    if any(part in skip_dirs for part in relative_path.parts):
        continue

A nested worktree is an independent checkout with its own generated AGENTS.md files carrying the APM marker, so they pass the marker gate and are removed.

There is currently no way to opt out:

  • skip_dirs is a hardcoded literal.
  • .gitignore is not consulted (our .worktrees entry has no effect).
  • apm.yml has no corresponding key, apm compile has no corresponding flag, and there is no env var.
  • DistributedAgentsCompiler.__init__ does take exclude_patterns, but the CLI never passes it, and it feeds the project-structure analyzer (same file, :123) rather than the orphan scan.

Suggested fix

A git worktree root is easy to detect: it contains a .git file (a gitdir pointer), not a .git directory. Skipping any directory whose root has that would fix this without new configuration:

def _is_nested_worktree(path: Path) -> bool:
    return (path / ".git").is_file()

…and prune such directories during the walk. Alternatively (or additionally), honour an excludes: key in apm.yml for the orphan scan.

Environment

  • apm 0.26.0 (also verified the code is unchanged in v0.27.0), macOS arm64, PyInstaller build from the GitHub release
  • Layout: main working tree at <repo>, additional git worktree add targets under <repo>/.worktrees/<slug>

Workaround

Move worktrees outside the repository root so base_dir.rglob(...) cannot reach them, and keep --clean out of any automatically-run script (we had it in package.json prepare, which meant every bun install triggered it).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cliCLI command surface, flags, help text (cross-cutting).priority/highShips in current or next milestonestatus/acceptedDirection approved, safe to start work.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).theme/governanceGoverned by policy. apm-policy, audit, enforcement, enterprise rollout.type/bugSomething does not work as documented.

    Type

    No type

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions