Repository navigation
Description
Describe the bug
Some hook files use the flat entry shape without a type field. This shape is valid in Cursor, where type defaults to command. For such entries, the claude render wraps them into { "matcher", "hooks": [...] } but does not add "type": "command" to the handler. Claude Code's hooks reference (Hooks reference, code.claude.com/docs/en/hooks, as of 2026-10-01) lists type as required for every hook handler.
The docs say flat command entries "become Claude's required { "matcher": "*", "hooks": [...] } entries". The nesting happens, but the handler is incomplete.
To Reproduce
C:\work\demo-pkg\apm.yml
name: demo-pkg
version: 0.0.1
targets: [claude, codex, cursor]C:\work\demo-pkg\.apm\hooks\flat.json
{ "version": 1, "hooks": { "preToolUse": [ { "command": "node \"${PLUGIN_ROOT}/.apm/hooks/scripts/gate.mjs\"", "matcher": "Write", "timeout": 10 } ] } }C:\work\demo-pkg\.apm\hooks\scripts\gate.mjs
process.exit(0);C:\work\consumer\apm.yml
name: consumer
version: 0.0.1
targets: [claude, codex, cursor]
dependencies:
apm:
- path: ../demo-pkgcd C:\work\consumer
git init
apm install --target claude,codex,cursor
Actual .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/demo-pkg/.apm/hooks/scripts/gate.mjs\"",
"timeout": 10
}
]
}
]
}
}Expected behavior
The handler should get the default "type": "command":
{ "type": "command", "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/demo-pkg/.apm/hooks/scripts/gate.mjs\"", "timeout": 10 }The same happens when this file sits next to a Claude-shaped hook file in the same package. Its group is added to that file's PreToolUse groups, still without type.
Environment (please complete the following information):
- OS: Windows 11 Enterprise (10.0.26200)
- Python Version: N/A (standalone Windows binary from the release zip)
- APM Version: 0.32.0. 0.31.0 gives byte-identical output.
Additional context
- I did not run Claude Code against this file, so I have not confirmed the runtime effect. [BUG] apm install --target claude writes flat hook entries to .claude/settings.json (missing matcher/hooks nesting) #2062 reported that Claude Code skips the whole
settings.jsonwhen a hook entry fails schema validation ("Files with errors are skipped entirely"). - In the same run,
.codex/hooks.jsonreceivespreToolUseunchanged, with the warningHook events for target 'codex' may not be recognized: preToolUse. The docs say Copilot-cased events are renamed per target, but there is nocodexevent map. - Related: [BUG] apm install --target claude writes flat hook entries to .claude/settings.json (missing matcher/hooks nesting) #2062 / fix(hooks): normalize flat entries for Claude #2097 (flat entries normalised for Claude), [FEATURE] Define OpenAPM hook semantics and replace pairwise hook translation #2111.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
Parth-Vasave commentedon Oct 1, 2026
Hi, I'd like to take this one. I've reproduced the root cause from the report: the Claude render wraps flat entries into {matcher, hooks} but doesn't default the handler's type to "command".
Proposed scope, for a maintainer to confirm or adjust:
danielmeppiel commentedon Oct 2, 2026
Decision: approve
Area: project
Scope: In Claude-native rendering, supply type: command for recognized command handlers whose source omits type, including flat entries merged into existing Claude-shaped groups. Preserve explicitly supplied handler types and existing restrictions.
Done when: Applicable untyped flat command input produces schema-valid Claude command handlers; explicit command and non-command types remain intact; regression coverage includes standalone conversion and merging into an existing hook configuration. Unrelated hooks, ownership, consent and other targets remain unchanged, and related guidance reflects the supported input/output contract.
Out of scope: Blindly defaulting every untyped handler, changing the shared representation to impose a default on all targets, automatic Codex/Gemini changes, guessing non-command handler types, Codex event-name mapping, or the broader #2111 program.
Review contact: Daniel Meppiel (@danielmeppiel)
The official Claude hook reference marks the handler type as required. That is a schema requirement, not proof of an implicit Claude default or an independently demonstrated whole-file runtime rejection.
This confirms the bounded Claude-only repair proposed in the contributor discussion, including the merged-group case. Existing contributor interest is preserved; no implementer assignment or review schedule is created here.
Recorded by Copilot on behalf of Daniel Meppiel (@danielmeppiel) after his explicit "ok to all yes" confirmation of the four priority batches on 2026-10-02. This records the human-approved scope, not an autonomous recommendation. It does not assign implementation, promise a release, approve a merge, or replace required review.
danielmeppiel commentedon Oct 2, 2026
Decision: approve
Area: project
Scope: In Claude-native rendering, supply type: command for recognized command handlers whose source omits type, including flat entries merged into existing Claude-shaped groups. Preserve explicitly supplied handler types and existing restrictions.
Done when: Applicable untyped flat command input produces schema-valid Claude command handlers; explicit command and non-command types remain intact; regression coverage includes standalone conversion and merging into an existing hook configuration. Unrelated hooks, ownership, consent and other targets remain unchanged, and related guidance reflects the supported input/output contract.
Out of scope: Blindly defaulting every untyped handler, changing the shared representation to impose a default on all targets, automatic Codex/Gemini changes, guessing non-command handler types, Codex event-name mapping, or the broader #2111 program.
Review contact: Daniel Meppiel (@danielmeppiel)
Reason: Formatting-only republication of the unchanged human-approved scope at #3130 (comment); the original remains the rationale and context source. Recorded by Copilot on behalf of Daniel Meppiel (@danielmeppiel) following his explicit acceptance of these eight bounded scopes on 2026-10-02 and his subsequent instruction to start their delivery. No scope expansion, new product decision, assignment or merge approval.
danielmeppiel commentedon Oct 2, 2026
Hi parth (@Parth-Vasave), thanks for offering to take this on. We have now accepted the bounded Claude-only scope and are coordinating delivery without duplicating your work.
Could you share an active branch/PR or confirm whether you prefer to continue with support, collaborate on the fix, or hand it off?
Automated implementation of this issue is paused pending that coordination. An empty assignee list is not being treated as withdrawal of your earlier claim.
Recorded by Copilot on behalf of Daniel Meppiel (@danielmeppiel) while coordinating his requested delivery run.
Parth-Vasave commentedon Oct 2, 2026
Thanks Daniel Meppiel (@danielmeppiel)! I'd like to continue with support: I'll implement the fix myself, with you as review contact. No branch or PR yet. I'll open a draft PR by 2026-10-03, starting with the failing regression tests.
I've confirmed the bug on current
main: both the standalone flat case and the case where it's merged into an existing Claude-shaped group still render handlers withouttype.Plan, within the approved scope:
type: "command"for recognized command handlers whose source omitstype, including flat entries merged into existing Claude-shaped groups. The shared representation stays unchanged, so no default is imposed on other targets.One question: a handler inside an already-nested Claude-shaped group that has a
commandbut notypegoes through the same Claude render path and also comes out withouttypetoday. Should it gettype: "command"too, or only flat input? The Scope line covers any recognized command handler, while Done-when only mentions flat input. I'll limit it to flat input unless you say otherwise.Noted on the evidence point: I'll describe this as making the output schema-valid rather than claiming a confirmed runtime rejection. I'll link the scope record in the PR.
docs(changelog): cite microsoft#3153 in the microsoft#3130 hook entry
Parth-Vasave commentedon Oct 2, 2026
Hi Daniel Meppiel (@danielmeppiel), the draft PR is up: #3153
It implements the approved Claude-only scope:
"type": "command"in.claude/settings.json, both standalone andwhen merged into an existing Claude-shaped group.
commandand non-command types, the shared hook representation, and all other targets are unchanged.It also includes a small 6-line change in
hook_integrator.pyso reinstall cleanup still recognises entrieswritten by earlier versions; without it, stale root-package entries would be left behind as untyped duplicates. The
PR description explains this in detail.
One question from my earlier comment is still open: should untyped handlers already inside nested Claude groups
also get
type: "command"? The PR currently handles flat input only; extending it is a one-condition change ifyou'd like it.
CI is waiting for maintainer approval to run (first-time contributor). Thanks!
github-actions commentedon Oct 6, 2026
Thanks to the reporter, and to
@Parth-Vasavefor taking this on and opening #3153. A maintainer-approved scope covers Claude-only rendering that suppliestype: commandfor recognized untyped command handlers, including merges into existing groups. Your open question (untyped handlers already inside nested Claude groups) still needs a maintainer answer; the approved scope text says 'recognized command handlers', so it is worth confirming explicitly on the PR. This is automated advice only; it is not scope approval, an assignment or a release commitment.Generated by autopilot-issue-triage-worker. This comment is AI-generated and may contain errors.
Merge current main into Claude handler typing fix
test(hooks): prove Claude typing through install and upgrade
fix(hooks): type untyped flat command handlers for Claude (#3153)