Skip to content

fix(hooks): render nested hook metadata as plain JSON - #3211

Open
Abhishek Acharya (abhishekacharya200) wants to merge 1 commit into
microsoft:mainfrom
abhishekacharya200:fix-hook-metadata-json-rendering
Open

Abhishek Acharya (abhishekacharya200) wants to merge 1 commit into
microsoft:mainfrom
abhishekacharya200:fix-hook-metadata-json-rendering

Conversation

@abhishekacharya200

Copy link
Copy Markdown

Description

hook_contract._freeze turns hook metadata into MappingProxyType and tuples, all the way down. The native renderers in hook_native_formats.py only did dict(handler.metadata) and dict(binding.metadata), and returned raw entries as-is, so nested mappings such as env stayed frozen. json.dumps then failed during apm install / apm update with Object of type mappingproxy is not JSON serializable.

This adds _thaw next to _freeze in hook_contract.py, so the contract that freezes metadata also owns turning it back into plain JSON values. Mappings become new dicts, lists and tuples become new lists, and scalars pass through unchanged. hook_native_formats.py uses it for handler metadata, binding metadata and raw entries in both the nested and flat render paths. The IR stays frozen, and there is no default=str, no dropped metadata and no consent bypass.

Issue and approved scope

Issue: #3167

Human scope-approval comment: #3167 (comment)

This PR completes the approved scope.

Fixes #3167

Type of change

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

Testing

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

New tests in tests/integration/test_architecture_contract_guards.py, next to the existing native-edge tests:

  • JSON round trip for Claude, Codex, Gemini, and nested (PreToolUse) and flat (PreInvocation) Antigravity. Each one checks that nested env, arrays, extension metadata and scalars are kept, along with the command and the timeout unit.
  • Hook-group metadata and raw entries round trip, including mappings inside arrays.
  • Changing the rendered output does not change the frozen IR.
  • Gemini's in-place _copilot_keys_to_gemini conversion keeps env.

All 8 new tests fail on main with Object of type mappingproxy is not JSON serializable and pass with this change. ruff check, ruff format --check, scripts/check_test_assertions.py and scripts/check_exact_test_duplicates.py are clean. In pytest tests/unit tests/integration -k hook, 1136 tests pass. The 11 that fail also fail on main on my Windows machine (symlink and hook-execution tests).

CLI reproduction, without running any hooks. A local package has .apm/hooks/check.json containing {"hooks":{"PreToolUse":[{"type":"command","command":"echo check","env":{"APM_TEST":"value"}}]}}, and a project depends on it with targets: [claude, codex]:

# main
$ apm install
  [x] 1 package failed:
    +- ../hookpkg -- Failed to integrate primitives from local package: Object of type mappingproxy is not JSON serializable
[x] Installation failed with 1 error(s) in 0.1s. No install transaction changes were committed.

# this branch
$ apm install
[*] Installed 1 APM dependency in 0.2s.

With this branch, .claude/settings.json and .codex/hooks.json both contain the handler with "env": {"APM_TEST": "value"}.

The hooks guide now states that nested handler and hook-group fields such as env are written to native JSON unchanged. CHANGELOG.md has an entry under Unreleased > Fixed.

Spec conformance (OpenAPM v0.1)

  • N/A -- this PR does not change OpenAPM-observable behaviour.

hook_contract freezes hook metadata recursively, but the native renderers only copied the top level. Nested mappings such as env stayed MappingProxyType and json.dumps failed during install and update.

Add _thaw next to _freeze and use it for handler metadata, binding metadata and raw entries in the nested and flat renderers. The IR stays frozen; rendered output is an independent copy.

Fixes microsoft#3167

Copilot AI left a comment

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.

🟡 Changes recommended

The canonical thawing path lacks its required static architecture guard, and the changelog entry omits the PR reference.

2 open findings
What changed in this PR

Fixes nested hook metadata serialization while preserving immutable hook IR.

Changes:

  • Adds recursive metadata thawing at native render boundaries.
  • Adds regression coverage across supported renderers.
  • Updates documentation and changelog.
File Description
src/​apm_cli/​hook_contract.py Adds recursive metadata thawing.
src/​apm_cli/​integration/​hook_native_formats.py Thaws metadata before native rendering.
tests/​integration/​test_architecture_contract_guards.py Tests serialization and IR independence.
docs/​src/​content/​docs/​producer/​author-primitives/​hooks-and-commands.md Documents nested metadata preservation.
CHANGELOG.md Records the bug fix.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

return value


def _thaw(value: Any) -> Any:
Comment thread CHANGELOG.md

### Fixed

- `apm install` and `apm update` no longer fail with `Object of type mappingproxy is not JSON serializable` when a hook carries nested metadata such as `env`; Claude, Codex, Gemini and Antigravity hook files keep that metadata as plain JSON. (by @abhishekacharya200, closes #3167)
@abhishekacharya200

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@microsoft-github-policy-service

Copy link
Copy Markdown

Abhishek Acharya (@abhishekacharya200) the command you issued was incorrect. Please try again.

Examples are:

@microsoft-github-policy-service agree

and

@microsoft-github-policy-service agree company="your company"

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] APM 0.33.0: apm update fails to integrate Azure Skills because frozen hook metadata is not JSON serializable

2 participants