You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is this a bug? Yes — sibling of #1138 / #1550 / #1678 / #1729, but distinct from all of them.
Summary
When instructions have already been deployed to .github/instructions/ (the normal state after apm install for a Copilot target), apm compile --target copilot correctly skips folding those instructions into AGENTS.md to avoid duplicate context for Copilot — but it still writes content-free AGENTS.md shells (header + footer, no instruction bodies). This happens for:
the root AGENTS.md, and
every distributed per-directory AGENTS.md the placement engine would emit (e.g. docs/AGENTS.md, src/AGENTS.md) when instructions are scoped via applyTo.
The Claude path partially handles this: when .claude/rules/ is already populated, apm compile --target claude avoids creating a new CLAUDE.md (added in #1138 via PR #1146, agents_compiler.py:785-791). The Copilot dedup-parity work (#1550) ported the skip logic but not even that empty-file suppression, so Copilot litters the tree with meaningless shells.
This is the AGENTS.md analog of #1138 (empty CLAUDE.md), and unlike the single-file Claude case it can leave several empty files scattered across subdirectories.
Environment
APM version: 0.19.0
OS: macOS
Steps to reproduce
Case A — root AGENTS.md (global instructions)
mkdir -p /tmp/apm-empty/.apm/instructions &&cd /tmp/apm-empty
cat > apm.yml <<'EOF'name: test-empty-agentsversion: 1.0.0targets: [copilot]EOF
cat > .apm/instructions/global.instructions.md <<'EOF'---description: A global instruction---This is global guidance that should reach Copilot.EOF# Simulate the post-`apm install` state:
mkdir -p .github/instructions
cp .apm/instructions/*.instructions.md .github/instructions/
apm compile --target copilot
cat AGENTS.md # => header + footer only, 233 bytes, no instructions
Case B — distributed subdir AGENTS.md (scoped instructions) — the worse case
When instruction dedup fires (.github/instructions/ already holds the instructions), no content-free AGENTS.md should be written — not the root file and not any distributed subdir file. Skip any placement whose only content would have been the now-elided instructions, and emit the same INFO-style log the Claude path does ("AGENTS.md not generated — Copilot reads .github/instructions/ directly").
On apm compile --clean, any existing APM-generatedAGENTS.md that would now end up empty should be removed (it's stale from a previous run). This must be gated on the <!-- Generated by APM CLI ... --> marker so hand-authored AGENTS.md files are never deleted. Plain apm compile should remain non-destructive: it stops writing new empty shells but does not delete pre-existing files.
Actual behavior
Case A writes a 233-byte root AGENTS.md (header + footer, no bodies).
Case B writes a 233-byte docs/AGENTS.mdandsrc/AGENTS.md, both empty shells.
apm compile --clean does not remove them — the distributed placement path regenerates them each run, so they're never classified as orphans.
(In all cases the instructions do correctly reach .github/instructions/ and .github/copilot-instructions.md — the empty AGENTS.md files are pure redundant noise.)
Multi-target is unaffected (verified)
With targets: [copilot, codex] and .github/instructions/ populated, dedup correctly does not fire and AGENTS.md retains full instruction content. can_dedup_agents_md_instructions (core/target_detection.py:281) returns True only for the exact set {vscode}; any wider target set keeps skip_instructions = False. The proposed suppression triggers only when skip_instructions is already True, so it can never strip content from Codex/OpenCode/Windsurf/Gemini or any multi-target build. The fix sits strictly on top of the #1678 target-aware gate.
Root cause
In src/apm_cli/compilation/:
_compile_claude_md (agents_compiler.py:785-791) suppresses creating an empty file: when files_written == 0 and skip_instructions, it writes nothing and logs the "reads .claude/rules/ directly" message.
_compile_distributed (agents_compiler.py:417-575) has no equivalent guard. It threads skip_instructions=True into DistributedAgentsCompiler.compile_distributed, whose _generate_agents_content (distributed_compiler.py:547-611) appends header + footer unconditionally and only gates the instruction body on skip_instructions (line 587). Every placement therefore yields a non-empty string and is written via _write_distributed_file, including subdir placements.
_find_orphaned_agents_files (distributed_compiler.py:643) only flags files not in the current run's generated set. Since the empty shells are regenerated each run, they're never orphans, so --clean leaves them.
Suggested fix
Mirror and extend the CLAUDE.md behavior on the AGENTS.md path, per-placement:
In the distributed compiler, when a placement's content would be a header/footer-only shell (i.e. skip_instructions elided its only instructions and it carries no other content such as constitution/agents roll-up), exclude it from content_map so it's never written. Apply to root and all subdir placements uniformly. Emit the same INFO log the Claude path uses, adapted to AGENTS.md.
Fold the would-be-empty, already-existing, APM-generated placement paths into --clean orphan cleanup so it removes stale shells from prior runs — gated on the <!-- Generated by APM CLI ... --> marker (reuse the hand-authored-protection convention already at agents_compiler.py:1131) so user-authored AGENTS.md files are never touched. Plain apm compile stays non-destructive (suppresses new writes only).
Cases that must remain unchanged:
AGENTS.md placements carrying non-instruction content (constitution, agents/prompts roll-up) still written.
Codex / OpenCode / Windsurf / Gemini / any multi-target set (already gated; skip_instructions is False).
--no-dedup / --force-instructions still produce full AGENTS.md.
Hand-authored AGENTS.md (no APM marker) never deleted by --clean.
Acceptance criteria
Failing test (Case A): target = copilot, .github/instructions/*.md present → no content-free root AGENTS.md written.
Failing test (Case B): same conditions with scoped instructions across subdirs → no empty docs/AGENTS.md / src/AGENTS.md written.
Same INFO-style "not generated — Copilot reads .github/instructions/ directly" message as the Claude path.
apm compile --clean removes pre-existing APM-generated AGENTS.md files that would now be empty; hand-authored AGENTS.md (no marker) untouched; plain apm compile deletes nothing.
[BUG] apm compile --clean never removes a stale CLAUDE.md after instructions move to .claude/rules/ #1729 — companion bug on the Claude side: the CLAUDE.md path has no cleanup logic at all, so a stale CLAUDE.md is never removed (even with --clean) once .claude/rules/ is populated. The two issues are siblings: the AGENTS.md path at least has orphan-removal infrastructure (_find_orphaned_agents_files / _cleanup_orphaned_files) but it does not currently catch this regenerated-but-redundant case; the Claude path has none. Both should add would-be-redundant-file removal on --clean, and should be fixed consistently.
Is this a bug? Yes — sibling of #1138 / #1550 / #1678 / #1729, but distinct from all of them.
Summary
When instructions have already been deployed to
.github/instructions/(the normal state afterapm installfor a Copilot target),apm compile --target copilotcorrectly skips folding those instructions intoAGENTS.mdto avoid duplicate context for Copilot — but it still writes content-freeAGENTS.mdshells (header + footer, no instruction bodies). This happens for:AGENTS.md, andAGENTS.mdthe placement engine would emit (e.g.docs/AGENTS.md,src/AGENTS.md) when instructions are scoped viaapplyTo.The Claude path partially handles this: when
.claude/rules/is already populated,apm compile --target claudeavoids creating a newCLAUDE.md(added in #1138 via PR #1146,agents_compiler.py:785-791). The Copilot dedup-parity work (#1550) ported the skip logic but not even that empty-file suppression, so Copilot litters the tree with meaningless shells.This is the AGENTS.md analog of #1138 (empty
CLAUDE.md), and unlike the single-file Claude case it can leave several empty files scattered across subdirectories.Environment
Steps to reproduce
Case A — root AGENTS.md (global instructions)
Case B — distributed subdir AGENTS.md (scoped instructions) — the worse case
Expected behavior
.github/instructions/already holds the instructions), no content-freeAGENTS.mdshould be written — not the root file and not any distributed subdir file. Skip any placement whose only content would have been the now-elided instructions, and emit the same INFO-style log the Claude path does ("AGENTS.md not generated — Copilot reads.github/instructions/directly").apm compile --clean, any existing APM-generatedAGENTS.mdthat would now end up empty should be removed (it's stale from a previous run). This must be gated on the<!-- Generated by APM CLI ... -->marker so hand-authoredAGENTS.mdfiles are never deleted. Plainapm compileshould remain non-destructive: it stops writing new empty shells but does not delete pre-existing files.Actual behavior
AGENTS.md(header + footer, no bodies).docs/AGENTS.mdandsrc/AGENTS.md, both empty shells.apm compile --cleandoes not remove them — the distributed placement path regenerates them each run, so they're never classified as orphans.(In all cases the instructions do correctly reach
.github/instructions/and.github/copilot-instructions.md— the emptyAGENTS.mdfiles are pure redundant noise.)Multi-target is unaffected (verified)
With
targets: [copilot, codex]and.github/instructions/populated, dedup correctly does not fire andAGENTS.mdretains full instruction content.can_dedup_agents_md_instructions(core/target_detection.py:281) returns True only for the exact set{vscode}; any wider target set keepsskip_instructions = False. The proposed suppression triggers only whenskip_instructionsis already True, so it can never strip content from Codex/OpenCode/Windsurf/Gemini or any multi-target build. The fix sits strictly on top of the #1678 target-aware gate.Root cause
In
src/apm_cli/compilation/:_compile_claude_md(agents_compiler.py:785-791) suppresses creating an empty file: whenfiles_written == 0 and skip_instructions, it writes nothing and logs the "reads.claude/rules/directly" message._compile_distributed(agents_compiler.py:417-575) has no equivalent guard. It threadsskip_instructions=TrueintoDistributedAgentsCompiler.compile_distributed, whose_generate_agents_content(distributed_compiler.py:547-611) appends header + footer unconditionally and only gates the instruction body onskip_instructions(line 587). Every placement therefore yields a non-empty string and is written via_write_distributed_file, including subdir placements._find_orphaned_agents_files(distributed_compiler.py:643) only flags files not in the current run's generated set. Since the empty shells are regenerated each run, they're never orphans, so--cleanleaves them.Suggested fix
Mirror and extend the CLAUDE.md behavior on the AGENTS.md path, per-placement:
skip_instructionselided its only instructions and it carries no other content such as constitution/agents roll-up), exclude it fromcontent_mapso it's never written. Apply to root and all subdir placements uniformly. Emit the same INFO log the Claude path uses, adapted to AGENTS.md.--cleanorphan cleanup so it removes stale shells from prior runs — gated on the<!-- Generated by APM CLI ... -->marker (reuse the hand-authored-protection convention already atagents_compiler.py:1131) so user-authoredAGENTS.mdfiles are never touched. Plainapm compilestays non-destructive (suppresses new writes only).Cases that must remain unchanged:
skip_instructionsisFalse).--no-dedup/--force-instructionsstill produce full AGENTS.md.AGENTS.md(no APM marker) never deleted by--clean.Acceptance criteria
target = copilot,.github/instructions/*.mdpresent → no content-free rootAGENTS.mdwritten.docs/AGENTS.md/src/AGENTS.mdwritten..github/instructions/directly" message as the Claude path.apm compile --cleanremoves pre-existing APM-generated AGENTS.md files that would now be empty; hand-authored AGENTS.md (no marker) untouched; plainapm compiledeletes nothing.--no-dedup.CHANGELOG.md### Fixedentry referencing this issue, [FEATURE] Skip instructions in CLAUDE.md when already deployed to .claude/rules/ #1138, and perf/dedup: parity for Copilot AGENTS.md vs .github/instructions/ (sibling of #1445) #1550.Related
skip_instructionsbut not the empty-file suppression.CLAUDE.mdis never removed (even with--clean) once.claude/rules/is populated. The two issues are siblings: the AGENTS.md path at least has orphan-removal infrastructure (_find_orphaned_agents_files/_cleanup_orphaned_files) but it does not currently catch this regenerated-but-redundant case; the Claude path has none. Both should add would-be-redundant-file removal on--clean, and should be fixed consistently.