Skip to content

fix: report from a Codex session on Windows - #17

Merged
omartelo merged 1 commit into
mainfrom
fix/codex-hooks-on-windows
Aug 13, 2026
Merged

omartelo merged 1 commit into
mainfrom
fix/codex-hooks-on-windows

Conversation

@omartelo

@omartelo omartelo commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator

A Codex session on Windows failed every hook this plugin registers — hook exited with code 1 on SessionStart, UserPromptSubmit and Stop, plus hook returned invalid stop hook JSON output on top of it. Nothing reached lich, and the errors sat in the user's transcript.

Two causes, both on this side.

The plugin root was spelled the one way Codex does not expand. Codex substitutes ${KEY} and nothing else before handing the line to a shell (discovery.rs):

let command = source.env.iter().fold(command, |command, (key, value)| {
    command.replace(&format!("${{{key}}}"), value)
});

hooks/codex-hooks.json used the bare $CLAUDE_PLUGIN_ROOT. On Unix that survives by accident — hooks go to $SHELL -lc, and the shell expands what Codex left alone. On Windows they go to cmd.exe /C, which does not, so every hook ran a path that does not exist. Fixed by using the braced form, the one hooks/hooks.json has always used.

cmd.exe cannot execute a .sh. Each hook now also registers a commandWindows handing the same script and the same argument to hooks/win-run.cmd, which looks for Git Bash — on PATH, then at its default install — and always exits 0, bash or no bash. Codex picks commandWindows over command on Windows and ignores it everywhere else, so one registration still serves both.

The invalid stop hook JSON output line was the second symptom of the first cause: cmd.exe writes its error to stdout, and Codex reads a Stop hook's stdout as a decision. The wrapper drops output for that reason too.

Nothing about a Unix run changes.

Test plan

  • node --test tests/*.test.mjs — 129 pass
  • Two new cases: codex-hooks.json may not carry a bare $CLAUDE_PLUGIN_ROOT, and every hook's commandWindows must wrap its command through win-run.cmd

Not in this PR

docs/providers.md still says Codex has no SessionEnd. It has one now — the event is in the current HookEventsToml — so the idle report may already be firing. Left alone until it is verified against a real session.

Every hook failed there: `hook exited with code 1` on each event, plus
`invalid stop hook JSON output`, because cmd.exe writes its own error to
stdout and Codex reads a Stop hook's stdout as a decision.

Two causes, both on this side. Codex substitutes `${KEY}` and nothing
else before handing the line to a shell, so the bare $CLAUDE_PLUGIN_ROOT
the Codex registration used only worked because the Unix shell expanded
what Codex had left alone -- `cmd.exe /C` does not. And cmd.exe cannot
execute a .sh at all.

The registration now spells the braced form, the one the Claude Code
registration has always used, and each hook gained a `commandWindows`
handing the same script and the same argument to hooks/win-run.cmd,
which finds Git Bash -- on PATH, then at its default install -- and
exits 0 whether or not it does. Codex prefers `commandWindows` on
Windows and ignores it elsewhere, so one registration still serves both
and nothing about a Unix run changes.
@omartelo
omartelo merged commit 683ea98 into main Aug 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant