Repository navigation
fix(hooks): render nested hook metadata as plain JSON - #3211
Open
Abhishek Acharya (abhishekacharya200) wants to merge 1 commit into
Open
Abhishek Acharya (abhishekacharya200) wants to merge 1 commit into
Abhishek Acharya (abhishekacharya200) wants to merge 1 commit into
Conversation
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 started reviewing on behalf of
Abhishek Acharya (abhishekacharya200)
October 12, 2026 04:52
View session
Contributor
There was a problem hiding this comment.
🟡 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: |
|
|
||
| ### 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) |
Author
|
@microsoft-github-policy-service agree |
|
Abhishek Acharya (@abhishekacharya200) the command you issued was incorrect. Please try again. Examples are: and |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
hook_contract._freezeturns hook metadata intoMappingProxyTypeand tuples, all the way down. The native renderers inhook_native_formats.pyonly diddict(handler.metadata)anddict(binding.metadata), and returned raw entries as-is, so nested mappings such asenvstayed frozen.json.dumpsthen failed duringapm install/apm updatewithObject of type mappingproxy is not JSON serializable.This adds
_thawnext to_freezeinhook_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.pyuses it for handler metadata, binding metadata and raw entries in both the nested and flat render paths. The IR stays frozen, and there is nodefault=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
Testing
New tests in
tests/integration/test_architecture_contract_guards.py, next to the existing native-edge tests:PreToolUse) and flat (PreInvocation) Antigravity. Each one checks that nestedenv, arrays, extension metadata and scalars are kept, along with the command and the timeout unit._copilot_keys_to_geminiconversion keepsenv.All 8 new tests fail on
mainwithObject of type mappingproxy is not JSON serializableand pass with this change.ruff check,ruff format --check,scripts/check_test_assertions.pyandscripts/check_exact_test_duplicates.pyare clean. Inpytest tests/unit tests/integration -k hook, 1136 tests pass. The 11 that fail also fail onmainon my Windows machine (symlink and hook-execution tests).CLI reproduction, without running any hooks. A local package has
.apm/hooks/check.jsoncontaining{"hooks":{"PreToolUse":[{"type":"command","command":"echo check","env":{"APM_TEST":"value"}}]}}, and a project depends on it withtargets: [claude, codex]:With this branch,
.claude/settings.jsonand.codex/hooks.jsonboth contain the handler with"env": {"APM_TEST": "value"}.The hooks guide now states that nested handler and hook-group fields such as
envare written to native JSON unchanged.CHANGELOG.mdhas an entry under Unreleased > Fixed.Spec conformance (OpenAPM v0.1)