Skip to content

fix: resolve Windows native hook session owner PID#3260

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
bohe76:agent/fix-windows-session-owner-pid
Jul 22, 2026
Merged

fix: resolve Windows native hook session owner PID#3260
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
bohe76:agent/fix-windows-session-owner-pid

Conversation

@bohe76

@bohe76 bohe76 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

On Windows, the native hook ancestry resolver called the Unix ps command. When that lookup failed, SessionStart could persist the transient PowerShell hook PID instead of the long-lived Codex PID. Once the shim exited, Stop authorization saw the selected session pointer as stale-dead.

The previous command-only Codex detection could also mistake shell arguments or .codex hook paths for the Codex process.

Changes

  • Read the Windows process table once with CIM and follow the parent chain from the hook process.
  • Use the process image name to distinguish codex.exe from PowerShell/cmd arguments that merely mention Codex.
  • Preserve the existing Linux and other Unix ancestry paths.
  • Add a Windows regression test covering the PowerShell shim, a misleading shell argument, and the real codex.exe ancestor.

Validation

  • npm run build
  • Targeted native-hook regression tests: 2 passed, 0 failed
  • npx biome lint src/scripts/codex-native-hook.ts src/scripts/__tests__/codex-native-hook.test.ts
  • Live Windows resolver smoke: returned the active codex.exe PID
  • omx doctor: 15 passed, 4 environment warnings, 0 failed
  • npm test: did not complete on Windows because the existing ask.test fake-provider flow invoked an external Claude process and stalled; the run was stopped after isolating that unrelated test path.

Checklist

  • PR is focused and avoids unrelated changes
  • Backward-compatibility impact considered
  • Document-refresh: not-needed | Runtime-only Windows process ownership fix with regression coverage; no user-facing contract changed.

Related

N/A

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Independent maintainer review — PR #3260 @ 8031bec81105d0e0b65668602afb136a74f5fe9c

Read-only adversarial review; branch not mutated.

Bug claim — verified

On win32, readParentPid/readProcessCommand fell through to the Unix ps path, which fails, so resolveSessionOwnerPidFromAncestry collapsed to the transient PowerShell shim PID. classifySessionProcess (src/hooks/session.ts:651) then probes that dead PID after shim exit and returns stale-dead, breaking Stop authorization. The mechanism in the PR description is accurate, not speculative.

Fix design — verified

  • One bounded CIM process-table read (powershell.exe -NoProfile -NonInteractive, 5s timeout, 4MB buffer, windowsHide), parent-chain walk capped at 6 hops, matching the Unix loop bound.
  • Image-name-first Codex detection (codex.exe) with shell-name exclusion (powershell/pwsh/cmd added to looksLikeShellCommand) correctly rejects the misleading-argument case (pwsh -Command "... codex.exe ..."). If CommandLine is access-denied, Name still satisfies looksLikeCodexProcess — graceful.
  • Unix ancestry path preserved: injected-dependency branch untouched, and the tightened looksLikeCodexCommand regex still matches canonical Unix shapes (node /usr/local/bin/codex.js) while excluding codex-native-hook.js wrappers and .codex hook paths — confirmed by the pre-existing ancestry regression test.
  • Cost is one CIM call per SessionStart only: resolveSessionOwnerPid is called solely from the SessionStart branch (codex-native-hook.ts:20230), not per hook event.
  • Cross-platform regression risk is low: win32 early-returns in readParentPid/readProcessCommand affect only the ancestry resolver (sole caller), and failure modes degrade to pre-fix behavior, never worse.

Interaction with merged #3257 fix (#3259, dev head 8f76c22b) — none

#3259 touches src/cli/update*, package-manager-ownership, postinstall. Zero shared files with this PR; merge-base 299f31ad; GitHub reports MERGEABLE.

Verification performed locally at exact head (Linux)

  • npm run build (tsc): clean.
  • dist/scripts/__tests__/codex-native-hook.test.js: 583/583 pass, including the new Windows lineage test and the Unix wrapper test.
  • dist/hooks/__tests__/session.test.js: 73/73 pass (stale-dead / owner-evidence contract consumer).
  • biome lint on both changed files: clean.

Hosted CI status

The CI workflow run for this head is action_required (first-time-contributor fork gate): hosted build/test lanes have not executed. Local runs above are the current evidence; approve the pending workflow run before merge per repo policy.

Blockers

None found.

Noise / non-blocking observations

  1. On CIM failure or timeout, resolveSessionOwnerPid silently falls back to process.pid (transient hook process), recreating the original stale-dead symptom with no diagnostic. A bounded warning would aid future triage.
  2. readWindowsProcessLineage's own JSON parsing (single-record object case, malformed records) is only exercised through a mocked lineage, not directly.
  3. Windows owner identity remains liveness-probe-only (no start-tick/cmdline identity as on Linux); PID reuse by an unrelated process keeps a pointer usable. Pre-existing platform contract, explicitly out of scope here.
  4. Contributor's npm test gap on Windows (ask.test external-provider stall) is honestly disclosed in the PR body and unrelated to this change.

Verdict: MERGE_READY


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner

CI record — exact reviewed head green, rebase required before merge

Follow-up to review 4757337998 (MERGE_READY). The approved hosted CI run has now reached terminal state on the exact reviewed head.

Exact-head CI evidence

  • Run: 29912070753 (pull_request), head_sha 8031bec81105d0e0b65668602afb136a74f5fe9cconclusion: success.
  • All executed lanes passed: Detect CI lanes, Lint, Build dist artifact, Typecheck, Ralplan preflight security (macOS), Coverage Gate (Team Critical), Ralph Persistence Gate, Test (Node 22 / smoke), Test (Node 20 / hooks-notify-platform), Test (Node 20 / team-state-runtime), Test (Node 20 / cli-core-rest), CI Status.
  • Rust Clippy / Rust Tests / Rust Format / Docs Check / Build (Full Source Build): legitimately skipped by lane classification (no Rust/docs changes in this diff).
  • Combined with the local verification from the signed review (tsc build clean; 583/583 codex-native-hook tests; 73/73 session contract tests; biome clean), head 8031bec8 is now fully green, hosted and local.

Merge precondition

The PR head is one commit behind current dev (8f76c22b, fix #3257/#3259 — file-disjoint from this change, verified: zero shared files, GitHub reports MERGEABLE). Per repo policy the stale head must not be merged as-is. The reviewed head requires a maintainer-supervised rebase (or equivalent update) onto current dev; after that, the same-head-equivalent rebased state must re-pass CI before merge. The contributor branch has not been mutated from this review lane.

Status: exact head 8031bec8 GREEN — merge blocked only on rebase onto current dev + rebased CI verification.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner

GJC maintainer rebase evidence — LOCAL CANDIDATE — PENDING PUSH

Rebase of PR #3260 prepared locally and verified; the contributor branch and this PR are still at the old head. Nothing has been pushed.

  • Old head (current PR head): 8031bec81105d0e0b65668602afb136a74f5fe9c
  • New head (LOCAL CANDIDATE — PENDING PUSH): 9142f91f977bd937df94a4c622ed43c190b3219e (tree dc4407768e89d7d4c265a7203bd6d46d1f78f832)
  • Canonical base: 8f76c22b01fb0314e801ea99c5c87e6347e76166 (origin/dev, re-verified stable immediately before this comment)
  • Merge-base: 299f31ad824409e5cae0015d982de41aa1b09552

Equivalence evidence

  • git range-diff 299f31ad..8031bec8 8f76c22b..9142f91f: exactly one mapping, 1: 8031bec8 = 1: 9142f91f, no !/</> entries.
  • Changed-file scope of 8f76c22b..9142f91f set-equal to the old PR scope: src/scripts/codex-native-hook.ts, src/scripts/__tests__/codex-native-hook.test.ts.
  • Byte-level diff old→new over both PR paths: empty (patch semantics unchanged).
  • Full old→new path delta set-equal to the upstream commit's own paths only (16 files under src/cli, src/scripts/postinstall*, docs/reports/issue-3257*); zero intersection with PR paths.

Frozen local checks at exact new head (after locked npm ci, lockfile byte-identical, disposable worktree)

  • npm run build: pass
  • npm run check:no-unused: pass
  • node dist/scripts/run-test-files.js dist/scripts/__tests__/codex-native-hook.test.js: 583/583 pass
  • Session tests (session, prompt-session-provenance, notify-hook-session-scope, notify-hook-session-idle-dedupe): 4+14+6+73 pass, 0 fail
  • npx --no-install biome lint on both changed files: clean

Superseded evidence

Prior review 4757337998 (MERGE_READY) and CI run 29912070753 (success) are bound to the old head and are superseded by this rebase; a fresh exact-head review and fresh CI on the new head are required.

Merge hold

Merge is held for BOTH a fresh exact-head MERGE_READY review on the pushed new head AND isDraft=false. This PR is currently a draft: even with green rebase/push/CI, merge eligibility reports BLOCKED_BY_DRAFT.

[GJC maintainer rebase evidence | session 019f8b39-26e6-7000-8264-82c84947cb27 | actor @Yeachan-Heo]

@Yeachan-Heo
Yeachan-Heo force-pushed the agent/fix-windows-session-owner-pid branch from 8031bec to 9142f91 Compare July 22, 2026 20:55
@Yeachan-Heo

Copy link
Copy Markdown
Owner

[GJC maintainer rebase push confirmation | session 019f8b39-26e6-7000-8264-82c84947cb27 | actor @Yeachan-Heo]

Pushed head confirmed at 9142f91f977bd937df94a4c622ed43c190b3219e on canonical base 8f76c22b01fb0314e801ea99c5c87e6347e76166; old head was 8031bec81105d0e0b65668602afb136a74f5fe9c. Push used --force-with-lease=refs/heads/agent/fix-windows-session-owner-pid:8031bec81105d0e0b65668602afb136a74f5fe9c to the contributor fork only; PUSH_TIMESTAMP=2026-07-22T20:55:07Z. Remote ref and PR headRefOid both re-verified at the new head post-push.

CI and a fresh exact-head adversarial review are pending. Merge remains held for BOTH a fresh exact-head MERGE_READY review on 9142f91f AND isDraft=false; current draft state yields BLOCKED_BY_DRAFT. Local recovery ref backup/pr3260-old-head retains the old head; no automatic restore will occur.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Final verdict — rebased head 9142f91f977bd937df94a4c622ed43c190b3219e

SHA-bound follow-up to review 4757337998 and green record comment 5050171032. Read-only lane; contributor branch not mutated.

Rebase equivalence — verified byte-identical

  • Base after rebase: 8f76c22b01fb0314e801ea99c5c87e6347e76166 (current dev at time of rebase), confirmed via merge-base.
  • git range-diff 299f31ad..8031bec8 8f76c22b..9142f91f1: 8031bec8 = 1: 9142f91f: exact patch equality.
  • git patch-id --stable of old vs. rebased diff: identical.
  • Rebased diff scope unchanged: exactly src/scripts/codex-native-hook.ts and src/scripts/__tests__/codex-native-hook.test.ts, +125/−11.
  • Tree delta old head → rebased head is exactly the #3259 file set (dev drift); those modules neither overlap with nor are imported by the changed hook/session modules.
  • All adversarial findings from the original exact-head review therefore carry over unchanged: bug claim accurate, fix sound, Unix paths preserved, one bounded CIM read per SessionStart only, failure modes degrade to pre-fix behavior at worst, no blockers.

Hosted CI on exact rebased head — terminal green

  • Run 29957125573 (pull_request), head_sha 9142f91f977bd937df94a4c622ed43c190b3219e — status completed, conclusion success.
  • 12 lanes success: Detect CI lanes, Lint, Build dist artifact, Typecheck, Ralplan preflight security (macOS), Coverage Gate (Team Critical), Ralph Persistence Gate, Test (Node 22 / smoke), Test (Node 20 / hooks-notify-platform), Test (Node 20 / team-state-runtime), Test (Node 20 / cli-core-rest), CI Status.
  • 5 lanes legitimately skipped by lane classification (Rust x3, Docs, Full Source Build) — no Rust/docs changes in this diff.
  • Combined with prior local verification of the byte-identical patch (tsc clean; 583/583 native-hook tests; 73/73 session contract tests; biome clean), the rebased head is fully verified.

Blockers

None. Non-blocking noise unchanged from the original review (silent transient-PID fallback on CIM failure, mocked-only lineage parsing test, pre-existing win32 probe-only identity).

Verdict: MERGE_READY

The PR is still draft; it may now be marked ready for review and merged to dev. This lane does not mutate or merge.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo marked this pull request as ready for review July 22, 2026 22:02
@Yeachan-Heo
Yeachan-Heo merged commit 5ac2e59 into Yeachan-Heo:dev Jul 22, 2026
20 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9142f91f97

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


function looksLikeShellCommand(command: string): boolean {
return /(^|[\/\s])(bash|zsh|sh|dash|fish|ksh)(\s|$)/i.test(command);
return /(^|[\\/\s])(bash|zsh|sh|dash|fish|ksh|powershell(?:\.exe)?|pwsh(?:\.exe)?|cmd(?:\.exe)?)(\s|$)/i.test(command);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize quoted Windows shell paths

On Windows the managed hook can launch PowerShell by a quoted absolute path, so Win32_Process.CommandLine may start like "C:\\...\\powershell.exe" -File ...; this pattern only treats powershell.exe/cmd.exe as a shell when whitespace or end-of-string follows the executable name. In fallback cases where no exact codex.exe/codex.js ancestor is matched, looksLikeShellCommand(lineage[0].command) then returns false and the resolver records the transient shim PID instead of lineage[1], recreating the stale-dead Stop owner on those installs.

Useful? React with 👍 / 👎.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants