Skip to content

[BUG] Flat hook entries without type reach .claude/settings.json without the required type: command #3130

Closed

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-pkg
cd 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

Activity

  1. Parth-Vasave commented on Oct 1, 2026

    @Parth-Vasave
    Contributor

    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:

    • Scope: default type: "command" on flat hook handlers when rendering for the claude target, including when the
    • group is merged into an existing Claude-shaped file's PreToolUse groups.
    • Done when: .claude/settings.json handlers always carry type, with unit tests for both cases.
    • Out of scope: the Codex preToolUse event-map warning (separate issue).
  2. danielmeppiel commented on Oct 2, 2026

    @danielmeppiel
    Collaborator

    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.

  3. danielmeppiel commented on Oct 2, 2026

    @danielmeppiel
    Collaborator

    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.

  4. danielmeppiel commented on Oct 2, 2026

    @danielmeppiel
    Collaborator

    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.

  5. Parth-Vasave commented on Oct 2, 2026

    @Parth-Vasave
    Contributor

    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 without type.

    Plan, within the approved scope:

    • In Claude-native rendering only, supply type: "command" for recognized command handlers whose source omits type, including flat entries merged into existing Claude-shaped groups. The shared representation stays unchanged, so no default is imposed on other targets.
    • Preserve explicitly supplied handler types (command and non-command) and existing restrictions. Handlers that aren't recognizable as commands are left as they are.
    • Regression coverage for standalone conversion and for merging into an existing hook configuration.
    • Unrelated hooks, ownership, consent and other targets remain unchanged.
    • Update related guidance to reflect the supported input/output contract, starting with the hooks-and-commands passage the issue quotes.

    One question: a handler inside an already-nested Claude-shaped group that has a command but no type goes through the same Claude render path and also comes out without type today. Should it get type: "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.

  6. added a commit that references this issue on Oct 2, 2026
  7. Parth-Vasave commented on Oct 2, 2026

    @Parth-Vasave
    Contributor

    Hi Daniel Meppiel (@danielmeppiel), the draft PR is up: #3153

    It implements the approved Claude-only scope:

    • Untyped flat command entries now render with "type": "command" in .claude/settings.json, both standalone and
      when merged into an existing Claude-shaped group.
    • Explicit command and non-command types, the shared hook representation, and all other targets are unchanged.
    • Regression tests cover both cases, and the related docs and usage guide are updated.

    It also includes a small 6-line change in hook_integrator.py so reinstall cleanup still recognises entries
    written 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 if
    you'd like it.

    CI is waiting for maintainer approval to run (first-time contributor). Thanks!

  8. github-actions commented on Oct 6, 2026

    @github-actions

    Thanks to the reporter, and to @Parth-Vasave for taking this on and opening #3153. A maintainer-approved scope covers Claude-only rendering that supplies type: command for 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.

    Generated by Triage Panel · copilot · auto · 75.3 AIC · ⌖ 7.84 AIC · ⊞ 12K · ◷

  9. added
    area/multi-targetMulti-target deploy spec, target directory creation, agent surface routing.
    triage/recommendedAutomated advice completed; not human scope approval.
    type/bugSomething does not work as documented.
    on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area/multi-targetMulti-target deploy spec, target directory creation, agent surface routing.status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.triage/recommendedAutomated advice completed; not human scope approval.type/bugSomething does not work as documented.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions