Skip to content

[Hooks] SubagentStart and SubagentStop are not dispatched for project-defined custom subagents on Windows #33097

Open

Description

@cufudjxnxjxj-art

What version of Codex CLI is running?

codex-cli 0.144.1 and codex-cli 0.144.4

What subscription do you have?

Not applicable to this probe (custom Responses-compatible API provider).

Which model were you using?

gpt-5.6-sol (custom Responses-compatible API provider)

What platform is your computer?

Microsoft Windows NT 10.0.19043.0 x64 (Windows 10 Pro, build 19043)

What terminal emulator and version are you using (if applicable)?

Windows PowerShell 5.1.19041.1023

Codex doctor report

{
  "status": "not_collected",
  "reason": "The completed isolated probes did not collect a Codex doctor report; no additional probe was run."
}

What issue are you seeing?

Trusted and active SubagentStart and SubagentStop command hooks are not invoked when a real project-defined custom subagent starts and completes. The custom subagent itself is created successfully and returns the expected result, but the hook handler process is never launched.

This was reproduced in three isolated repositories/runs with Codex CLI 0.144.1 and 0.144.4. The final 0.144.4 run used the wildcard matcher .*, so the result is not explained by the earlier exact matcher.

The current Codex Hooks documentation documents both lifecycle events, says their matcher is applied to the subagent type, and states that project-local hooks load for trusted projects after the hook definitions are reviewed and trusted.

The custom agent starts, returns exactly HOOK_PROBE_WORKER_COMPLETE, and completes, but every real run records:

  • SubagentStart handler process launches: 0
  • SubagentStop handler process launches: 0
  • raw lifecycle payload rows: 0
  • hook command errors shown by Codex: none

The handler writes and flushes a process-start record before reading or parsing stdin. Therefore, zero process-start rows means the configured command handler was not launched; this is not merely a payload parsing failure.

Because no real hook payload exists, the opaque runtime agent_id, payload agent_type, parent session_id, transcript_path, and agent_transcript_path are unavailable. The selected project profile is known to be hook_probe_worker, but the payload's runtime agent_type is not inferred or invented.

Codex_Subagent_Hook_Issue_Package.zip

What steps can reproduce the bug?

Sanitized equivalent of the tested configuration

The tested fixture contained both command and commandWindows, with the absolute handler path quoted because the real path contained spaces.

.codex/hooks.json:

{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "\"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStart",
            "commandWindows": "\"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStart",
            "timeout": 10
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "\"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStop",
            "commandWindows": "\"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStop",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

.codex/agents/hook_probe_worker.toml:

name = "hook_probe_worker"
description = "Read-only native lifecycle Hook probe."
sandbox_mode = "read-only"

developer_instructions = """
Do not read any file.
Do not execute any command.
Do not write any file.
Do not create or invoke another agent.
Do not call any tool.
Return exactly this single line and nothing else:
HOOK_PROBE_WORKER_COMPLETE
"""

The attached raw_hook_probe.py writes handler_process_start.jsonl before it reads stdin, then records the raw input and JSON parse status in raw_hook_invocations.jsonl. The .cmd wrapper invokes that script with the event name.

Reproduction steps

  1. Create an isolated Git repository on Windows.
  2. Add the custom agent, lifecycle hooks, and recorder from the sanitized fixture.
  3. Replace the placeholder with the quoted absolute path to raw_hook_probe.cmd.
  4. Start Codex from the repository root with an isolated CODEX_HOME.
  5. Trust the repository.
  6. Review and trust both hooks in /hooks.
  7. Confirm both hooks are active and have no pending review.
  8. Start exactly one project custom agent of type hook_probe_worker.
  9. Wait for it to return HOOK_PROBE_WORKER_COMPLETE.
  10. Check evidence/real/handler_process_start.jsonl and evidence/real/raw_hook_invocations.jsonl.

Do not pre-create the two evidence files. If the handler launches, it creates them itself.

What is the expected behavior?

When hook_probe_worker starts, Codex should launch the matching SubagentStart command hook and send the lifecycle JSON object to its stdin.

When the same subagent completes, Codex should launch the matching SubagentStop command hook and send the corresponding lifecycle JSON object to its stdin.

Additional information

Environment

  • OS: Microsoft Windows 10 Pro, 64-bit, version 10.0.19043 (build 19043)
  • Shell: Windows PowerShell 5.1.19041.1023
  • Codex CLI versions tested: 0.144.1 and 0.144.4
  • 0.144.4 executable SHA-256: 51398051C2332B6AFE08DC3B9DBB4056085C197F35CA57A307EE303D450CADA5
  • Model: gpt-5.6-sol
  • Model provider: custom Responses-compatible API provider
  • Custom agent profile: hook_probe_worker
  • Hook source: project-local .codex/hooks.json
  • Repository trust: confirmed in the UI
  • Hook trust: both hooks manually reviewed, trusted, and shown as active
  • Windows sandbox: initialized successfully

The custom provider may be relevant, but custom-agent creation and completion worked normally in every run. No claim is made that the provider is or is not causal.

Reproduction matrix

Codex version Matcher Agent completed SubagentStart SubagentStop Handler launches
0.144.1 ^hook_probe_worker$ Yes No No 0
0.144.4 ^hook_probe_worker$ Yes No No 0
0.144.4 .* Yes No No 0

Each row represents exactly one real custom-agent run. No additional agent was started or resumed in a run.

Controls and causes already checked

  • The custom agent was selected and returned the exact expected result.
  • The repository and both hook definitions were trusted, and the hooks were shown as active.
  • The 0.144.4 wildcard run rules out the tested exact matcher as the explanation.
  • The command used a quoted absolute Windows path.
  • The same .cmd wrapper and Python recorder launch successfully with controlled synthetic lifecycle input.
  • The recorder logs process startup before stdin parsing, so malformed or missing JSON would still produce a process-start row.
  • The real runs showed no hook launch error.
  • The probes were isolated from the production project integration.

Current diagnosis boundary

The first unobserved boundary is upstream of the handler process. The evidence narrows the issue to one of these areas but does not distinguish between them:

  1. native subagent lifecycle event generation/dispatch; or
  2. loading or inheritance of trusted project-local lifecycle hooks for the custom-agent runtime.

The evidence does not establish a source-level root cause, and it does not establish that the custom model provider is irrelevant.

The unresolved boundary is runtime lifecycle dispatch or project-hook inheritance/loading.

Related but not duplicate: #21753 is an umbrella Hook parity tracker; #26383 reports all repo hooks missing under codex exec 0.137.0 on macOS. This report is specifically about trusted and Active custom-subagent lifecycle hooks in the interactive native Windows CLI across 0.144.1 and 0.144.4.

Questions

  1. Are SubagentStart and SubagentStop expected to run for project-defined custom subagents on native Windows Codex CLI?
  2. Should trusted project-local hooks participate in custom-subagent lifecycle dispatch?
  3. Is lifecycle hook dispatch intended to be independent of the configured model provider?
  4. Is there a supported runtime trace or developer logging option that shows event generation, matcher evaluation, and hook loading for these events?

Sanitized attachments

  • minimal wildcard fixture (.codex/hooks.json, custom-agent TOML, .cmd wrapper, and Python recorder)
  • three-run comparison
  • result summaries and SHA-256 references for the original local evidence archives
  • no API keys, authentication files, cookies, sessions, transcripts, CODEX_HOME, or unrelated project files

Attachment SHA-256: BC900DA45B9C57B0EE1E2CF750FF7B665D11533921791B0FBB749CD3FF6AA1E7.

Activity

  1. added
    CLIIssues related to the Codex CLI
    windows-osIssues related to Codex on Windows systems
    hooksIssues related to event hooks
    subagentIssues involving subagents or multi-agent features
    on Jul 14, 2026
  2. abhinav-oai commented on Jul 14, 2026

    @abhinav-oai
    Contributor

    Thanks for the detailed report and the excellent reproduction package. The issue here is that the native Windows environment uses PowerShell to run commandWindows; it is not necessarily run through cmd.exe. PowerShell needs the & invocation operator when calling a quoted path with spaces.

    Could you update the two hooks as follows?

    "commandWindows": "& \"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStart"
    "commandWindows": "& \"C:\\absolute path\\fixture_repo\\hooks\\raw_hook_probe.cmd\" SubagentStop"

    Then re-trust the updated hooks with /hooks and retry the reproduction. The .cmd wrapper itself can remain unchanged.

    We’ll improve the documentation around which shell interprets Windows hook commands, since this distinction is easy to miss. Thanks again for flagging it!

  3. GordonFP commented on Aug 4, 2026

    @GordonFP

    Cross-platform adjacent reproduction and config-axis isolation on macOS.

    Environment:

    • macOS (Apple Silicon)
    • Codex CLI 0.146.0
    • interactive native multi-agent runtime
    • hooks injected through CLI session flags with --dangerously-bypass-hook-trust, while user config was ignored
    • command hook path contained no spaces

    Observed A/B result:

    • default subagent: SubagentStart = 1, SubagentStop = 1
    • task_name-only subagent: SubagentStart = 1, SubagentStop = 1
    • named project-defined agent with a non-empty runtime role layer: SubagentStart = 0, SubagentStop = 0

    Config-axis isolation:

    • metadata-only role (name + description): both hooks fired
    • role with model + reasoning override: neither hook fired
    • role with sandbox override only: neither hook fired
    • role with developer_instructions only: neither hook fired

    All custom agents started, returned the requested marker, and completed normally. The recorder writes a process-start row before parsing stdin, so zero rows means no executable handler was registered/launched.

    After source inspection, this macOS variant has a narrower likely cause than lifecycle dispatch or Hook-definition loss:

    1. A non-empty role layer calls reload::build_next_config in codex-rs/core/src/agent/role.rs.
    2. Config::bypass_hook_trust is runtime-only and comes from ConfigOverrides.
    3. role.rs::reload_overrides does not preserve bypass_hook_trust; ..Default::default() resets it to false.
    4. Hook discovery still sees the inherited session-flags Hook definitions, but the command handlers fail the trust gate and are not registered.

    Candidate minimal fix:

    bypass_hook_trust: config.bypass_hook_trust.then_some(true),

    in role.rs::reload_overrides, plus a unit test that applies a non-empty role and asserts the runtime override remains true, and an integration test asserting one start and one stop invocation.

    I validated the minimal source fix locally against current official main: a new unit regression test failed before the change because applying the role changed bypass_hook_trust from true to false; after adding the line above, the same test passed (1 passed, 0 failed). This proves the config-state loss and the narrow field-preservation fix at unit level. A full lifecycle integration test is still recommended before merge.

    Immediate workaround for users: define the Hook in project/user configuration and persistently review/trust it through /hooks, instead of relying on invocation-only bypass. A metadata-only role also avoids the rebuild, but loses role-specific runtime settings.

    Scope clarification: this does not prove the Windows report has the same root cause. The original fixture used persisted project Hook trust, so the PowerShell & explanation may still apply there. This result establishes a separate cross-platform named-role/trust-inheritance defect with the same visible symptom.

  4. vedmalex commented on Aug 6, 2026

    @vedmalex

    Independent confirmation on macOS, Codex CLI 0.146.1 — same root cause as @GordonFP's 2026-08-04 comment above.

    Setup: a project-local .codex/hooks.json with SubagentStop (matcher: "", wildcard) plus a custom agent TOML that sets both model and sandbox_mode. The framework this feeds is a DA-review gate: a spawned reviewer subagent must return a verdict, and the parent captures it via SubagentStop.

    Symptom: spawn_agent(agent_type="<custom-role>", fork_turns="none") completes normally (the subagent's own session transcript shows a well-formed final message), but the configured SubagentStop command handler is never launched — confirmed with an unconditional trace statement at the very top of our own handler process (before any argument parsing), across multiple runs: zero invocations. The same hooks.json's Stop handler (root session) fires correctly in the same run.

    Diagnosis matches exactly: the custom agent role carries model/sandbox_mode, which triggers core/src/agent/role.rs's apply_role_to_config → reload::build_next_config. That rebuilds the child session's Config via ConfigOverrides { .., ..Default::default() } (role.rs::reload_overrides), dropping the runtime-only bypass_hook_trust (defaults to false per core/src/config/mod.rs). The child session's hooks engine (session/session.rs → hooks/src/engine/discovery.rs) then silently drops the untrusted SubagentStop handler — no error, no warning, no diagnostic of any kind.

    Also confirmed: --dangerously-bypass-hook-trust on the invoking session does not help here either, since it's the same runtime-only override that gets dropped on the role-triggered rebuild.

    Workaround (matches what's proposed above): persist hook trust in the config layer stack instead of relying on the session-level flag — write [hooks.state] entries with each handler's trusted_hash into ~/.codex/config.toml (obtainable non-interactively via codex app-server's hooks/list RPC, or interactively via /hooks). Trust state in the layer stack survives the role rebuild; the ephemeral CLI flag does not. After applying this, SubagentStop fired correctly and the subagent's verdict was captured end-to-end.

    +1 for @GordonFP's proposed fix (bypass_hook_trust: config.bypass_hook_trust.then_some(true) in role.rs::reload_overrides) — our independent repro matches the exact mechanism, just triggered by a different combination of role-config fields (model + sandbox_mode rather than whatever combination was tested there).

    One severity note beyond the original report: because the failure is completely silent (no hook error, no log line, nothing in codex exec console output — child-session hook activity isn't echoed to the parent's terminal output at all), any automation relying on SubagentStop for a spawned, role-configured agent will silently lose data with no indication anything went wrong. That silence seems worth its own follow-up (e.g. a warning when a hook is skipped due to untrusted state), separate from the config-inheritance fix itself.

  5. mattglass commented on Aug 10, 2026

    @mattglass

    Independent confirmation on macOS (Intel) with Codex CLI 0.146.0.

    I reproduced the custom-agent hook-trust inheritance issue in an isolated trusted repository.

    Observed matrix:

    • Root SessionStart with --dangerously-bypass-hook-trust: delivered.
    • Default subagent SubagentStart with the same bypass: delivered as agent_type=default.
    • Custom TOML agent with the bypass: hook silently skipped.
    • The same custom agent with persisted exact-hook trust: SubagentStart delivered with the exact hyphenated agent_type.

    The custom agent launched and returned successfully in every case. This isolates the failure from custom-agent naming, matcher syntax, project trust, and general hook execution.

    Source inspection of rust-v0.146.0 is consistent with the diagnosis already reported here: applying a non-empty custom-agent role rebuilds the child configuration through role.rs::reload_overrides, but that rebuild does not preserve config.bypass_hook_trust. Persisted hook trust remains in the configuration layer stack and survives the rebuild, which explains the successful workaround.

    Immediate workaround confirmed: review and persistently trust the hook through /hooks instead of relying on --dangerously-bypass-hook-trust.

    Suggested regression coverage: one end-to-end custom agent_type case for both SubagentStart and SubagentStop, plus a config-level assertion that the invocation-scoped bypass survives the role rebuild.

  6. sudo-vaibhav commented on Aug 23, 2026

    @sudo-vaibhav

    bumping this up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workinghooksIssues related to event hookssubagentIssues involving subagents or multi-agent featureswindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions