Repository navigation
Deliver Codex lifecycle notifications from detached Windows hooks - #7
Conversation
Recover the live Codex ancestor console while keeping hook control streams captured. Add isolated ConPTY delivery coverage and publish the candidate as plugin 0.4.3. Co-Authored-By: Warp <agent@warp.dev>
| ulong childCreated; | ||
| if (!CreationTime(GetCurrentProcess(), out childCreated)) | ||
| return IntPtr.Zero; | ||
| IntPtr snapshot = CreateToolhelp32Snapshot(SnapshotProcesses, 0); |
There was a problem hiding this comment.
How expensive is taking a snapshot? Seems potentially heavyweight for finding the codex process
There was a problem hiding this comment.
Setting up a benchmark to test this.
There was a problem hiding this comment.
agent output:
I benchmarked the exact implementation on Windows Server 2022 with 8 logical CPUs, 16 GiB RAM, and Windows PowerShell 5.1.
| Operation | Samples | Median | p95 |
|---|---|---|---|
| Snapshot creation and close | 200 | 0.43 ms | 0.54 ms |
| Full successful ancestor lookup | 200 | 0.86 ms | 0.94 ms |
| Full lookup with 64 additional processes | 200 | 1.23 ms | 1.42 ms |
| Fresh PowerShell notification hook | 25 | 243 ms | 255 ms |
The snapshot only captures process metadata, not process memory, heaps, or modules, but it does cover all processes. The full lookup measurements include enumeration, dictionary construction, ancestor validation, reflection overhead, and handle cleanup.
All 25 fresh hooks successfully delivered notifications through ConPTY and preserved the stdout/stderr control streams.
Given these results, I’d keep the supported snapshot API rather than introduce an internal API dependency. Discovery accounts for roughly 0.4–0.5% of fresh-hook runtime on this machine. These are single-VM measurements, though, not a guarantee for every customer machine.
| if (process == IntPtr.Zero) | ||
| break; | ||
| ulong parentCreated; | ||
| if (WaitForSingleObject(process, 0) != WaitTimeout |
There was a problem hiding this comment.
Is this simply to check that the parent is alive (given that we wait with a 0ms timeout)?
There was a problem hiding this comment.
Yes. WaitForSingleObject(process, 0) == WAIT_TIMEOUT means it hasn’t exited at that instant, since a process handle becomes signaled when the process exits.
At creation-time there is also a check which rejects a reused parent PID whose process started after the child.
| uint[] original = ConsoleProcesses(); | ||
| if (Array.IndexOf(original, targetId) < 0) | ||
| { | ||
| foreach (uint id in original) |
There was a problem hiding this comment.
This looks a lot like FindCodex - why do we need to loop again?
There was a problem hiding this comment.
FindCodex walks process ancestry to locate the notification destination
This only checks the processes attached to the hook’s original console and retain a live peer so we can reattach to that console afterward
| } | ||
| } | ||
|
|
||
| IntPtr console = CreateFileW("CONOUT$", 0x40000000, 3, IntPtr.Zero, 3, 0, IntPtr.Zero); |
There was a problem hiding this comment.
If we're writing to this console object, do we need to modify stdin/out/err too?
There was a problem hiding this comment.
Not for the write. WriteConsoleW uses the CONOUT$ handle which is independent from stdin/stdout/stderr. The SetStdHandle calls will restore the original handles because AttachConsole can replace them with console handles. This will keep hook-control JSON on stdout and diagnostics on stderr instead of the codex console.
captainsafia
left a comment
There was a problem hiding this comment.
I have one comment about whether we might benefit from pre-compiling the script, especially if we are invoking it frequently whenever hooks are executed. We weren't doing this before so I'm leaving it as a non-blocking comment.
|
|
||
| private static bool IsCodex(string executable) | ||
| { | ||
| return String.Equals(executable, "codex.exe", StringComparison.OrdinalIgnoreCase) |
There was a problem hiding this comment.
OOC, do we expect the codex-*.exe search to grab versioning codex installs?
There was a problem hiding this comment.
Not for the official versioned installs (they use codex.exe).
The codex-*.exe match is for target-suffixed release binaries, such as codex-x86_64-pc-windows-msvc.exe.
| } | ||
| } | ||
| "@ | ||
| Add-Type -Path (Join-Path $PSScriptRoot "console-writer.cs") |
There was a problem hiding this comment.
OOC, how often do we end up calling into this? Main reason is that this will re-compile this CSharp file everytime this hook is invoked so we'll end up recompiling the C# file to a DLL every time. The C# compiler is efficient enough that this might not be a problem but curious how often this hook is invoked.
There was a problem hiding this comment.
It is called from SessionStart, UserPromptSubmit, PostToolUse, PermissionRequest, and Stop.
- PostToolUse is the frequent one, since it fires after each tool execution.
Full hook timings
Windows Server 2022, 8 vCPUs, 16 GB RAM. Each scenario used 30 fresh PowerShell processes after warmups, with randomized ordering.
| Measurement | PowerShell 5.1 median / p95 | PowerShell 7.4 median / p95 |
|---|---|---|
| Startup-only baseline | 152 / 366 ms | 216 / 362 ms |
Enabled PostToolUse hook |
370 / 591 ms | 853 / 1,174 ms |
The enabled hook timings include startup, compilation, and successful notification delivery. Across the delivery suite, all 340 enabled notifications were decoded from ConPTY and verified against their expected session IDs and events.
Base versus this PR
For a comparable successful-delivery path with an inherited console:
| Revision | PowerShell 5.1 median | PowerShell 7.4 median |
|---|---|---|
| Base | 325 ms | 749 ms |
| This PR | 345 ms | 764 ms |
| Difference | +20 ms | +15 ms |
A separate, interleaved compilation-only comparison measured:
Add-Type duration |
PowerShell 5.1 median | PowerShell 7.4 median |
|---|---|---|
| Base, inline C# | 87 ms | 259 ms |
| This PR, C# file | 95 ms | 287 ms |
These are runner-specific measurements with substantial variation, so the small differences shouldn't be treated as precise latency guarantees. But the recurring compilation cost isn't negligible, particularly for PostToolUse. A precompiled helper could avoid that cost; this PR doesn't introduce most of it.
## Description Plugin 0.4.3 is available from the canonical Git marketplace. warpdotdev/codex-warp#7 merged on October 14; both the marketplace entry and plugin manifest on `main` advertise 0.4.3. Raise the Codex notification-plugin floor to 0.4.3 so cached 0.4.2 installs get upgraded. The companion plugin restores lifecycle delivery from detached Windows hooks; it prevents missing session IDs and completion signals. The orchestration-plugin minimum stays at 0.4.1. This PR contains only the client version requirement and test changes. Update-behavior coverage remains; there is no constant-only getter test. The transport implementation and Windows integration coverage live in the companion PR. ## Linked Issue - [ ] The linked issue is labeled `ready-to-spec` or `ready-to-implement`. No dedicated issue is linked; the companion PR carries the fix. - [ ] Where appropriate, screenshots or a short video of the implementation are included below. No GUI change; validation uses isolated console fixtures. ## Testing - [ ] I have manually tested my changes locally with `./script/run`. Not run; this change only updates the plugin requirement. Local validation on October 14: - `cargo nextest run -p warp --lib --locked -E 'test(plugin_manager::codex::tests)'`: 38 passed. Nextest reported one LEAK warning for `does_not_support_update_without_codex_plugin`; no assertion failed. - `cargo check -p warp --lib --locked`: passed. - `cargo clippy -p warp --lib --tests --locked -- -D warnings`: passed. - `cargo fmt -p warp` and `git diff --check`: passed. Existing outdated-cache coverage now uses 0.4.2. The companion's six Windows delivery tests passed on both system ConPTY and Warp's bundled DLL. Post-publication staging E2E validation was not run in this session. Full Warp CI attempt 3 was requested: https://github.com/warpdotdev/warp/actions/runs/37846977663. ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode CHANGELOG-NONE Conversation: https://staging.warp.dev/conversation/f70d81ec-85a4-4322-abaf-a1fb9713ce7e Review follow-up: https://staging.warp.dev/conversation/743855e1-c346-4782-8655-d4e082820056 Plan: https://staging.warp.dev/drive/notebook/sjtUyYQASbuCQEbfYS6AFA Co-Authored-By: Warp <agent@warp.dev> Co-authored-by: Warp <agent@warp.dev>
Codex launches Windows hooks without the original console. The notifier can exit successfully without delivering SessionStart or Stop, leaving Warp without a native session ID or completion signal. This change reconnects the hook to the closest live Codex ancestor's console while keeping hook control streams captured.
Review guide
Start with
plugins/warp/scripts/console-writer.cs. Focus on ancestor identity/liveness checks, handle lifetime, and standard-stream restoration. The delivery fixture checks received OSC payloads, not just successful Win32 API returns.What changed
This avoids a new IPC endpoint or an upstream Codex binary fork. Publish this plugin before shipping the companion Warp minimum-version update: warpdotdev/warp#16368.
Validation
A Windows validation agent ran all six delivery tests on both system ConPTY and Warp's bundled ConPTY from
v0.2026.10.08.08.30.dev_00; both suites passed. Coverage includes detached/direct/cmd launches, spaces, Unicode, concurrent notifications, captured control streams, unrelated ancestors, and an exited closest Codex ancestor with an older live coordinator. The production writer/common files were checksum-matched to the local candidate, and the fixture's executable AST matched.Locally,
bash tests/test-hooks.shpassed 65 checks. PowerShell parsing, C# compilation, and whitespace checks passed. The existing POSIX notification suite timed out on this Mac, including on the unmodified baseline; it is not claimed passing.CI passed both plugin test jobs. Windows CI executed the final PowerShell helper and all six delivery tests against this committed 0.4.3 candidate; it reported "PowerShell hook tests passed" and six passing tests. Linux CI also passed. Real staging E2E follow-ups remain pending after publication.
CHANGELOG-BUG-FIX: Fixed delivery of Codex lifecycle notifications from detached Windows hooks.
Conversation: https://staging.warp.dev/conversation/f70d81ec-85a4-4322-abaf-a1fb9713ce7e
Plan: https://staging.warp.dev/drive/notebook/sjtUyYQASbuCQEbfYS6AFA
Co-Authored-By: Warp agent@warp.dev