Skip to content

[BUG] A per-dependency targets: list disables per-file hook target routing #2258

Description

@srobroek

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions