Skip to content

test(agent-hooks): guard the Windows direct-spawn hook launcher contract - #11081

Closed
OrcaWin wants to merge 1 commit into
stablyai:mainfrom
OrcaWin:guard-8430-direct-spawn
Closed

OrcaWin wants to merge 1 commit into
stablyai:mainfrom
OrcaWin:guard-8430-direct-spawn

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Jul 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Locks the invariant behind #8430/#8737: Codex, Antigravity and Devin hand the managed hook command to CreateProcess as argv[0] — not to cmd.exe — so it must stay a single spawnable token naming a real .cmd. Anything else is unspawnable and fails every lifecycle hook with exit 1.

Test-only. No production code changes.

The gap

Nothing currently guards which wrapper each service picks:

  • installer-utils.test.ts tests wrapWindowsCmdHookCommand directly, so it stays green no matter which wrapper a service calls.
  • The Codex-side check that would catch it is it.skipIf(process.platform !== 'win32'), and pr.yml runs unit tests on ubuntu-latest only — so it never executes in PR CI.

Net effect: a service switching from wrapWindowsCmdHookCommand to wrapWindowsHookCommand reintroduces #8430 with fully green CI.

This surfaced while evaluating #8665, which proposed exactly that swap for Codex to fix #8645. #8645 was already fixed by #8737 via the bare-path form (user-confirmed on Orca 1.4.141), so that swap would have been a pure regression.

Approach

Rather than skipping on non-Windows, this mocks the managed script path (and the disk write) while leaving the real wrappers in play. The platform-sensitive input is the only thing stubbed, so the win32 launcher contract is asserted deterministically on Linux CI.

Per agent, the command is taken from the narrowest available seam — getCodexManagedHookInstallMaterial(), getDevinManagedCommand(), and Antigravity's installed hooks.json.

Each direct-spawn agent asserts the command is the bare managed .cmd path, is a single argv[0] token, and is not a shell launcher. A shell-launched control (Cursor) asserts the complement, so the split is pinned in both directions and "make every agent spawnable" can't pass as a valid refactor.

Verification

Each assertion was proven non-vacuous by swapping wrappers locally (reverted, not committed):

Change simulated Result
Codex → wrapWindowsHookCommand (the #8665 change) FAILS
Antigravity → wrapWindowsHookCommand FAILS
Devin → wrapWindowsHookCommand FAILS
Cursor → wrapWindowsCmdHookCommand (reverse) FAILS
unmodified main 4 passed

Also confirmed empirically on Windows via System.Diagnostics.Process with UseShellExecute=false (CreateProcess, no shell): the bare .cmd path spawns and exits 0, while the encoded PowerShell launcher fails to spawn with "The system cannot find the file specified". Note that a Node-based spawnSync check is misleading here — Node refuses .cmd with shell:false since the CVE-2024-24576 guard, so it errors for both forms.

  • pnpm typecheck — clean
  • oxlint on the new file — clean
  • npx vitest run src/main/agent-hooks/windows-direct-spawn-launcher.test.ts — 4 passed
  • Affected suites (agent-hooks/, codex, antigravity, devin, cursor): 5 failures, all pre-existing — an identical control run on unmodified origin/main with this file removed produced the same 5 (Windows-local symlink-permission and POSIX-script assertions; they pass on Linux CI).

Linux execution is confirmed by this PR's own CI rather than assumed: shard tests 4/16 (ubuntu) logs ✓ src/main/agent-hooks/windows-direct-spawn-launcher.test.ts (4 tests), so all four assertions really execute instead of skipping.

Notes

Issue #8645 is still open even though #8737 fixed it and a user confirmed the fix on 1.4.141 — worth closing separately.

Codex/Antigravity/Devin hand the managed hook command to CreateProcess as
argv[0] rather than running it through cmd.exe, so it must stay a single
spawnable token naming a real .cmd. stablyai#8430 broke that with a cmd-builtin
compound and failed every hook with exit 1; stablyai#8737 restored the bare path.

Nothing currently guards which wrapper each service picks:
installer-utils.test.ts tests wrapWindowsCmdHookCommand directly, so a
service switching to wrapWindowsHookCommand reintroduces stablyai#8430 untested.
Surfaced while evaluating stablyai#8665, which proposed exactly that swap for
Codex to fix stablyai#8645 -- already fixed by stablyai#8737 via the bare path.

The existing codex-side check is win32-gated and pr.yml runs unit tests
on ubuntu only, so it never executes in PR CI. This mocks the managed
script path instead of the platform, keeping the real wrappers in play so
the contract is asserted on Linux.

Covers all three direct-spawn agents plus a shell-launched control
(Cursor) so the split is pinned in both directions.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Vitest suite for Windows agent hook launcher contracts. It mocks Electron paths, the home directory, installer utilities, and platform behavior, then verifies Codex, Devin, and Antigravity commands use directly spawnable .cmd paths. It also verifies Cursor uses an encoded PowerShell launcher rather than a bare .cmd path.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning This PR adds regression tests only; it does not resolve the Windows hook failures or the exit-1 behavior described in #8645. Implement the actual Windows launcher fix so SessionStart, UserPromptSubmit, and Stop hooks run without exit 1 after the Codex update.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The diff stays scoped to the documented regression test coverage and introduces no unrelated production changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately summarizes the Windows launcher contract regression test.
Description check ✅ Passed The description is detailed and covers the change, rationale, verification, and notes, but it omits screenshots, AI review, and security sections.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a695dca-3f09-4c3a-ac90-234c6cc0e831

📥 Commits

Reviewing files that changed from the base of the PR and between badf911 and 5d1670e.

📒 Files selected for processing (1)
  • src/main/agent-hooks/windows-direct-spawn-launcher.test.ts

Comment on lines +107 to +110
it('keeps every Antigravity managed command directly spawnable', () => {
withPlatform('win32', () => new AntigravityHookService().install())
const commands = readAntigravityCommands(homeDir)
expect(commands.length).toBeGreaterThan(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Seed the Antigravity hooks configuration before installation.

This test creates only homeDir; AntigravityHookService.install() returns an error when .gemini/config/hooks.json is missing, so readAntigravityCommands() then throws while reading a nonexistent file. Create a minimal valid configuration and assert the install status is installed before reading the generated commands.

Based on the supplied AntigravityHookService.install() implementation, missing configuration returns an error.

@nwparker

Copy link
Copy Markdown
Contributor

Thanks for the contribution. The Windows direct-spawn hook launcher contract is now covered by tests already on main (windows-direct-cmd-hook-command, windows-batch-hook-launcher, windows-direct-spawn-launcher), so we are closing this test-only PR as redundant.

@nwparker nwparker closed this Oct 24, 2026
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.

[Bug]: orca: Hooks fail after updating Codex to v0.144.3 (SessionStart/UserPromptSubmit/Stop hook exited with code 1)

2 participants