Skip to content

fix(hooks): stabilize root-local hook source ids#1330

Open
imk1t wants to merge 2 commits into
microsoft:mainfrom
imk1t:fix/root-hook-source-drift-1329
Open

fix(hooks): stabilize root-local hook source ids#1330
imk1t wants to merge 2 commits into
microsoft:mainfrom
imk1t:fix/root-hook-source-drift-1329

Conversation

@imk1t
Copy link
Copy Markdown
Contributor

@imk1t imk1t commented May 14, 2026

Description

TL;DR

Root-local .apm/hooks/*.json installs now use a checkout-stable source marker derived from apm.yml instead of the worktree basename. Merged hook targets heal stale same-content root hook entries whose old _apm_source came from a previous checkout name, so re-running install does not append duplicates. User-authored hooks and dependency package hooks remain untouched unless they are the current source being replaced.

Note

Closes #1329. The array-dedup healer is intentionally scoped to merged hook targets; Copilot's individual-file hook model is not part of this duplicate-array failure mode.

Problem (WHY)

  • [BUG] Root .apm hooks can duplicate when _apm_source changes with checkout directory #1329 reports that root hooks "can be duplicated" when _apm_source changes between installs.
  • Generated merged configs could retain an old root source entry and append the fresh entry, even though the hook content was identical.
  • The expected state is "exactly one semantic copy" of the root hook after install/sync.
  • [!] Root hook ownership followed the checkout directory name, not stable project metadata, so moving the same root .apm content between worktrees could create install drift.

Why these matter: install idempotency is a DevX contract, and hook emission is a multi-harness surface. The issue explicitly asks for root .apm _apm_source to be "stable across checkout directories" and lists Claude, Codex, Gemini, Cursor, and Windsurf as affected merged-hook targets.

Approach (WHAT)

# Fix Principle Source
1 Split root-local hook source markers from raw install_path.name; use safe apm.yml name with _local fallback. DevX, Portability by manifest #1329
2 Store root-local merged hook ownership as _local/<name> so root content cannot collide with dependency source names. Multi-harness support, OSS / community-driven Review-agent finding during this fix
3 Heal stale root source entries by comparing hook content without _apm_source, while preserving user hooks and dependency sources. DevX, Multi-harness support #1329
4 Keep dependency packages on the existing install_path.name marker so package aliases and cross-package installs do not collapse. OSS / community-driven Existing hook-integrator behavior

Implementation (HOW)

  • src/apm_cli/integration/hook_integrator.py -- adds root-local package detection, safe manifest-name normalization, _local/<name> merged-source markers, and content-key based stale-source healing. Current-source cleanup still runs once per event, while stale-source healing runs per root hook file so multiple files targeting the same event are handled.
  • tests/unit/integration/test_hook_integrator.py -- adds regression coverage for Claude and Codex stale-source healing, stable root source naming, user-hook preservation, dependency-hook preservation, dependency-name collision avoidance, and multiple root hook files sharing one event.
  • tests/integration/test_registry.py -- keeps Ruff green by replacing a narrow cleanup try/except/pass block with contextlib.suppress; no registry behavior change.
  • CHANGELOG.md -- documents the root hook source-drift fix under Unreleased / Fixed.

Diagrams

Legend: root hook content now gets a stable local marker before merged-target cleanup, while dependency and user-owned entries stay outside the stale-root healer.

flowchart LR
    subgraph Source[Root hooks]
        H1[".apm/hooks/*.json"]
        H2["apm.yml name"]
    end
    subgraph Integrator[HookIntegrator]
        I1["stable root package name"]:::new
        I2["merged source marker _local/name"]:::new
        I3["same-content stale root healer"]:::new
    end
    subgraph Outputs[Merged hook targets]
        O1[".claude/settings.json"]
        O2[".codex/hooks.json"]
        O3["Cursor Gemini Windsurf"]
    end
    D1["dependency package sources"]
    U1["user hooks without _apm_source"]

    H2 --> I1
    H1 --> I2
    I1 --> I2
    I2 --> I3
    I3 --> O1
    I3 --> O2
    I3 --> O3
    D1 --> O1
    U1 --> O1
    classDef new stroke-dasharray: 5 5;
    class I1,I2,I3 new;
Loading

Trade-offs

  • Namespaced root markers. Chose _local/<safe manifest name> for merged hook ownership; rejected using the raw manifest name because a dependency package can legitimately have the same source id.
  • Root-only stale healing. Chose same-content healing only for root-local installs; rejected broad semantic dedup across all packages because distinct dependency packages may intentionally install identical hooks.
  • Merged targets only. Chose not to run the array healer for Copilot because Copilot writes individual hook files rather than merged hook arrays; stable root-local naming still prevents future basename churn there.
  • Best-effort manifest parsing. Chose fallback to package metadata and _local when apm.yml cannot provide a safe name; rejected failing install because this path is cleanup/idempotency support, not manifest validation.

Benefits

  1. Reinstalling root .apm hooks over an old checkout-derived _apm_source leaves one managed semantic hook, not two.
  2. Root-local merged hook ownership is stable as _local/<apm.yml name> across checkout directory names.
  3. User hooks without _apm_source remain in the generated merged config.
  4. Identical dependency hooks remain distinct from root hooks, including when the dependency source name matches the root manifest name.
  5. Multiple root hook files targeting the same event are all healed and preserved.

Closes #1329

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

uv run pytest tests/unit/integration/test_hook_integrator.py -x:

============================= 130 passed in 0.50s ==============================

uv run pytest tests/unit tests/test_console.py -x:

8461 passed, 1 warning

uv run --extra dev ruff check src/ tests/:

All checks passed!

uv run --extra dev ruff format --check src/ tests/:

763 files already formatted

git diff --check:


npx --yes -p @mermaid-js/mermaid-cli mmdc -i /private/tmp/apm-pr-diag-1329.mmd -o /private/tmp/apm-pr-diag-1329.svg --quiet:


Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 Re-run install/sync after an old root checkout source id exists; merged hooks contain one semantic root hook. DevX, Multi-harness support tests/unit/integration/test_hook_integrator.py::test_root_local_heals_stale_source_in_claude_settings (regression-trap for #1329)
tests/unit/integration/test_hook_integrator.py::test_root_local_heals_stale_source_in_codex_hooks (regression-trap for #1329)
unit
2 Root .apm hook ownership stays stable when the checkout directory basename changes. DevX, Portability by manifest tests/unit/integration/test_hook_integrator.py::test_root_local_source_uses_manifest_name unit
3 User-authored hooks without _apm_source survive root hook healing. OSS / community-driven, DevX tests/unit/integration/test_hook_integrator.py::test_root_local_heals_stale_source_in_claude_settings unit
4 Dependency package hooks that are semantically identical to root hooks remain installed as separate package-owned hooks. OSS / community-driven, Multi-harness support tests/unit/integration/test_hook_integrator.py::test_root_local_healer_preserves_dependency_source_entries
tests/unit/integration/test_hook_integrator.py::test_content_dedup_preserves_cross_package
unit
5 A dependency source named like the root manifest does not get removed by root-local cleanup. OSS / community-driven, DevX tests/unit/integration/test_hook_integrator.py::test_root_local_source_marker_does_not_collide_with_dependency_name unit
6 Multiple root hook files targeting one event are all preserved while stale entries are healed. DevX, Multi-harness support tests/unit/integration/test_hook_integrator.py::test_root_local_heals_stale_source_for_multiple_hook_files_same_event unit

How to test

  • Seed .claude/settings.json with a root hook tagged by an old _apm_source, then run root-local hook integration; expect one managed _local/<name> entry plus any user-owned entries.
  • Repeat the stale-source setup for .codex/hooks.json; expect exactly one managed root hook entry.
  • Install a dependency package with the same semantic hook as the root project; expect both dependency and _local/<name> sources to remain.
  • Run uv run pytest tests/unit/integration/test_hook_integrator.py -x; expect all hook-integrator tests to pass.
  • Run the Ruff check and format commands above; expect no lint or format failures.

@imk1t imk1t marked this pull request as ready for review May 14, 2026 21:22
@imk1t imk1t requested a review from danielmeppiel as a code owner May 14, 2026 21:22
Copilot AI review requested due to automatic review settings May 14, 2026 21:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Stabilizes root-local hook source IDs so re-running hook installation/sync remains idempotent across different checkout directory names, and adds targeted healing to remove stale same-content root entries in merged hook targets.

Changes:

  • Derive root-local hook “source” from apm.yml name (sanitized) and namespace merged-target ownership as _local/<name>.
  • Heal stale merged-hook entries for root-local installs by removing prior same-content entries with outdated _apm_source values while preserving dependency- and user-owned entries.
  • Add regression tests for Claude/Codex merged-hook stale-source healing and adjust an integration test cleanup block.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/apm_cli/integration/hook_integrator.py Implements stable root-local source markers and stale-entry healing in merged hook targets
tests/unit/integration/test_hook_integrator.py Adds unit coverage for root-local stable naming + stale-source healing across merged targets
tests/integration/test_registry.py Replaces try/except cleanup with contextlib.suppress (no behavior change intended)
CHANGELOG.md Documents the root hook source drift + stale-entry healing fix

Comment thread tests/unit/integration/test_hook_integrator.py Outdated
Comment on lines +585 to +601
def _dependency_hook_sources(project_root: Path) -> set[str]:
"""Return source markers that correspond to installed dependency dirs."""
apm_modules = project_root / "apm_modules"
if not apm_modules.is_dir():
return set()
sources: set[str] = set()
for path in apm_modules.rglob("*"):
if not path.is_dir():
continue
if (
(path / "hooks").is_dir()
or (path / ".apm" / "hooks").is_dir()
or (path / "apm.yml").is_file()
or (path / "SKILL.md").is_file()
):
sources.add(path.name)
return sources
Comment on lines +619 to +621
if not heal_stale_root_source or not source or source in dependency_sources:
return False
return self._hook_entry_content_key(entry) in fresh_content_keys
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label May 15, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel: ship_with_followups

fix: root hook installs are now idempotent across checkout paths -- stale duplicate entries in Claude/Codex/Cursor/Gemini/Windsurf configs are healed automatically on reinstall.

cc @danielmeppiel -- a fresh advisory pass is ready for your review.

The panel converges cleanly: the fix is correct, the unit-tier test coverage is targeted and meaningful, and no panelist raised a blocking concern. The core user promise -- 'apm install on any checkout path does not accumulate duplicate hook entries in AI-tool configs' -- is delivered and defended by six new unit tests. The implementation strategy (stable _local/{name} source marker + content-based stale-entry healing) is sound and the guards against false-positive removal of dependency-owned entries are present.

Two open items are worth tracking. First, the CHANGELOG entry leaks internal vocabulary (_apm_source, 'heal stale same-content merged hook entries') that means nothing to users. doc-writer and oss-growth-hacker converge on a user-first rewrite; this is a one-line fix that should land before merge given that CHANGELOG is the public-facing release surface. Second, all new tests call HookIntegrator directly -- the full user promise only manifests when the real apm install CLI entry point drives the integrator end-to-end. That integration-tier gap is the highest-signal post-merge follow-up.

The rglob I/O concern (python-architect, supply-chain-security-expert) is real but bounded: _dependency_hook_sources is only called for root-local installs and the practical depth is shallow in real repos. The symlink-cycle risk is theoretical absent a compromised apm_modules tree. Both are recommended follow-ups, not blockers. Logging gaps (cli-logging-expert, devx-ux-expert) are quality-of-life items that do not affect correctness.

Dissent. No substantive disagreement between panelists. The only mild divergence is emphasis: devx-ux-expert frames silent healing as a 'failure mode is the product' concern, while cli-logging-expert frames it as a debug-visibility gap. Both resolve to the same action (add a debug log after healing); the CEO sides with the logging framing -- this is a verbose/debug-mode concern, not a user-visible UX failure.

Aligned with: Portable by manifest (stable _local/{name} markers derived from apm.yml, not the filesystem path -- hook configs portable across checkout directories). Multi-harness / multi-host (healing runs uniformly across Claude, Codex, Cursor, Gemini, Windsurf). Pragmatic as npm (idempotent reinstall is table-stakes for any package manager -- this closes the gap). OSS community-driven (fix driven by reproducible issue #1329 with clear symptoms and regression tests).

Growth signal. This fix quietly solves one of the highest-visibility trust-breakers for AI-tool users: duplicate hooks in .claude/settings.json after a fresh clone or a second checkout. The breadth of targets handled uniformly is a growth multiplier. Story angle: 'apm hooks are now safe to share in team repos -- reinstalling on any checkout path heals stale entries instead of duplicating them.'

Panel summary

Persona B R N Takeaway
Python Architect 0 2 3 Solid fix; one recommended refactor (RemovalContext dataclass) and one I/O scope concern (rglob). No blocking issues.
CLI Logging Expert 0 2 1 No new user-visible output (correct). Two debug-log gaps matter for verbose/agent mode diagnostics.
DevX UX Expert 0 2 1 Idempotency contract well-addressed. Two recommended items: silent healing visibility and bare _local fallback edge case.
Supply Chain Security Expert 0 0 2 No blocking security issues. Two nits: symlink traversal in rglob, and a comment suggestion on _safe_source_name.
OSS Growth Hacker 0 0 1 Strong trust-signal fix. CHANGELOG wording leads with implementation detail rather than user outcome.
Doc Writer 0 1 1 CHANGELOG entry leaks internal symbols. No docs drift found elsewhere.
Test Coverage Expert 0 2 0 Six targeted scenario tests defend the core user promise. Two gaps: no integration-tier test; private helper edge cases uncovered.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Doc Writer] Rewrite CHANGELOG entry to user-observable outcome before merge. -- Current entry exposes _apm_source and 'heal stale same-content merged hook entries' -- internal vocabulary obscuring the user benefit. doc-writer and oss-growth-hacker converge on a user-first rewrite. CHANGELOG is the public release surface; this is a one-line fix with zero risk.
  2. [Test Coverage Expert] Add integration-tier test: seed stale .claude/settings.json, run apm install via subprocess, assert stale entry replaced by _local/{name}. -- All six new tests call HookIntegrator directly. The full user promise -- that apm install heals stale entries -- is untested at the CLI-entry-point tier. Evidence: outcome=missing at tests/integration/.
  3. [CLI Logging Expert] Add _log.debug() calls for silent OSError/YAMLError swallows and for the stale-entry healing count. -- Unexpected _local fallbacks and healed-entry counts are invisible in verbose mode. Two targeted debug log lines would make agent-assisted debugging meaningfully more informative.
  4. [Python Architect] Bound _dependency_hook_sources to depth-1/2 iteration instead of rglob('*') across the full apm_modules tree. -- rglob('*') is O(total files in all deps) and follows symlinks. A depth-bounded walk also closes the supply-chain-security-expert's symlink-cycle concern in one change.
  5. [Test Coverage Expert] Add unit-tier coverage for private helper edge cases: _is_root_local_package with None path, _get_root_local_package_name with name: null or name: '' in apm.yml. -- The bare _local fallback produces a source marker that differs from _local/{name}, meaning a future add of name: to apm.yml triggers a one-time re-heal. No test currently asserts this fallback path.

Architecture

classDiagram
    direction LR
    class BaseIntegrator {
      <<AbstractBase>>
      +check_collision()
      +is_content_identical_to_source()
      +find_hook_files()
    }
    class HookIntegrator {
      <<ConcreteIntegrator>>
      +integrate_package_hooks()
      +integrate_package_hooks_merged_target()
      +_get_package_name()
      +_get_hook_source_marker()
      +_is_root_local_package()
      +_safe_source_name()
      +_get_root_local_package_name()
      +_hook_entry_content_key()
      +_dependency_hook_sources()
      +_should_remove_prior_merged_entry()
    }
    class HookIntegrationResult {
      <<ValueObject>>
      +hooks_integrated int
    }
    class IntegrationResult {
      <<ValueObject>>
      +files_integrated int
      +files_updated int
      +files_skipped int
    }
    class _MergeHookConfig {
      <<ValueObject>>
      +config_filename str
      +target_key str
    }
    BaseIntegrator <|-- HookIntegrator : extends
    IntegrationResult <|-- HookIntegrationResult : extends
    HookIntegrator ..> _MergeHookConfig : reads
    HookIntegrator ..> HookIntegrationResult : produces
    class HookIntegrator:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A([apm install -- root package]) --> B[integrate_package_hooks_merged_target]
    B --> C{_is_root_local_package?}
    C -- No --> D[source_marker = install_path.name]
    C -- Yes --> E[_get_root_local_package_name]
    E --> E1[read apm.yml via load_yaml]
    E1 -- success --> E2[_safe_source_name from manifest name]
    E1 -- OSError/YAMLError --> E3[fallback to package.name or _local]
    E2 --> F[source_marker = _local/name]
    E3 --> F
    D --> G[heal_stale_root_source = False]
    F --> H[heal_stale_root_source = True]
    H --> I[_dependency_hook_sources: scan apm_modules/]
    G --> J[dependency_sources = empty set]
    I --> J2[dependency_sources = set of dep dir names]
    J --> K[per hook_file loop]
    J2 --> K
    K --> L[build fresh_content_keys via _hook_entry_content_key]
    L --> M{remove_current_source OR heal_stale_root_source?}
    M -- Yes --> N[_should_remove_prior_merged_entry filter]
    N --> O{owns current source?}
    O -- Yes --> REMOVE[remove entry]
    O -- No --> P{heal AND not dep source AND content matches?}
    P -- Yes --> REMOVE
    P -- No --> KEEP[keep entry]
    M -- No --> KEEP
    KEEP --> Q[extend with new entries]
    REMOVE --> Q
    Q --> R[write merged JSON config file]
Loading

Recommendation

Ship after the CHANGELOG entry is rewritten to user-observable language (follow-up #1 above -- one line, zero risk, should land in this PR). All other follow-ups are post-merge: the integration-tier test gap is the highest-signal item to track on the milestone. The fix is correct, safe, and well-tested at unit tier; blocking it for the integration test would penalize a solid contributor fix for a gap that predates this PR.


Full per-persona findings

Python Architect

  • [recommended] _should_remove_prior_merged_entry has 5 keyword-only parameters; extract a RemovalContext frozen dataclass at src/apm_cli/integration/hook_integrator.py:603
    The method signature is repeated verbatim at two call-sites. A frozen _RemovalContext dataclass encapsulates call-state once, aligns with the BaseIntegrator value-object pattern, and makes future flag additions non-invasive.
    Suggested: @dataclass(frozen=True) class _RemovalContext: source_marker: str; fresh_content_keys: set[str]; heal_stale_root_source: bool; dependency_sources: set[str]; remove_current_source: bool

  • [recommended] _dependency_hook_sources rglobs the entire apm_modules tree on every root-package install at src/apm_cli/integration/hook_integrator.py:585
    O(total files in all deps) and unbounded I/O on the install path. A depth-1/2 bounded walk correctly identifies installed package roots without traversing deep script trees.
    Suggested: Replace rglob('*') with explicit iterdir() at depth 1 and 2.

  • [nit] sort_keys=True in _hook_entry_content_key is redundant after sorted(entry.items()) at src/apm_cli/integration/hook_integrator.py:581
    Drop sort_keys=True.

  • [nit] import yaml at module level solely for yaml.YAMLError at src/apm_cli/integration/hook_integrator.py:54
    Replace with from yaml import YAMLError.

  • [nit] _is_root_local_package called 3x in the hot path at src/apm_cli/integration/hook_integrator.py:768
    Compute once and thread through.

CLI Logging Expert

  • [recommended] Silent swallow of OSError/YAMLError in _get_root_local_package_name should emit a debug log at src/apm_cli/integration/hook_integrator.py:543
    Without it, unexpected _local fallbacks are invisible in verbose/agent mode. Add _log.debug("apm.yml name read failed (%s); falling back to package name", e) before the pass.

  • [recommended] Stale-entry healing is silent at src/apm_cli/integration/hook_integrator.py:619
    Users and agents cannot confirm old checkout-derived entries were cleaned up. Add _log.debug("Healed %d stale root-source entries for event '%s'", removed, event_name) after the filter step when heal_stale_root_source is True and removed > 0.

  • [nit] Silent JSON decode failure when reading existing hook config is not logged at src/apm_cli/integration/hook_integrator.py:791
    Pre-existing pattern; new healing logic makes a debug warning more relevant.

DevX UX Expert

  • [recommended] Silent healing provides no user-visible signal when stale entries are removed at src/apm_cli/integration/hook_integrator.py:619
    Users inspecting .claude/settings.json post-reinstall and seeing missing entries have no explanation. A --verbose-gated line would satisfy the discoverability contract.

  • [recommended] When apm.yml has no name field, source marker degrades to bare _local at src/apm_cli/integration/hook_integrator.py:550
    Weakens the namespace convention. When user later adds name:, the source ID changes and triggers a one-time re-heal. A doc comment or debug log noting this edge case prevents future confusion.

  • [nit] _hook_entry_content_key excluding _apm_source means a byte-identical user-written entry could silently get APM's source marker at src/apm_cli/integration/hook_integrator.py:580
    Low probability; worth a code comment since the new healing path increases exposure.

Supply Chain Security Expert

No blocking findings.

  • [nit] apm_modules.rglob('*') follows symlinks by default at src/apm_cli/integration/hook_integrator.py:591
    A malicious package could plant a symlink cycle inside apm_modules, causing unbounded traversal. Add if path.is_symlink(): continue before the is_dir() check.

  • [nit] _safe_source_name permits interior dots after stripping at src/apm_cli/integration/hook_integrator.py:526
    No file-system risk (value is only used as a JSON string), but a comment clarifying it must never be used as a path component without containment would prevent future misuse.

OSS Growth Hacker

  • [nit] CHANGELOG entry leads with internal implementation detail at CHANGELOG.md
    Current: "...use a stable local hook source id and heal stale same-content merged hook entries left behind by older checkout-derived _apm_source values..." Suggested: "Hooks installed from root .apm/hooks/*.json are now idempotent across different checkout directories -- duplicate hook entries in Claude/Codex/Cursor/Gemini/Windsurf config files no longer accumulate when the same repo is cloned to a different path."

Auth Expert -- inactive

No auth-relevant files changed; hook_integrator.py does not touch AuthResolver, token management, credential resolution, or host classification.

Doc Writer

  • [recommended] CHANGELOG entry exposes internal implementation vocabulary at CHANGELOG.md:12
    _apm_source and "heal stale same-content merged hook entries left behind by older checkout-derived _apm_source values" mean nothing to users. The user-observable behavior is simply: re-running apm install no longer creates duplicate hook entries.
    Suggested: "Root .apm/hooks/*.json installs now use a stable source identifier, making hook entries in Claude/Codex/Cursor/Gemini/Windsurf configs idempotent across re-installs; stale duplicate entries from older installs are removed automatically. ([BUG] Root .apm hooks can duplicate when _apm_source changes with checkout directory #1329)"

  • [nit] "keeping ... idempotent" uses gerund where adjacent entries use declarative present tense at CHANGELOG.md:12
    Restructure to match the section's tense pattern.

Test Coverage Expert

  • [recommended] No integration-tier test for end-to-end "install heals stale root source" flow
    All 6 new tests call HookIntegrator directly (unit tier). The full user promise only manifests when the real apm install CLI entry point drives the integrator. Grepped tests/integration/*.py for _apm_source, stale, root_local, heal -- zero matches in hook-healing context.
    Proof (missing at integration-with-fixtures): tests/integration/test_local_install.py -- proves: Running apm install heals stale checkout-name entries in .claude/settings.json [portability-by-manifest, devx]
    Suggested: Add a fixture-based integration test that seeds a stale .claude/settings.json, runs apm install via subprocess, and asserts the stale entry is replaced by _local/{name}. Mark @pytest.mark.integration.

  • [recommended] Private helper edge cases uncovered at any tier
    _is_root_local_package(pkg_info, None) -> False; path.resolve() raising OSError -> False; _get_root_local_package_name with name: "" or name: null in apm.yml falling back to _local (not _local/_local). Grepped for direct calls to private methods -- zero matches.
    Proof (missing at unit): tests/unit/integration/test_hook_integrator.py::TestIssue1007Fixes::test_root_local_source_marker_when_apm_yml_name_is_empty -- proves: When apm.yml has name: '' or null, source marker degrades gracefully to '_local' rather than crashing [devx]

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #1330 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • fix(hooks): stabilize root-local hook source ids #1330 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1330 · ● 2.9M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label May 15, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

Maintainer sweep [2026-05-15]: tried to land your rebase from a maintainer terminal but my OAuth scope refuses workflow-file pushes (today's main contains a ci.yml change), so this needs to come from you.

Trivial rebase, two conflicts, both safe to resolve as below:

git fetch origin main
git rebase origin/main

Conflict 1 -- CHANGELOG.md (Unreleased > Fixed): keep BOTH sides. Main has new entries for #1335 and #1299; your branch adds #1329. Final order: #1335, #1299, #1329, then the existing entries below.

Conflict 2 -- tests/integration/test_registry.py line ~71: keep MAIN's version (-- breaking later tests, ASCII double-hyphen). Repo encoding rule (.github/instructions/encoding.instructions.md) bans em-dashes for cp1252 safety on Windows.

I verified the rebase locally and the panel verdict (ship_with_followups, only stylistic followups) still applies. Once you push the rebase + CI is green, please re-tag for review and I'll merge.

Thanks @imk1t!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Root .apm hooks can duplicate when _apm_source changes with checkout directory

3 participants