Repository navigation
Description
Describe the bug
Some hook files use the flat entry shape without a type field (valid in Cursor, where type defaults to command). For such entries, the codex render wraps them into { "matcher", "hooks": [...] } groups (the native shape from #3059 / #3060) but does not add "type": "command" to the handler.
Codex's hook schema requires that field. In openai/codex, MatcherGroup.hooks is Vec<HookHandlerConfig>, and HookHandlerConfig is an internally tagged enum (#[serde(tag = "type")]) with command, mcp_tool and prompt variants and no default (codex-rs/config/src/hook_config.rs). An untyped handler therefore does not match any variant. Mohammed Alkindi (@MohammedAlkindi) pointed this out in a review of #3153.
This is the Codex counterpart of #3130. The Claude fix in #3153 deliberately leaves Codex unchanged, because #3130's approved scope excludes automatic Codex changes.
To Reproduce
demo-pkg/apm.yml
name: demo-pkg
version: 0.0.1
targets: [codex]demo-pkg/.apm/hooks/flat.json (PascalCase event, so only the missing type is in play)
{ "version": 1, "hooks": { "PreToolUse": [ { "command": "node \"${PLUGIN_ROOT}/.apm/hooks/scripts/gate.mjs\"", "matcher": "Write", "timeout": 10 } ] } }demo-pkg/.apm/hooks/scripts/gate.mjs
process.exit(0);consumer/apm.yml
name: consumer
version: 0.0.1
targets: [codex]
dependencies:
apm:
- path: ../demo-pkgcd consumer
mkdir .codex
apm install
Actual .codex/hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"command": "node \".codex/hooks/demo-pkg/.apm/hooks/scripts/gate.mjs\"",
"timeout": 10
}
]
}
]
}
}Expected behavior
The handler should get "type": "command", as Codex's schema requires:
{ "type": "command", "command": "node \".codex/hooks/demo-pkg/.apm/hooks/scripts/gate.mjs\"", "timeout": 10 }Explicitly typed handlers should stay as authored.
Environment (please complete the following information):
- OS: macOS 27.0.1
- Python Version: 3.12.12
- APM Version: 0.33.0 (source checkout of
mainat6333b56a; also reproduced on the fix(hooks): type untyped flat command handlers for Claude #3153 headbbd89bf3)
Logs
Install succeeds with no warning about the handler:
[i] Targets: codex (source: apm.yml)
[+] ../demo-pkg (local)
|-- 1 hook(s) integrated -> .codex/hooks.json
| PreToolUse: runs .codex/hooks/demo-pkg/.apm/hooks/scripts/gate.mjs (flat.json)
[*] Installed 1 APM dependency in 0.1s.
Additional context
- This is a schema finding from Codex's source. I have not separately confirmed how a Codex runtime reports or handles the invalid entry.
- Related: [BUG] Flat hook entries without
typereach.claude/settings.jsonwithout the requiredtype: command#3130 / fix(hooks): type untyped flat command handlers for Claude #3153 (same gap for Claude, fixed Claude-only), [BUG] Codex hooks miss the required nested hooks level #3059 / fix(hooks): render Codex hooks in native shape #3060 (Codex nestedhookslevel, where every example was already typed), [FEATURE] Define OpenAPM hook semantics and replace pairwise hook translation #2111 (broader hook semantics work). - Separate from this issue: with a camelCase event such as
preToolUse(as in [BUG] Flat hook entries withouttypereach.claude/settings.jsonwithout the requiredtype: command#3130's repro), the Codex render also keeps the event name and warns that Codex expects PascalCase. That event-name mapping was out of [BUG] Flat hook entries withouttypereach.claude/settings.jsonwithout the requiredtype: command#3130's scope, and I have not included it here.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
Parth-Vasave commentedon Oct 16, 2026
Hi, I'd like to take this one. I fixed the Claude counterpart in #3153, so I know this render path.
Proposed scope, for a maintainer to confirm or adjust:
type: "command"for recognized command handlers whose flat source entry omitstype, mirroring the Claude-only approach in fix(hooks): type untyped flat command handlers for Claude #3153. Preserve explicitly supplied handler types, and leave untyped entries that aren't recognizable as commands unchanged..codex/hooks.json. Explicit types stay intact. Reinstalling over hooks written by earlier versions converges to one typed entry without leaving untyped duplicates. Regression tests cover these cases, and the hooks guidance reflects the Codex input/output contract. Claude, Cursor and other targets stay unchanged.preToolUse), changing the shared hook representation to impose a default on every target, guessing non-command handler types, and the broader [FEATURE] Define OpenAPM hook semantics and replace pairwise hook translation #2111 work.Two questions:
As with #3130, I'll describe the result as schema-valid output rather than claim a confirmed Codex runtime rejection.
danielmeppiel commentedon Oct 21, 2026
Decision: approve
Area: project
Scope: In Codex-native rendering, supply type: command when converting recognized command handlers whose flat source entries omit type, including merging converted entries into existing groups. Reuse the existing native-rendering and ownership mechanisms; preserve explicit types, non-command input, matchers, timeouts, restrictions, consent, supporting resources and other targets. Reconcile proven APM-owned older output to the corrected form without stale untyped duplicates.
Done when: Installed-CLI first-install, reinstall and update coverage demonstrates one correctly typed Codex command entry, with regression coverage for both older flat and currently generated nested-untyped APM-owned output. Explicit types, non-command and already-nested source handlers remain unchanged; manual, unowned or ambiguously owned hooks are not rewritten or removed. Verify the pinned native schema, preserve denied-consent behavior and source files, and do not execute hooks during installation. Update directly related hook documentation and usage guidance. Describe generated-schema correctness separately from any independently obtained native runtime evidence.
Out of scope: Defaulting types inside already-nested source handlers, event-name mapping, a shared-representation default across targets, guessing non-command types, new targets, broader #2111 semantics, frontmatter-hook expansion, module-packaging changes, a competing contribution, assignment, technical PR approval, release commitment, automation dispatch or restart of blocked delivery.
Review contact: Daniel Meppiel (@danielmeppiel)
danielmeppiel commentedon Oct 21, 2026
Accepted: correct the Codex output APM generates from simple flat command entries, and repair older output that APM can prove it owns.
For example, when an author supplies
{"command": "python check.py"}, APM already wraps it in Codex's nested hook structure. That generated handler also needs"type": "command". Reinstall/update must replace the earlier incomplete APM-owned entry, not leave both versions behind.parth (@Parth-Vasave), this preserves your existing claim and answers the two scope questions:
The distinction is the input shape, not whether the final output is nested. Replacing an older nested entry generated by APM from flat input is included when ownership is established; rewriting a manually authored or ambiguously owned entry is not.
The October 20 investigation ran the actual installed Python CLI for first and repeat local-package installs at
53ccf06d11dd2d675e8ea8c3e2523a6d321c1b79. Untyped flat input remained untyped for Codex but gainedcommandfor Claude; explicit types, a manual hook and source bytes were preserved, and a sentinel confirmed the fixture hook did not execute. The native renderer is byte-identical at the October 21 main snapshot9ff06af17bdec985f07a5b8565344ac155a29490. Other install/resource-path code has advanced; the full lifecycle was not rerun at that newer revision.The inspected upstream Codex schema requires the
typediscriminator. This is generated-output and source-schema evidence, not an observed Codex runtime rejection or proof of repaired historical migration.The accepted tradeoff is deliberate: already-structured source with a missing type remains an authoring error rather than receiving automatic correction. No event-name conversion, broad hook semantics, frontmatter expansion or packaging change is included. #3130's Claude-only acceptance and the separate consent/module-metadata work remain unchanged.
The adjacent strict scope defines completion. The issue remains open with labels and assignees unchanged; the existing contributor claim is preserved without a new assignment. This product decision does not approve a PR, promise a release or dispatch automated implementation.
Prepared and published with AI assistance following the maintainer's explicit narrow-scope confirmation. Evidence and its limits are described above.
Parth-Vasave commentedon Oct 25, 2026
Thanks for confirming the scope, Daniel Meppiel (@danielmeppiel). I'm starting on this now and will build on the renderer and ownership code merged in #3153:
typeget"type": "command"in.codex/hooks.json, including when they merge into existing groups. Already-nested source handlers, explicit types and non-command entries stay unchanged.I've confirmed against the pinned Codex source (
36512a34,hook_config.rs) thatHookHandlerConfigis#[serde(tag = "type")]with no default.