Skip to content

fix(hooks): type untyped flat command handlers for Claude - #3153

Draft
parth (Parth-Vasave) wants to merge 7 commits into
microsoft:mainfrom
Parth-Vasave:fix/3130-claude-hook-handler-type
Draft

parth (Parth-Vasave) wants to merge 7 commits into
microsoft:mainfrom
Parth-Vasave:fix/3130-claude-hook-handler-type

Conversation

@Parth-Vasave

@Parth-Vasave parth (Parth-Vasave) commented Oct 2, 2026 •

Copy link
Copy Markdown

fix(hooks): type untyped flat command handlers for Claude

Description

Claude-native rendering now supplies "type": "command" for flat hook command entries that omit type, so .claude/settings.json receives schema-valid handlers. Explicit handler types, nested Claude groups, non-command entries and every other target are unchanged.

Warning

Readiness blocked after main integration. At the published head, tests/integration/test_architecture_owner_rule_mutations.py::test_matrix_covers_every_registry_guard_exactly_once fails because main registers registry-delegation-build-id-verification without a matrix case. The matrix and all owner registry files match main b71cb47c exactly. Passing scoped tests and remote CI do not override this failure. No shared-main repair is authorized during release wind-down; this PR remains open and draft.

TL;DR

Flat hook entries without type are valid in Cursor (where type defaults to command), but the Claude render wrapped them into { "matcher", "hooks": [...] } groups and left the handler without the type field Claude's hook schema requires. The Claude renderer now adds it for recognized command entries only. A shared legacy-native selector keeps reinstall cleanup recognizing entries written before this fix without changing ownership or consent policy.

Note

Flat input only. Nested Claude handlers remain as authored. The contributor accepted the completed folds; no author rebase or additional push is requested. This PR remains draft.

Problem (WHY)

  • The issue's exact repro (preToolUse with command, matcher, timeout, no type) rendered a Claude handler with no type. As the report puts it: "The nesting happens, but the handler is incomplete."
  • The same happened when the flat file sat next to a Claude-shaped hook file in the same package: its group was merged into the same PreToolUse list, still untyped.
  • [!] Typing the output changes the content key that reinstall cleanup matches on. Without legacy-form recognition, a project's own root .apm/hooks entries carrying a stale source marker would be left behind as an untyped duplicate instead of healed, regressing the #1329 / #1392 contract.

The approved Done-when requires schema-valid Claude command handlers from applicable untyped flat input while preserving ownership and other targets. This PR claims schema validity, not a demonstrated Claude Code runtime rejection. The regression and mutation evidence below makes that boundary checkable, following Agent Skills' validation pattern: "do the work, run a validator (a script, a reference checklist, or a self-check), fix any issues, and repeat until validation passes."

Approach (WHAT)

  • In Claude rendering only, default type: "command" for a flat entry with no type that the neutral hook grammar reads as a command (command, or bash/powershell/windows normalized to command).
  • Leave explicit types (including prompt with a command field), nested Claude groups, and untyped non-command entries as authored.
  • Leave the shared hook IR (hook_contract.py, hook_ir.py) and other targets' rendering behavior unchanged; Codex's existing typing and event mapping are not overridden.
  • Route legacy ownership-match shapes through _legacy_hook_entries(): pre-default Claude groups, existing flat Codex entries, and no legacy entries for other targets.
  • Prove installation and upgrade through real CLI scenarios, with a static owner-boundary guard and mutation checks.

The reviewed fix was carried forward by a normal merge of main b71cb47cd99e665d285a497ba6d05e810ae42ea4 into bbd89bf3e4ea5ff9ad59ea86f4e34a9203285b77, preserving both parents and contributor commits. Git's local merge was clean despite the earlier GitHub DIRTY report: both changelog entries and independent hook architecture guards were retained. The PR's production helpers and regression tests are unchanged; the actual prior panel is carried forward, not represented as a new review.

Implementation (HOW)

File Change
src/apm_cli/integration/hook_native_formats.py _with_claude_default_handler_type() reuses _handler_to_ir() for command recognition. _to_claude_hook_entries(..., default_handler_type=False) reproduces pre-fix output; _legacy_hook_entries() owns Claude/Codex legacy-shape selection.
src/apm_cli/integration/hook_integrator.py Uses that selector for legacy_content_keys, replacing the inline Codex-only branch. Existing ownership removal policy is unchanged. The file is 2,099 of 2,100 lines at this head.
scripts/architecture_linter/checks/mutation_hook_contract.py Registers _nhc_legacy_native_shapes under mutation_writes.neutral_hook_contract, requiring cleanup to call the native-shape owner.
tests/unit/integration/test_hook_integrator_issue3130.py 14 component cases cover flat/merged input, command aliases, explicit types, nested/non-command preservation, other-target isolation, and exact settings/sidecar/source-marker healing.
tests/integration/test_architecture_contract_guards.py Covers typed/untyped Claude native edges and verifies the boundary linter detects bypassing the legacy selector.
tests/integration/test_required_lifecycle_state_machine.py Two real CLI scenarios (standalone/merged) install, recreate legacy output and stale root ownership, then reinstall twice; assert durable-state convergence, user-setting preservation, an unchanged Codex sentinel, and no hook execution.
docs/src/content/docs/producer/author-primitives/hooks-and-commands.md Documents Claude's flat-only input/output contract next to the passage the issue quotes.
packages/apm-guide/.apm/skills/apm-usage/package-authoring.md Mirrors that contract in the packaged usage guide.
CHANGELOG.md One Fixed entry under [Unreleased], crediting parth (@Parth-Vasave) and linking #3153 / #3130.

Diagrams

Legend: dashed nodes are new native-renderer helpers; legacy and current Claude forms meet at existing ownership reconciliation before settings and sidecar writes.

flowchart LR
    subgraph Source[Source]
        A["Flat Claude command entries"]
    end
    subgraph Render[Native shapes]
        L["_legacy_hook_entries: pre-fix form"]
        N["_with_claude_default_handler_type: recognized untyped flat entries"]
        C["_to_claude_hook_entries: nested groups"]
    end
    subgraph Reconcile[Ownership reconciliation]
        K["_hook_entry_content_key: compare old and current forms"]
        W["Write settings.json and apm-hooks.json"]
    end
    A --> L
    L --> K
    A --> N
    N --> C
    C --> K
    K --> W
    classDef new stroke-dasharray: 5 5;
    class L,N new;
Loading

Trade-offs

  • Flat input only. Matches the approved Done-when; extending defaulting into nested Claude groups is not part of this fix.
  • Default in the Claude renderer, not shared IR. Keeps target-specific requirements out of neutral grammar and avoids imposing this default on other targets.
  • Keep legacy matching, centralize its shape selection. Dropping compatibility would reintroduce stale-root duplicates; the owner helper avoids duplicating that decision in the integrator and stays within its line limit.
  • Spec waiver rather than spec edits. OpenAPM v0.1 has no normative target-native handler-type fields. The portable hook subset in [FEATURE] Define OpenAPM hook semantics and replace pairwise hook translation #2111 and JSON-thaw work in fix(hooks): render nested hook metadata as plain JSON #3211 remain separate.
  • Bounded validation. Component and real CLI tests verify materialized state; neither they nor the schema claim demonstrate Claude Code runtime rejection. No full local suite was rerun at this head.

Benefits

  1. The issue's repro writes {"type": "command", "command": "...", "timeout": 10} instead of an untyped handler.
  2. A flat file beside a Claude-shaped file yields the expected typed handlers in both settings.json and apm-hooks.json.
  3. Explicit command / prompt types, nested handlers, and non-command entries retain their existing behavior.
  4. Codex and Cursor tests prove the Claude-only default helper is not invoked for those targets.
  5. Reinstalling over pre-fix settings converges to typed owned groups, including stale root-package entries, without claiming user hooks or executing commands.
Historical contributor observations (not current-head assertions)

The original write-up recorded these observations around cecf16be / 18c4c43c (v0.33.0). They were not revalidated as part of this body correction and are not evidence of failures at the reviewed head:

  • Broad local tests wrote apm.lock.yaml, .github/mcp.json, .codex/config.toml, apm_modules/, .local/state/gh/ and build/lib/ into the checkout.
  • An unqualified uv run pytest was reported to skip the suite through the performance opt-in collection hook; the v0.33.0 lockfile was reported to reference the Microsoft package feed.
  • Some root-level tests had stale assertions, and the earlier local unit run reported 23,584 passed / 2 load-sensitive failures. An earlier lifecycle run reported 228 passed / 1 missing-pwsh failure.
  • Earlier audit, NOTICE and docs checks were reported successful. Those results are historical, not a substitute for the exact-head evidence below.

Issue and approved scope

Issue: Fixes #3130

Human scope-approval comment: #3130 (comment)

This PR completes the bounded issue scope: Claude-native typing of untyped flat command handlers (standalone and merged alongside an existing Claude-shaped hook file), preserved explicit and non-command types, regression coverage for both cases, and related guidance. It does not change shared hook representation, other targets' behavior, Codex event-name mapping, ownership or consent behavior. Contributor coordination is resolved by acceptance of the folds; the existing advisory records the prior ship_now panel at bbd89bf3 and the current shared-main readiness blocker; it is not an instruction to publish or merge.

Type of change

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

Testing

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

Evidence is bound to 71fa67b6f5f6927fb49edd8e0099f60120a7c425, with tested base b71cb47cd99e665d285a497ba6d05e810ae42ea4. "All existing tests pass" remains unchecked because the named shared-main matrix gate fails; no full local suite was run at this head. The six reviewed-head workflows below completed successfully, but do not establish readiness over that failure. Later main changes are not covered by this evidence.

Validation evidence

Check Recorded result
Named shared-main gate: uv run --frozen --extra dev pytest -q tests/integration/test_architecture_owner_rule_mutations.py::test_matrix_covers_every_registry_guard_exactly_once FAILED: 1 failed in 2.64s; missing registry-delegation-build-id-verification matrix coverage. conflict-b71/shared-baseline.log and .xml. No fix attempted.
bash .worker-evidence/lint.sh Exit 0: canonical ruff check/format, YAML I/O, portable-relative-path and 2,100-line guards, pylint R0801, auth boundary, plus architecture boundary; conflict-b71/lint.log names the exact merged head.
Focused functional command below 260 passed in 78.13s, including the 14 issue-specific component cases, real CLI scenarios, owner guards and main's hook-sidecar drift E2E case.
Retained prior-head conformance evidence (bbd89bf3) 319 passed, 2 skipped; 129 requirements aligned; statements unchanged then. Not rerun locally after this merge; current remote conformance succeeded.
Retained prior-head strict-quality evidence (bbd89bf3, quality-final.log) 64 passed; assertion ratchet and exact-duplicate ratchet clean.
Retained prior-head cd docs && npm run test:links (bbd89bf3) 14 link-checker tests passed, 0 failed; not a claim that 14 is the number of site links checked.
Current exact-head owner mappings (conflict-b71/functional.xml) Both CLI variants and the legacy-selector boundary test executed and passed within the 260-test run, with no skips.
Existing Mode B waiver (mode-b-final.log, unchanged PR delta) Prior measurement: 25 substantive added critical-path lines; genuine waiver retained, with current remote conformance successful.

Remote workflows at the reviewed head: CI, Merge Gate, CodeQL, Deploy Docs, Spec conformance, and NOTICE Drift Check all completed success. gh pr checks 3153 --repo microsoft/apm --watch --interval 30 completed with exit 0 after one observer connection-reset retry (no workflow rerun), including CI's unit shards, lifecycle and Windows gates.

Exact focused/conformance commands and retained output

Run from the worktree root. The evidence directory and its lint.sh are retained worker artifacts, not committed project files. The lint script runs the canonical commands with --frozen, including scripts/windows_native_symlink_probe_entry.py; its macOS-compatible Python checks enforce the CI YAML, relative-path and line-count guards.

TMPDIR="$PWD/.worker-evidence" UV_CACHE_DIR="$PWD/.worker-evidence/uv-cache" APM_E2E_TESTS=1 APM_BINARY_PATH="$PWD/.venv/bin/apm" uv run --frozen --extra dev pytest -q tests/unit/integration/test_hook_integrator*.py tests/integration/test_architecture_contract_guards.py tests/integration/test_required_lifecycle_state_machine.py::test_required_claude_untyped_hook_upgrade_preserves_ownership_and_converges 'tests/integration/test_architecture_owner_rule_mutations.py::test_owner_rule_catches_its_guard_mutation[hooks-integrations-neutral-hook-contract]' tests/integration/test_hook_sidecar_drift_e2e.py --basetemp=.worker-evidence/conflict-b71/pytest-functional --junitxml=.worker-evidence/conflict-b71/functional.xml
260 passed in 78.13s (0:01:18)

Retained prior-head (bbd89bf3) conformance commands/output; not rerun locally after this merge:

uv run --frozen --extra dev python -m tests.spec_conformance.orphan_check
uv run --frozen --extra dev pytest tests/spec_conformance -p no:randomly --no-header -q --basetemp=.worker-evidence/pytest-conformance2
uv run --frozen --extra dev python -m tests.spec_conformance.gen_statement
git diff --exit-code CONFORMANCE.json CONFORMANCE.md
[+] orphan_check OK: 129 requirements aligned across anchors / manifest / Appendix C / pytest markers
319 passed, 2 skipped in 40.48s
[+] wrote CONFORMANCE.json and CONFORMANCE.md

That prior run's final git diff --exit-code passed with no statement drift. bash .worker-evidence/lint.sh exited 0 and ended with:

All seven canonical lint gates plus architecture passed at 71fa67b6f5f6927fb49edd8e0099f60120a7c425

Mutation evidence is retained from five deliberately broken variants at reviewed bbd89bf3, all restored there. The faithful merge changed no PR regression trap or guard, so no new mutation program was needed: disabling defaulting caused 10 failures; removing legacy keys caused 3; overwriting explicit types caused 1; removing the existing rewrite-scope static check caused 1; removing both Claude legacy-owner routing and its new static check caused 4. These are failed-test counts, not counts of separate mutation programs.

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 A flat untyped hook becomes a typed Claude handler in settings and the sidecar. Multi-harness support, Portability by manifest tests/unit/integration/test_hook_integrator_issue3130.py::test_standalone_flat_untyped_entry_gets_command_type (regression-trap for #3130) integration (component)
2 The same flat hook beside a Claude-shaped file retains both groups correctly. Multi-harness support tests/unit/integration/test_hook_integrator_issue3130.py::test_flat_untyped_entry_merged_next_to_claude_shaped_file integration (component)
3 Command aliases are recognized; explicit types, non-command entries and nested handlers are not retyped. Portability by manifest tests/unit/integration/test_hook_integrator_issue3130.py::test_flat_handler_types_are_defaulted_only_for_commands (7 cases); tests/unit/integration/test_hook_integrator_issue3130.py::test_untyped_handler_inside_nested_claude_group_is_unchanged integration (component)
4 Codex and Cursor installs do not invoke Claude-only defaulting. Multi-harness support, Vendor-neutral tests/unit/integration/test_hook_integrator_issue3130.py::test_claude_handler_default_does_not_reach_other_targets (2 cases; helper-isolation check) integration (component)
5 Reinstall heals old untyped groups and stale root source markers without duplicate owned entries. DevX (pragmatic as npm), Governed by policy tests/unit/integration/test_hook_integrator_issue3130.py::test_reinstall_replaces_untyped_group_written_before_fix; tests/unit/integration/test_hook_integrator_issue3130.py::test_stale_root_source_untyped_group_is_still_healed integration (component)
6 Real CLI install and two reinstalls preserve user settings, ownership, source files and unrelated target state without executing the installed hook. DevX (pragmatic as npm), Governed by policy, Secure by default tests/integration/test_required_lifecycle_state_machine.py::test_required_claude_untyped_hook_upgrade_preserves_ownership_and_converges[standalone] and [merged] e2e

The registered owner guard is separately proven by tests/integration/test_architecture_contract_guards.py::test_legacy_hook_shapes_boundary_rejects_cleanup_bypass; it and both CLI mappings executed successfully at the merged head within the 260-test run.

How to test

  • Run uv run --frozen --extra dev pytest -q tests/unit/integration/test_hook_integrator_issue3130.py; expect 14 passed.
  • Run the focused command above with the project's .venv/bin/apm; expect both standalone/merged CLI upgrade scenarios and owner-boundary tests to pass.
  • Install the issue's flat preToolUse hook without type using apm install --target claude; confirm PreToolUse in .claude/settings.json has "type": "command" and .claude/apm-hooks.json records the owned group.
  • Add a Claude-shaped hook file beside it and reinstall twice; confirm expected groups, sidecar ownership and unrelated settings remain stable.
  • Run uv run --frozen --extra dev pytest -q tests/unit/integration/test_hook_integrator_issue3130.py -k 'other_targets or nested or defaulted_only'; confirm other-target isolation and explicit/nested/non-command preservation.

Spec conformance (OpenAPM v0.1)

If this PR changes behaviour that an OpenAPM v0.1 req-XXX covers,
confirm the three-step ritual in the
development guide:

  • Spec edit: docs/src/content/docs/specs/openapm-v0.1.md updated
    (new/changed <a id="req-XXX"></a> anchor + prose + Appendix C
    row).
  • Manifest edit: docs/src/content/docs/specs/manifests/openapm-v0.1.requirements.yml
    updated.
  • Test edit: a @pytest.mark.req("req-XXX") test under
    tests/spec_conformance/ added or extended.
  • CONFORMANCE.{md,json} regenerated via
    uv run --extra dev python -m tests.spec_conformance.gen_statement
    and committed.
  • N/A -- this PR does not change OpenAPM-observable behaviour.

src/apm_cli/integration/ is a Mode B critical path; the reviewed diff has 25 substantive added lines there. The unmodified Mode B script accepted the existing contributor waiver. No req-XXX covers target-native hook handler fields, and req-lk-021 ownership reconciliation is preserved, not changed.

apm-spec-waiver: Claude-native hook handler type default; OpenAPM v0.1 defines no target-native hook handler fields and req-lk-021 ownership reconciliation is preserved

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Flat hook command entries that omit `type` (valid in Cursor, where it
defaults to `command`) were wrapped into Claude's matcher groups without
the handler `type` field that Claude's hook schema requires.

The Claude renderer now supplies `"type": "command"` for flat entries
that the neutral hook grammar reads as command handlers. Explicit
handler types, untyped handlers already inside nested Claude groups,
non-command entries, and all other targets are unchanged.

Reinstall cleanup also matches the pre-fix untyped render, so stale
root-package entries keep healing instead of duplicating (the microsoft#1329 /
microsoft#1392 contract), mirroring the existing Codex legacy-content-key path.

Refs microsoft#3130

apm-spec-waiver: Claude-native handler type default for microsoft#3130; OpenAPM v0.1 defines no target-native hook handler fields, the portable hook subset is deferred to microsoft#2111 (out of approved scope), and req-lk-021 ownership reconciliation is preserved.
@Parth-Vasave

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@Parth-Vasave

Copy link
Copy Markdown
Author

Hi Daniel Meppiel (@danielmeppiel), one scope question carried over from #3130:

This PR adds type: "command" only to flat untyped command entries, both standalone and merged into
an existing Claude-shaped group. Handlers that already sit inside a nested Claude group with a
command but no type still render without type today.

The approved scope says "recognized command handlers", which could cover these too. Done-when only
mentions flat input, though. Should I include the nested case in this PR? It's a one-condition
change plus a regression test. Or should it stay out of scope?

I'm also happy to mark this ready for review whenever it suits you, so CI can run.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The codex case in test_claude_handler_default_does_not_reach_other_targets asserts '"type"' not in rendered, but Codex needs that field too: MatcherGroup.hooks is Vec<HookHandlerConfig>, an enum with #[serde(tag = "type")] (codex-rs/config/src/hook_config.rs in openai/codex), so an untyped handler fails to deserialize. Leaving Codex alone fits the approved scope, but this assertion pins the invalid output. Limiting the parametrization to cursor keeps the scope check without blocking a Codex follow-up.

Preserve current main changes while validating the bounded microsoft#3130 fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
@danielmeppiel

Daniel Meppiel (danielmeppiel) commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

APM Review Panel: prior ship_now; current readiness blocked

Blocked, not ready to ship. At published head 71fa67b6f5f6927fb49edd8e0099f60120a7c425, tests/integration/test_architecture_owner_rule_mutations.py::test_matrix_covers_every_registry_guard_exactly_once fails because registry-delegation-build-id-verification is registered without a matrix case. This is the reported main-b71 baseline defect; the matrix and all owner registry files match that main exactly. Passing scoped checks and remote CI do not override the failure. No baseline fix is authorized during release wind-down.

The previously executed architect, coverage and CEO delta panel at bbd89bf3e4ea5ff9ad59ea86f4e34a9203285b77 recommended ship_now. This update carries that actual review forward through a faithful merge of main b71cb47cd99e665d285a497ba6d05e810ae42ea4; it is not a new panel execution.

Thanks parth (@Parth-Vasave) for accepting the completed folds. No author rebase or additional push is requested. Original contributor commits are preserved, and the PR remains open and draft. No draft publication, PR merge or reviewer request was performed.

Bounded conflict recovery

  • GitHub had reported DIRTY; the actual local merge completed automatically with no unmerged paths.
  • Both changelog entries and the independent native-legacy and ownership-sidecar architecture checks were retained. The two parents of 71fa67b6f5f6927fb49edd8e0099f60120a7c425 are the reviewed head and main b71cb47cd99e665d285a497ba6d05e810ae42ea4.
  • The PR's production helpers, all regression tests and original static guard functions are unchanged. Main-only files match the landed main exactly. Prior panel/mutation evidence therefore remains applicable; no broad panel or new mutation program was run.
  • Only recognized untyped flat Claude command handlers gain type: command. Explicit types, nested groups, noncommands, other targets, ownership and consent remain unchanged. No unlanded fix(hooks): render nested hook metadata as plain JSON #3211 JSON-thaw work was imported and no actual Claude runtime rejection is claimed.
  • Push: git push https://github.com/Parth-Vasave/apm.git HEAD:refs/heads/fix/3130-claude-hook-handler-type (normal fast-forward, no force or history rewrite).

Exact-head validation

  • All seven current canonical lint gates plus architecture passed at 71fa67b6f5f6927fb49edd8e0099f60120a7c425.
  • 260 focused hook/architecture/real-CLI tests passed, including both standalone/merged CLI upgrades, the legacy-selector guard, and main's sidecar drift E2E scenario. All three canonical-owner functional mappings executed without skips at this exact head.
  • Canonical-owner v2 evidence was freshly detected against main b71cb47cd99e665d285a497ba6d05e810ae42ea4 and its three mapped functional tests passed. The completion is schema-valid blocked; terminal readiness verification is not claimed.
  • Six standard workflows completed success: CI, Merge Gate, CodeQL, docs, conformance, and NOTICE. One observer connection reset was retried; no workflow failed or was rerun.
  • Earlier local conformance (319 passed, 2 skipped), quality (64 passed), docs-link (14 passed), and five mutation-program results remain prior-head evidence, not new local runs. The genuine 25-line Mode B waiver remains unchanged.

Handoff

GitHub reports MERGEABLE / BLOCKED at 71fa67b6f5f6927fb49edd8e0099f60120a7c425: the branch conflict is gone, but the named matrix gate blocks readiness. The PR is intentionally still open and draft. Existing counters remain 2 outer iterations, 0 Copilot rounds and 0 CI recovery iterations. No shared-main fix, workflow rerun, merge, ready-marking or further PR work will be started. A future authorized repair of main's missing matrix case and revalidation remain unfinished.


Generated by autopilot-pr-merge-worker. This comment is AI-generated and may contain errors.

Fold panel coverage gaps for microsoft#3130: verify ownership sidecars, actual CLI install/reinstall and legacy root healing without executing hooks. Preserve non-target behavior without pinning invalid Codex output; add canonical-edge and explicit-type traps and clarify flat-only guidance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Keep the integrator below the canonical module limit by consolidating the duplicate legacy-key selection through the existing native renderer owner. Preserve Codex flat legacy matching and Claude untyped upgrade healing. Add a static boundary assertion and mutation-proven functional guardrail.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
@Parth-Vasave

Copy link
Copy Markdown
Author

Thanks Daniel Meppiel (@danielmeppiel) for the panel pass, and Mohammed Alkindi (@MohammedAlkindi) for the Codex catch.

I'll take the follow-ups myself in this PR:

  1. Sidecar assertions: extend test_reinstall_replaces_untyped_group_written_before_fix and test_stale_root_source_untyped_group_is_still_healed to also check .claude/apm-hooks.json, covering the typed content and the correct source marker.
  2. CLI lifecycle coverage: add a real apm install / reinstall test for untyped flat Claude hooks that compares the durable settings.json and sidecar snapshots.
  3. Codex assertion (Mohammed Alkindi (@MohammedAlkindi)): agreed, asserting no "type" in Codex output pins output Codex can't deserialize. I'll drop the codex case and keep the cursor one, so the Claude-only scope is still checked without blocking a Codex follow-up. Codex behaviour stays unchanged here, and I'm happy to open a separate issue for untyped Codex handlers if that's useful.

Per the panel, nested-group defaults stay out of scope, so the PR remains flat-input only.

I'll also rebase onto current main (only CHANGELOG.md conflicts) and push everything together. Daniel Meppiel (@danielmeppiel), could you hold the convergence pass on this branch so we don't overlap? Once it's pushed, could you approve the CI runs? They're all action_required at the moment.

@Parth-Vasave

Copy link
Copy Markdown
Author

Thanks Daniel Meppiel (@danielmeppiel), your convergence pass covered all three follow-ups, including a cleaner fix for Mohammed Alkindi (@MohammedAlkindi)'s Codex point, so I've dropped my local version and won't push over it. Happy to help with anything else before this comes out of draft.

Preserve the reviewed flat-only Claude repair and all landed main changes. The automatic merge retains both changelog entries and independent hook architecture guard extensions without changing regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a

This branch has not been deployed

No deployments
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] Flat hook entries without type reach .claude/settings.json without the required type: command

3 participants