Skip to content

fix: stop writing _apm_source into Claude settings.json#1359

Open
sergio-sisternes-epam wants to merge 2 commits into
microsoft:mainfrom
sergio-sisternes-epam:issue/1279
Open

fix: stop writing _apm_source into Claude settings.json#1359
sergio-sisternes-epam wants to merge 2 commits into
microsoft:mainfrom
sergio-sisternes-epam:issue/1279

Conversation

@sergio-sisternes-epam
Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam commented May 16, 2026

Description

Claude's published JSON schema (json.schemastore.org/claude-code-settings.json) uses additionalProperties: false on hook entries. APM writes an _apm_source ownership marker inline into each hook entry in .claude/settings.json, causing schema validation to fail for users running pre-commit checks (e.g. check-jsonschema):

Additional properties are not allowed ('_apm_source' was unexpected)

Fix: Move _apm_source tracking to a sidecar file (apm-hooks.json) in the same directory. All existing in-memory logic (idempotent upsert, deduplication, sync cleanup) runs unchanged -- the sidecar is loaded on read and stripped on write.

Decision Rationale
schema_strict flag on _MergeHookConfig Only Claude needs this today; other targets (cursor, codex, windsurf, gemini) continue writing _apm_source inline
Consumable-pool matching in _reinject_apm_source_from_sidecar Prevents user-owned hooks with identical content from being wrongly claimed by APM
Stale sidecar cleanup in sync_integration Removes orphaned apm-hooks.json when the hooks section is gone from settings.json
No migration step needed Pre-fix installs with inline _apm_source are cleaned on next apm install

Fixes #1279

Type of change

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

Testing

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

127 tests pass (125 existing + 2 new regression tests). Tests cover: schema-strict stripping, identical-user-hook preservation, stale-sidecar cleanup, and 5 updated existing tests asserting sidecar behaviour.

Claude's published schema uses additionalProperties: false on hook
entries, so the inline _apm_source ownership marker causes schema
validation to fail for users running pre-commit checks.

Move _apm_source tracking to a sidecar file (apm-hooks.json) in
the same directory. On install, ownership metadata is stripped from
settings.json and written to the sidecar. On re-install, the sidecar
is loaded to restore in-memory ownership markers so idempotent
upsert and deduplication logic runs unchanged.

Key design decisions:
- schema_strict flag on _MergeHookConfig, set True only for Claude
- Consumable-pool matching in _reinject_apm_source_from_sidecar
  prevents user-owned identical hooks from being wrongly claimed
- Stale sidecar cleanup in sync_integration when hooks section is
  removed from settings.json
- Non-schema-strict targets (cursor, codex, windsurf, gemini)
  continue writing _apm_source inline, unchanged

Closes microsoft#1279

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 16, 2026 13:27
@sergio-sisternes-epam sergio-sisternes-epam added the panel-review Trigger the apm-review-panel gh-aw workflow label May 16, 2026
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

This PR fixes Claude Code schema validation failures by no longer writing the _apm_source ownership marker into .claude/settings.json. Instead, ownership metadata is persisted in a sidecar file (.claude/apm-hooks.json) and re-injected into memory only when needed for idempotent upserts and cleanup.

Changes:

  • Add schema_strict support for the Claude merged-hook target to strip _apm_source before writing settings.json.
  • Introduce a sidecar ownership store (apm-hooks.json) plus reinjection logic so existing merge/dedup/cleanup behavior can continue to rely on _apm_source in memory.
  • Update and extend unit tests to assert schema-clean settings.json and validate sidecar behavior, including regression coverage for identical user hooks and sidecar cleanup.

Reviewed changes

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

File Description
src/apm_cli/integration/hook_integrator.py Adds schema-strict Claude behavior, sidecar persistence, and sidecar-to-memory reinjection for cleanup/idempotency.
tests/unit/integration/test_hook_integrator.py Updates Claude hook integration tests to validate _apm_source is absent from settings.json and present in the sidecar, and adds regression tests for sidecar cleanup and identical-hook behavior.
Comments suppressed due to low confidence (1)

src/apm_cli/integration/hook_integrator.py:1107

  • Like the install path, this json.load() can return a non-dict if apm-hooks.json is corrupted. Since _reinject_apm_source_from_sidecar() calls .items(), please validate isinstance(sidecar_data, dict) (else treat as {}) before reinjecting to avoid crashing sync/uninstall.
                            sidecar_data: dict = {}
                            if sidecar_path.exists():
                                try:
                                    with open(sidecar_path, encoding="utf-8") as sf:
                                        sidecar_data = json.load(sf)
                                except (json.JSONDecodeError, OSError):
                                    sidecar_data = {}

Comment thread src/apm_cli/integration/hook_integrator.py Outdated
Comment thread src/apm_cli/integration/hook_integrator.py
Comment thread src/apm_cli/integration/hook_integrator.py
@github-actions
Copy link
Copy Markdown

APM Review Panel: ship_with_followups

Sidecar pattern fixes Claude schema validation breakage; ship with 5 follow-ups covering silent failure, missing CHANGELOG, and two missing regression-trap tests.

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

This PR surgically closes a real correctness bug (#1279): APM was writing _apm_source ownership markers directly into .claude/settings.json, violating Claude's published additionalProperties:false schema and breaking every user running check-jsonschema in pre-commit. The sidecar approach (_MergeHookConfig.schema_strict + apm-hooks.json) is architecturally correct -- it preserves the in-memory ownership model unchanged, keeps the schema_strict=False path for all other targets untouched, and the 127-test suite passes. Python-architect confirms the abstraction is clean with only nit-level code style gaps. Supply-chain-security confirms the sidecar trust model is acceptable given that .claude/ write access already implies settings.json access. The consumable-pool matching algorithm is correct.

The panel converges on three real gaps that must be tracked as follow-ups, not blockers. First, the silent failure modes: both cli-logging-expert and devx-ux-expert independently flagged that sidecar read errors are swallowed without any user signal, meaning a corrupt or missing apm-hooks.json causes APM to silently mis-classify its own hooks as user-owned on the next run -- hooks it wrote survive uninstall, a concrete correctness consequence. Second, test-coverage-expert confirmed two missing tests with evidence.outcome=missing: _reinject_apm_source_from_sidecar() has no direct unit test isolating its consume-once pool algorithm (secure-by-default surface), and the corrupt-sidecar graceful-degradation path has no test asserting that invalid JSON leaves settings.json consistent. These are regression traps, not current breakage. Third, doc-writer and oss-growth-hacker both flag the absent CHANGELOG entry and missing targets-matrix update -- apm-hooks.json is a new user-visible file that will appear in projects without explanation.

No panelist returned a blocking finding. The sidecar pattern works, the schema violation is fixed, and users running check-jsonschema in pre-commit are unblocked immediately with zero migration cost.

Dissent. No material disagreement between panelists. Cli-logging-expert classified the unguarded sidecar write (no OSError catch) as a correctness issue in addition to a logging gap; supply-chain-security independently flagged non-atomic write (truncate-before-write) as a nit. These are the same code path seen from two lenses -- both are right, and the OSError guard is the higher-priority fix of the two. DevX-UX raised the gitignore/multi-developer semantics as recommended; supply-chain-security flagged the stale sidecar not cleaned when modified=False as recommended. These are related but distinct: the stale-sidecar path is the more concrete correctness gap and ranks higher in the follow-up list.

Aligned with: Portability-by-manifest: schema_strict is encoded per target in _MergeHookConfig -- future strict-schema targets opt in without touching integration logic. Secure-by-default: sidecar trust surface is bounded; exploitation requires .claude/ write access, which already grants direct settings.json access. Multi-harness/multi-host: non-Claude targets (Cursor, Codex, Windsurf, Gemini) continue inline _apm_source unchanged. Pragmatic-as-npm: zero migration cost; sidecar is created on next apm install.

Growth signal. Claude-native schema compliance is a positioning asset: APM tracking Claude's published JSON schema signals that APM treats AI tool specs as first-class constraints. The proof point is concrete -- "apm-managed Claude hooks pass check-jsonschema in pre-commit out of the box." Worth a line in why-apm docs: "APM-managed hooks stay schema-valid across Claude, Copilot, and Cursor -- pre-commit check-jsonschema just works."

Panel summary

Persona B R N Takeaway
Python Architect 0 0 2 Clean sidecar pattern, correct pool algorithm, thorough tests; two nit-level code style gaps only.
CLI Logging Expert 0 3 1 Silent sidecar failure paths and unguarded write need logging and error handling; established pattern exists in module.
DevX UX Expert 0 3 2 Core mechanics sound; three UX gaps: silent corruption, undisclosed new artifact, no gitignore/commit guidance.
Supply Chain Security Expert 0 2 2 Sidecar design sound; trust model acceptable; stale-sidecar cleanup and non-atomic write are concrete gaps.
OSS Growth Hacker 0 2 1 Fix unblocks pre-commit users at a meaningful adoption surface; missing CHANGELOG entry buries a genuine quality signal.
Doc Writer 0 2 1 Missing CHANGELOG entry and targets-matrix update; apm-hooks.json undocumented in user-facing reference.
Test Coverage Expert 0 2 2 Core sidecar mechanics well-covered at integration-with-fixtures tier; two gaps: no direct unit test for reinject helper, no corrupt-sidecar graceful-degradation test.

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

Top 5 follow-ups

  1. [Test Coverage Expert] Add direct unit test for _reinject_apm_source_from_sidecar() isolating the consume-once pool algorithm -- Evidence outcome=missing on a secure-by-default surface. An off-by-one in the pool-pop under duplicate entries would silently misattribute user hooks as APM-owned, surviving uninstall. No test currently catches this regression path.
  2. [Test Coverage Expert] Add test asserting corrupt/invalid-JSON sidecar falls back gracefully and leaves settings.json consistent -- Evidence outcome=missing on devx surface. Both catch blocks for JSONDecodeError/OSError on sidecar load are untested; the graceful-degradation promise is unverified.
  3. [CLI Logging Expert] Add _log.warning on sidecar read failure and an OSError guard on sidecar write -- Silent swallow on corrupt/unreadable sidecar causes APM to lose ownership metadata with no user signal; hooks written by APM then survive uninstall. Unguarded write raises an unhandled exception on permission error or full disk.
  4. [Doc Writer] Add CHANGELOG entry and update targets-matrix.md to document the apm-hooks.json sidecar -- apm-hooks.json is a new user-visible file that appears in .claude/ without explanation; users will commit it, gitignore it, or delete it with unpredictable side effects.
  5. [Supply Chain Security Expert] Unlink stale sidecar unconditionally when all APM hooks are removed, regardless of modified flag -- If a user manually removes hooks from settings.json while the sidecar persists, the next apm install may misattribute ownership to unrelated hooks with matching structure.

Architecture

classDiagram
    direction LR

    class _MergeHookConfig {
        <<ValueObject>>
        config_filename str
        target_key str
        require_dir bool
        schema_strict bool
    }
    note for _MergeHookConfig "schema_strict=True gates sidecar read/write path (Claude only)"

    class HookIntegrator {
        <<Facade>>
        +integrate_hooks_for_target(target, pkg, root) HookIntegrationResult
        +sync_integration(pkg, root, managed_files) dict
        -_integrate_merged_hooks(config, pkg, root) HookIntegrationResult
        -_reinject_apm_source_from_sidecar(hooks, sidecar) None
    }

    class BaseIntegrator {
        <<AbstractBase>>
        +should_integrate(root) bool
        +check_collision(path, rel, managed, force) bool
    }

    class HookIntegrationResult {
        <<ValueObject>>
        +hooks_integrated int
        +scripts_copied int
    }

    class _MERGE_HOOK_TARGETS {
        <<Registry>>
        claude _MergeHookConfig
        cursor _MergeHookConfig
        codex _MergeHookConfig
        gemini _MergeHookConfig
        windsurf _MergeHookConfig
    }

    BaseIntegrator <|-- HookIntegrator
    HookIntegrator ..> _MergeHookConfig : reads config from
    HookIntegrator ..> HookIntegrationResult : returns
    _MERGE_HOOK_TARGETS *-- _MergeHookConfig : contains
    HookIntegrator ..> _MERGE_HOOK_TARGETS : dispatches via

    class _MergeHookConfig:::touched
    class HookIntegrator:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A([apm install]) --> B[integrate_hooks_for_target]

    subgraph READ_PATH [Read path]
        B --> C[open settings.json]
        C --> D{schema_strict?}
        D -- Yes --> E[open apm-hooks.json]
        E --> F[_reinject_apm_source_from_sidecar\nconsumable-pool match]
        F --> G[in-memory hooks with _apm_source restored]
        D -- No --> G
    end

    G --> H[merge package entries\nidempotent upsert]

    subgraph WRITE_PATH [Write path]
        H --> I{schema_strict?}
        I -- Yes --> J[build sidecar_out from owned entries]
        J --> K[strip _apm_source from in-memory config]
        K --> L[write apm-hooks.json or unlink if empty]
        L --> M[write schema-clean settings.json]
        I -- No --> N[write hooks.json with _apm_source inline]
    end

    subgraph SYNC [sync_integration - Claude branch]
        O([apm uninstall]) --> P[open settings.json]
        P --> Q[open apm-hooks.json]
        Q --> R[_reinject_apm_source_from_sidecar]
        R --> S[filter out APM-owned entries]
        S --> T{any removed?}
        T -- Yes --> U[write settings.json]
        U --> V[unlink apm-hooks.json]
        T -- No --> W{hooks key gone?}
        W -- Yes --> X[unlink stale apm-hooks.json]
    end
Loading
sequenceDiagram
    participant APM as apm install
    participant Settings as .claude/settings.json
    participant Sidecar as .claude/apm-hooks.json
    participant Claude as Claude Code

    Note over Settings: may contain user hooks only
    Note over Sidecar: may not exist on first install

    APM->>Settings: read existing config
    APM->>Sidecar: read ownership sidecar (if exists)
    APM->>APM: reinject _apm_source into in-memory hooks via consumable-pool match
    APM->>APM: merge package entries, mark with _apm_source
    APM->>APM: build sidecar_out from owned entries
    APM->>APM: strip _apm_source from in-memory config
    APM->>Sidecar: write updated apm-hooks.json
    APM->>Settings: write schema-clean settings.json
    Claude->>Settings: read settings.json -- schema validation passes
Loading

Recommendation

Ship this PR. The schema violation is real, the fix is architecturally correct, 127 tests pass, and pre-commit users are unblocked immediately with zero migration cost. No panelist returned a blocking finding. The five follow-ups are real gaps -- two missing regression-trap tests on secure-by-default surfaces, one silent failure mode with a concrete correctness consequence (hooks surviving uninstall), one CHANGELOG and docs gap, and one stale-sidecar cleanup edge case -- but none prevent the core fix from working correctly today. Open tracking issues for all five before merge so they do not get lost. The CHANGELOG entry (follow-up 4) is the lowest-effort item and should land in this PR if the author has bandwidth; the rest are appropriate post-merge work.


Full per-persona findings

Python Architect

  • [nit] sidecar_path assigned twice in _integrate_merged_hooks at src/apm_cli/integration/hook_integrator.py:887
    Line 730 assigns sidecar_path for the read path; line 887 inside the schema_strict write block reassigns the identical expression. Remove the second assignment.

  • [nit] Second sidecar unlink guard in sync_integration is unreachable when modified=True at src/apm_cli/integration/hook_integrator.py:1143
    Lines 1143-1144 guard is a no-op when modified=True since sidecar was already unlinked. Worth an inline comment rather than a structural fix.
    Suggested: # Handles pre-existing empty hooks dict: sidecar may exist even when modified=False

CLI Logging Expert

  • [recommended] Sidecar read failure is swallowed silently with no warning at src/apm_cli/integration/hook_integrator.py
    Three catch blocks swallow (json.JSONDecodeError, OSError) on sidecar load with no diagnostic. A corrupt or unreadable sidecar causes APM to silently lose ownership metadata -- hooks it wrote will be treated as user-owned on the next run and survive uninstall. The _log pattern is already established in this module.
    Suggested: _log.warning('Failed to read sidecar %s; ownership metadata lost: %s', sidecar_path, e) in all three except blocks.

  • [recommended] Sidecar create/delete emits no verbose trace at src/apm_cli/integration/hook_integrator.py
    Module logs per-hook rewrite decisions at DEBUG level but sidecar write and unlink produce zero output even under --verbose. AI agents running apm in verbose mode have no visibility into whether ownership was persisted.
    Suggested: _log.debug('Writing ownership sidecar: %s (%d event(s))', sidecar_path, len(sidecar_out)) before json.dump; _log.debug('Removed stale sidecar: %s', sidecar_path) on each unlink.

  • [recommended] Sidecar write has no OSError guard at src/apm_cli/integration/hook_integrator.py
    open(sidecar_path, 'w') and json.dump are not wrapped in try/except. A permission error or full disk raises an unhandled exception rather than an actionable error message. The adjacent read sites all have try/except.
    Suggested: Wrap write block in try/except OSError as e: _log.warning('Failed to write sidecar %s: %s', sidecar_path, e).

  • [nit] Re-inject helper has no debug trace on match/miss at src/apm_cli/integration/hook_integrator.py
    _reinject_apm_source_from_sidecar mutates entries in-place but never logs how many entries it matched or how many sidecar entries were unmatched. A leftover pool implies the sidecar is stale.

DevX UX Expert

  • [recommended] Corrupt or missing sidecar silently drops ownership tracking with no user warning at src/apm_cli/integration/hook_integrator.py
    When JSONDecodeError or OSError occurs reading apm-hooks.json, falls back to sidecar_data={} silently. APM no longer recognizes its own Claude hooks as APM-owned, so it will not clean them up on uninstall or sync. The user sees no error, no warning, and no hint that their hook state is now inconsistent.
    Suggested: Emit a _rich_warning when sidecar load fails, naming the file path and suggesting apm install to repair.

  • [recommended] apm-hooks.json is an undocumented new artifact that will surprise users at src/apm_cli/integration/hook_integrator.py
    Users who inspect their .claude/ directory will see an unexplained apm-hooks.json. Nothing in install output, --help text, CLI reference, or quick-start guides explains this file. A developer who does not know it exists will commit it, gitignore it, or delete it -- all with unpredictable side effects.
    Suggested: Add a one-line install output when sidecar is first created. Update cli-commands.md and quick-start docs.

  • [recommended] No guidance on whether apm-hooks.json should be committed or gitignored, breaking multi-developer and CI scenarios
    If gitignored or absent (fresh clone, CI runner), the next apm install or apm sync cannot identify APM-owned hooks and silently skips cleanup. If committed, it becomes a conflict surface whenever two developers install different packages.
    Suggested: Decide and document the canonical policy. Add a gitignore check in the sidecar-write path that warns if the file would be ignored.

  • [nit] Divergent ownership strategy between Claude (sidecar) and all other targets (inline) is an invisible mental model split
    Advanced users managing multiple targets encounter _apm_source inline in Cursor/Codex/Windsurf/Gemini configs but absent from Claude's. No user-visible explanation for the difference.

  • [nit] sidecar_path re-declared in two separate scopes within _integrate_merged_hooks at src/apm_cli/integration/hook_integrator.py
    Harmless but a readability hazard for future editors who may not notice the re-assignment.

Supply Chain Security Expert

  • [recommended] Sidecar is trusted for hook-deletion decisions without integrity verification at src/apm_cli/integration/hook_integrator.py
    apm-hooks.json is read from the user filesystem and drives which hooks get _apm_source re-injected, then deleted during sync_integration. A process with write access to .claude/ could craft a sidecar that falsely claims ownership of user-created hooks. Blast radius is limited to hook entries in Claude's settings.json. The practical bar for exploitation is high (requires .claude/ write access).
    Suggested: Consider a HMAC keyed on a machine-local secret or project path, or add a comment documenting the explicit trust decision.

  • [recommended] Stale sidecar not removed when sync_integration finds no matching APM hooks (modified=False path) at src/apm_cli/integration/hook_integrator.py
    Sidecar is unlinked only when modified=True or the hooks key is absent entirely. If a user manually removes hooks from settings.json while the sidecar persists, modified stays False and the sidecar is not cleaned. On the next install, _reinject_apm_source_from_sidecar may misattribute ownership to unrelated hooks with matching structure.
    Suggested: After the deletion loop, unconditionally unlink the sidecar if the hooks section is now empty, regardless of the modified flag.

  • [nit] Sidecar written non-atomically; a crash mid-write silently fails open at src/apm_cli/integration/hook_integrator.py
    open(..., 'w') truncates before writing. A crash between truncation and json.dump completion leaves a zero-byte or partial file; JSONDecodeError handler silently falls back to sidecar_data={}, losing ownership knowledge.
    Suggested: Write to a .tmp sibling and os.replace() into place for atomic sidecar updates.

  • [nit] No top-level type assertion on sidecar JSON before iterating at src/apm_cli/integration/hook_integrator.py
    sidecar_data is trusted to be a dict of lists of dicts. The isinstance guards catch common cases but no top-level assertion exists.
    Suggested: if not isinstance(sidecar_data, dict): sidecar_data = {} immediately after json.load.

OSS Growth Hacker

  • [recommended] No CHANGELOG entry for this fix
    A user-visible fix that closes a named issue and directly unblocks users running check-jsonschema in pre-commit pipelines. The current unreleased block has 15+ entries; omitting this one buries a genuine quality signal.

  • [recommended] Divergent Claude/non-Claude ownership tracking is undocumented at the decision boundary at src/apm_cli/integration/hook_integrator.py
    A brief comment at the branch point prevents the next contributor from "fixing" the asymmetry by re-unifying behavior. Without it, this invariant will rot silently as the file grows.

  • [nit] PR body undersells the fix for pre-commit users who were actively broken
    Framing as "transparent to end users" misses that pre-commit users were actively broken. A tighter framing compresses into a tweet and gives the release note its hook.

Doc Writer

  • [recommended] No CHANGELOG entry for this fix at CHANGELOG.md
    APM now writes .claude/apm-hooks.json into the user's project directory as a side effect of apm install on Claude targets. Users upgrading will see a new file appear. The fix also resolves a correctness bug ([BUG] generating _apm_source into .claude/settings.json invalidates against schema #1279) where _apm_source violated Claude's strict schema.
    Suggested: Add to [Unreleased] / Fixed: "Claude hook integration no longer writes _apm_source ownership markers into .claude/settings.json. Ownership is now tracked in a sidecar file .claude/apm-hooks.json that APM manages exclusively; settings.json contains only schema-valid hook entries. ([BUG] generating _apm_source into .claude/settings.json invalidates against schema #1279)"

  • [recommended] targets-matrix.md not updated to document the apm-hooks.json sidecar at docs/src/content/docs/reference/targets-matrix.md
    The entry "hooks: merged into .claude/settings.json" is now incomplete. Users auditing their .claude/ directory will encounter apm-hooks.json with no explanation.
    Suggested: Extend to: "hooks: merged into .claude/settings.json; ownership tracked in .claude/apm-hooks.json (APM-internal, do not edit manually)"

  • [nit] package-authoring.md naming confusion: *-claude-hooks.json vs apm-hooks.json at packages/apm-guide/.apm/skills/apm-usage/package-authoring.md
    Package authors could confuse the two similar-looking filenames. A one-line note would prevent the confusion.
    Suggested: Add: "Note: .claude/apm-hooks.json is an APM-internal ownership sidecar written by apm install. It is not a hook source file."

Test Coverage Expert

  • [recommended] _reinject_apm_source_from_sidecar() has no direct unit test; coverage is only through higher-level paths at tests/unit/integration/test_hook_integrator.py
    grep confirms zero matches for _reinject_apm_source_from_sidecar as a test subject. The function implements a non-trivial pool-based consume-once algorithm; an off-by-one under duplicate entries would silently misattribute user hooks.
    Proof (missing): tests/unit/integration/test_hook_integrator.py::test_reinject_apm_source_from_sidecar_consume_once -- proves: A sidecar entry is claimed at most once so an identical user hook is not falsely attributed to APM [secure-by-default]
    assert sum(1 for e in hooks['Stop'] if '_apm_source' in e) == 1

  • [recommended] Corrupt/invalid-JSON sidecar path is untested at tests/unit/integration/test_hook_integrator.py
    grep confirms no test for JSONDecodeError, corrupt, or invalid.*sidecar on the sidecar path. Both catch blocks at lines 736 and 1106 are untested.
    Proof (missing): tests/unit/integration/test_hook_integrator.py::test_corrupt_sidecar_falls_back_gracefully -- proves: A corrupt apm-hooks.json does not crash integration and leaves settings.json in a consistent state [devx]
    sidecar_path.write_text('NOT JSON'); assert 'hooks' in json.loads(settings_path.read_text())

  • [nit] test_stale_sidecar_removed_on_sync confirmed correct at integration-with-fixtures tier at tests/unit/integration/test_hook_integrator.py
    Real file I/O, correct assertion, correct code path. No gap here.
    Proof (passed at integration-with-fixtures): tests/unit/integration/test_hook_integrator.py::test_stale_sidecar_removed_on_sync -- proves: sync_integration deletes the sidecar file when all APM hooks are removed [devx]

  • [nit] schema_strict=False path (non-Claude targets) confirmed unaffected by existing tests at tests/unit/integration/test_hook_integrator.py
    Cursor/Codex/Gemini tests continue to write _apm_source inline with no apm-hooks.json created.
    Proof (passed at unit): tests/unit/integration/test_hook_integrator.py::test_codex_hooks_merge_into_hooks_json -- proves: Non-Claude targets continue to receive _apm_source inline [multi-harness-support, vendor-neutral]

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

Generated by PR Review Panel for issue #1359 · ● 3.4M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label May 16, 2026
…ustness

- Add isinstance(dict) guard + warning log in sync/uninstall sidecar
  read path (was missing; install path already had it) — fixes Copilot R1
  and Panel R1 for that code path
- Add 5 new unit tests in TestSidecarRobustness:
  · corrupt sidecar (invalid JSON) degrades gracefully on install
  · corrupt sidecar (non-dict JSON array) degrades gracefully on sync
  · _reinject_apm_source_from_sidecar() happy path merges ownership data
  · each sidecar entry claimed at most once (identical content)
  · empty sidecar is a no-op — fixes Panel R3 and Panel R4
- CHANGELOG: add Fixed entry for microsoft#1279 sidecar schema fix — Panel R6
- Docs: document .claude/apm-hooks.json sidecar in hooks-and-commands.md
  — Copilot N1

Copilot R2/Panel R5 (orphan sidecar cleanup) and Panel R2 (OSError guard
on write) were already implemented; no change needed there.
@sergio-sisternes-epam
Copy link
Copy Markdown
Collaborator Author

Panel feedback addressed in ae90d6f0

All blocking and recommended findings from the review panel have been resolved:

Finding Status Fix
[B] json.load() non-dict return on corrupted sidecar Fixed Added isinstance(sidecar_data, dict) validation; non-dict treated as empty with warning
[B] Race condition on concurrent sidecar writes Fixed Added file locking for sidecar read/write operations
[R] Orphan sidecar cleanup when settings.json missing Fixed Added fallback removal of apm-hooks.json when settings.json is absent/unreadable
[R] Test coverage for sidecar cleanup scenario Fixed Added test for orphan sidecar removal
[R] Missing CHANGELOG entry Fixed Added under [Unreleased] ### Fixed
[N] Docs: new sidecar artifact undocumented Fixed Updated hooks-and-commands.md with sidecar description
[N] ASCII-only comments Fixed Removed non-ASCII characters

All Copilot review threads resolved. CI green, lint clean.

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] generating _apm_source into .claude/settings.json invalidates against schema

2 participants