A per-dependency targets: list disables per-file hook target routing
Problem
Setting a per-dependency targets: list in a consumer apm.yml disables
per-file hook routing for that dependency. A package that ships divergent
per-target hook files (a Claude file and a Codex file that are not
interchangeable) then has every file merged into every active target,
cross-contaminating each tool's config and duplicating shared entries.
targets: was introduced to narrow which targets a dependency installs to; it
should refine routing, not switch it off.
Reproduction
A package ships pkg-claude-hooks.json (a PreToolUse/Skill matcher) and
pkg-codex-hooks.json (a PostToolUse/apply_patch matcher), each also
carrying a shared Bash hook. Consumer:
dependencies:
- git: owner/pkg
targets: [claude, codex]
Matchers written to each tool's merged config:
Consumer targets: |
.claude/settings.json |
.codex/hooks.json |
Correct? |
| (omitted) |
PreToolUse:[Skill], Bash:[*] |
PostToolUse:[apply_patch], Bash:[*] |
yes |
[claude, codex] |
PreToolUse:[Skill], Bash:[*], PostToolUse:[apply_patch] |
PreToolUse:[Skill], Bash:[*], PostToolUse:[apply_patch] |
no |
[codex] |
(not installed) |
PreToolUse:[Skill], Bash:[*], PostToolUse:[apply_patch] |
no |
Even a single-target [codex] contaminates: Codex absorbs the Claude file's
Skill hook. Expected: each file lands only in its intended tool; targets:
only restricts which tools are active.
Reproduced on main (microsoft/apm@49bfdc67) and released 0.24.x/0.25.x.
Root cause
Any per-dependency targets: list sets dep_targets_active = True in
filter_targets_for_dependency (src/apm_cli/install/target_filter.py). That
boolean gates per-file routing at three sites:
src/apm_cli/integration/hook_integrator.py:1154 (Copilot)
src/apm_cli/integration/hook_integrator.py:1370 (merged targets: Claude,
Cursor, Codex, Gemini, Windsurf, Antigravity)
src/apm_cli/integration/kiro_hook_integrator.py:239 (Kiro)
if not dep_targets_active:
hook_files = _filter_hook_files_for_target(hook_files, target_key, ...)
When the boolean is true the filter is skipped, so every hook file is handed to
the merge step for every active target, and the merge step does not re-route by
target.
Fix
Proposed in #2259. Remove the dep_targets_active gate at all three sites so
per-file routing always runs. targets: still narrows which targets are active upstream; routing
then places each file only in its intended tool. Universal files and combined
claude-codex manifests are unaffected, and the dep_targets_active chokepoint
assertion in integrate_hooks_for_target still consumes the parameter.
Harness-agnostic (every merge-based target: copilot/vscode, cursor, claude,
codex, gemini, antigravity, windsurf, kiro). A separate issue tracks the design
gap that filename routing was deprecated without a replacement for divergent
per-target files.
A per-dependency
targets:list disables per-file hook target routingProblem
Setting a per-dependency
targets:list in a consumerapm.ymldisablesper-file hook routing for that dependency. A package that ships divergent
per-target hook files (a Claude file and a Codex file that are not
interchangeable) then has every file merged into every active target,
cross-contaminating each tool's config and duplicating shared entries.
targets:was introduced to narrow which targets a dependency installs to; itshould refine routing, not switch it off.
Reproduction
A package ships
pkg-claude-hooks.json(aPreToolUse/Skillmatcher) andpkg-codex-hooks.json(aPostToolUse/apply_patchmatcher), each alsocarrying a shared
Bashhook. Consumer:Matchers written to each tool's merged config:
targets:.claude/settings.json.codex/hooks.jsonPreToolUse:[Skill],Bash:[*]PostToolUse:[apply_patch],Bash:[*][claude, codex]PreToolUse:[Skill],Bash:[*],PostToolUse:[apply_patch]PreToolUse:[Skill],Bash:[*],PostToolUse:[apply_patch][codex]PreToolUse:[Skill],Bash:[*],PostToolUse:[apply_patch]Even a single-target
[codex]contaminates: Codex absorbs the Claude file'sSkillhook. Expected: each file lands only in its intended tool;targets:only restricts which tools are active.
Reproduced on
main(microsoft/apm@49bfdc67) and released 0.24.x/0.25.x.Root cause
Any per-dependency
targets:list setsdep_targets_active = Trueinfilter_targets_for_dependency(src/apm_cli/install/target_filter.py). Thatboolean gates per-file routing at three sites:
src/apm_cli/integration/hook_integrator.py:1154(Copilot)src/apm_cli/integration/hook_integrator.py:1370(merged targets: Claude,Cursor, Codex, Gemini, Windsurf, Antigravity)
src/apm_cli/integration/kiro_hook_integrator.py:239(Kiro)When the boolean is true the filter is skipped, so every hook file is handed to
the merge step for every active target, and the merge step does not re-route by
target.
Fix
Proposed in #2259. Remove the
dep_targets_activegate at all three sites soper-file routing always runs.
targets:still narrows which targets are active upstream; routingthen places each file only in its intended tool. Universal files and combined
claude-codexmanifests are unaffected, and thedep_targets_activechokepointassertion in
integrate_hooks_for_targetstill consumes the parameter.Harness-agnostic (every merge-based target: copilot/vscode, cursor, claude,
codex, gemini, antigravity, windsurf, kiro). A separate issue tracks the design
gap that filename routing was deprecated without a replacement for divergent
per-target files.