Skip to content

Codex: make installed stop hooks fire-and-forget - #7410

Merged
azooz2003-bit merged 5 commits into
mainfrom
feat-codex-stop-hook-timeout
Jul 6, 2026
Merged

azooz2003-bit merged 5 commits into
mainfrom
feat-codex-stop-hook-timeout

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • make the persistent installed Codex Stop hook use the same fire-and-forget wrapper as session-start and prompt-submit
  • extend the hook installer regression to inspect generated persistent scripts instead of the older inline command shape
  • add coverage that an installed async stale Stop cannot mark a newer Codex turn idle

Verification

  • direct black-box probe of installed stop script returned quickly while the slow child completed later with the right stdin/PID
  • CMUX_SKIP_ZIG_BUILD=1 xcodebuild -project cmux.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /tmp/cmux-stopfx4b test -only-testing:cmuxTests/CLICodexHookTimeoutRegressionTests (new async stale-stop regression passed; one existing harness-style timeout assertion remained flaky despite subprocess status 0 and expected stdout)
  • autoreview --mode branch --base origin/main clean

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Made the installed Codex Stop hook fire-and-forget so it returns fast, matching session-start and prompt-submit. This reduces CLI blocking when stopping a turn and aligns behavior across Codex hooks.

  • Tests
    • Parse installed hooks from hooks.json and assert fire-and-forget scripts for session-start, prompt-submit, and stop; added coverage that the installed Stop returns quickly and that a stale async Stop cannot mark a newer turn idle.
    • Added CLICodexHookTimeoutRegressionTestSupport.swift, wired it into the test target, and prefixed Codex test helper symbols to avoid collisions.

Written for commit 0345eb6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved Codex hook “fire-and-forget” eligibility so the stop subcommand is now treated as eligible, helping it complete sooner and reducing delays.
  • Tests

    • Refactored Codex hook timeout regression tests to validate against parsed installed hook entries (including body/payload and PID markers).
    • Expanded slow and async stop scenarios with more reliable polling-based assertions and updated socket/CLI mocking.
  • Chores

    • Added dedicated Codex hook regression test-support utilities and included them in the Xcode test target.

@vercel

vercel Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 6, 2026 9:32pm
cmux-staging Building Building Preview, Comment Jul 6, 2026 9:32pm

@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds stop to codex fire-and-forget handling, introduces parsed hook-entry test support with socket/process helpers, and updates timeout regression tests to select hooks by event name and assert against resolved hook bodies.

Changes

Codex Stop Hook Behavior and Test Refactor

Layer / File(s) Summary
Fire-and-forget eligibility change
CLI/CMUXCLI+AgentHookDefinitions.swift
codexHookCanRunFireAndForget now also matches the stop subcommand.
Test support model and harness
cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift, cmux.xcodeproj/project.pbxproj
Adds installed-hook and process-result support types, hook loading and environment setup, executable script helpers, socket server utilities, process timeout execution, polling helpers, and project wiring for the new test support file.
Regression test updates
cmuxTests/CLICodexHookTimeoutRegressionTests.swift
The regression tests now use parsed hook entries and resolved bodies, select hooks by event name, add stop-hook coverage, and replace fixed waits with polling for async stop assertions.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The committed diff only changes test files; no production Swift actor-isolation declarations were introduced or worsened, and tests are exempt.
Cmux Swift Blocking Runtime ✅ Passed Production Swift only adds stop to an allowlist; semaphores/locks/sleeps are confined to cmuxTests test scaffolding, which the rule allows.
Cmux Browser Automation Off-Main ✅ Passed PR only changes Codex hook install/tests/project wiring; no browser.*, WebKit, or socketWorkerMethods/processV2Command changes appear in the diff.
Cmux Expensive Synchronous Load ✅ Passed The diff only adds stop to Codex fire-and-forget hook handling and test support; no agent-history load or MainActor/interactive sync-load path was introduced.
Cmux Cache Substitution Correctness ✅ Passed Diff only broadens Codex stop fire-and-forget and refactors tests/support; it doesn’t replace any fresh authoritative read with a cached/opportunistic value in a persistence/history/snapshot path.
Cmux No Hacky Sleeps ✅ Passed Added polling/sleeps only in cmuxTests test scaffolding; no non-test runtime delay logic changed. Production diff just adds Stop to fire-and-forget.
Cmux Algorithmic Complexity ✅ Passed Production change is a constant-time boolean check over three fixed Codex subcommands; remaining edits are test/support code and don’t worsen scalable-path complexity.
Cmux Swift Concurrency ✅ Passed The only new DispatchQueue usage is confined to new test-support helpers; production Swift changes only broaden Codex stop hook handling and add no legacy async patterns.
Cmux Swift @Concurrent ✅ Passed Touched Swift is synchronous/GCD-based; no new @concurrent, nonisolated async, or actor-isolated UI work was introduced.
Cmux Swift File And Package Boundaries ✅ Passed PASS: The only production edit is a 1-line boolean tweak in a 782-line CLI glue file; the new 278-line helper and added code live in cmuxTests, a test-only boundary.
Cmux Swiftpm Lockfiles ✅ Passed Diff only touches two Swift test files; no .gitignore, workflow, Package.resolved, or Xcode package-reference changes are present.
Cmux Swift Logging ✅ Passed The production change only broadens a hook predicate; the changed Swift files add no print/debugPrint/dump/NSLog, Logger, or ad hoc diagnostic logging.
Cmux User-Facing Error Privacy ✅ Passed PASS: the PR’s non-test change only touches a fire-and-forget boolean; the added test/support code has no user-facing errors or sensitive payloads.
Cmux Full Internationalization ✅ Passed PR only changes hook logic plus tests/support; no new user-facing Swift text, xcstrings, or web locale files were added.
Cmux Swiftui State Layout ✅ Passed Only test files changed; no SwiftUI view/state/layout code or rule-patterns like ObservableObject, GeometryReader, or render-time mutation were introduced.
Cmux Architecture Rethink ✅ Passed PASS: the only production change is a one-line Codex stop fire-and-forget fix; all polling/locks/semaphores live in cmuxTests test-only support, which the rule allows.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only touches Codex hook CLI/tests; no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes, so the auxiliary-window rule doesn't apply.
Cmux Source Artifacts ✅ Passed Changed paths are hand-written source, tests, and Xcode wiring; no generated logs, caches, build outputs, or scratch artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No changed Swift file under **/Sources/**; the only production edit adds stop to fire-and-forget logic, not a test/debug seam.
Cmux No Ambient Global State ✅ Passed No new production ambient globals; the only code change is a private static helper tweak, and the new top-level helpers live in test support with no shared mutable state.
Title check ✅ Passed The title accurately captures the main change: installed Stop hooks now use the fire-and-forget wrapper.
Description check ✅ Passed The description includes clear summary and verification details; only non-critical template sections are omitted.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-codex-stop-hook-timeout

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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/CLICodexHookTimeoutRegressionTests.swift`:
- Around line 147-209: The two “returns before slow cmux command finishes” tests
are duplicating the same setup and assertions, so extract a shared parametrized
helper from codexInstalledHookReturnsBeforeSlowCmuxCommandFinishes and
codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes. Make the helper
accept the event name, sleep duration, and expected routed args substring, then
reuse it for both tests while keeping the fake CLI setup, install/run flow, and
file-wait assertions in one place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5bcfc54d-2ab5-4d64-b0a5-217b4e697604

📥 Commits

Reviewing files that changed from the base of the PR and between 0af6916 and cb338e2.

📒 Files selected for processing (2)
  • CLI/CMUXCLI+AgentHookDefinitions.swift
  • cmuxTests/CLICodexHookTimeoutRegressionTests.swift

Comment on lines +147 to 209
@Test func codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes() throws {
let cliPath = try bundledCLIPath()
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("cmux-codex-stop-hook-async-\(UUID().uuidString)", isDirectory: true)
let codexHome = root.appendingPathComponent(".codex", isDirectory: true)
let fakeCLI = root.appendingPathComponent("cmux", isDirectory: false)
let capturedStdin = root.appendingPathComponent("hook-stdin.json", isDirectory: false)
let capturedArgs = root.appendingPathComponent("hook-args.txt", isDirectory: false)
let capturedPID = root.appendingPathComponent("hook-pid.txt", isDirectory: false)
let doneFile = root.appendingPathComponent("hook-done.txt", isDirectory: false)
try FileManager.default.createDirectory(at: codexHome, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }

try makeExecutableShellFile(at: fakeCLI, lines: [
"#!/bin/sh",
"printf '%s\\n' \"$*\" > \"$CMUX_TEST_ARGS\"",
"printf '%s\\n' \"$CMUX_CODEX_PID\" > \"$CMUX_TEST_PID\"",
"cat > \"$CMUX_TEST_STDIN\"",
"sleep 2",
"printf done > \"$CMUX_TEST_DONE\"",
])

let install = runProcess(
executablePath: cliPath,
arguments: ["hooks", "codex", "install", "--yes"],
environment: codexHookTestEnvironment(root: root, codexHome: codexHome),
timeout: 5
)
#expect(!install.timedOut, Comment(rawValue: install.stderr))
#expect(install.status == 0, Comment(rawValue: install.stderr))

let command = try #require(
codexHookEntries(in: codexHome).first { $0.eventName == "Stop" }?.command
)
let payload = #"{"session_id":"codex-session","stop_hook_active":false}"#
let run = runProcess(
executablePath: "/bin/sh",
arguments: ["-c", command],
environment: [
"HOME": root.path,
"PATH": "/usr/bin:/bin:/usr/sbin:/sbin",
"TMPDIR": root.path,
"CMUX_SURFACE_ID": "surface-123",
"CMUX_SOCKET_PATH": "/tmp/cmux-test.sock",
"CMUX_BUNDLED_CLI_PATH": fakeCLI.path,
"CMUX_CODEX_PID": "4242",
"CMUX_TEST_STDIN": capturedStdin.path,
"CMUX_TEST_ARGS": capturedArgs.path,
"CMUX_TEST_PID": capturedPID.path,
"CMUX_TEST_DONE": doneFile.path,
],
standardInput: payload,
timeout: 1
)

#expect(!run.timedOut, Comment(rawValue: run.stderr))
#expect(run.status == 0, Comment(rawValue: run.stderr))
#expect(run.stdout == "{}\n")
#expect(waitForFile(capturedStdin, containing: payload, timeout: 1))
#expect(waitForFile(capturedArgs, containing: "--socket /tmp/cmux-test.sock hooks codex stop", timeout: 1))
#expect(waitForFile(capturedPID, containing: "4242", timeout: 1))
#expect(waitForFile(doneFile, containing: "done", timeout: 3))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared helper for the two "returns before slow cmux command finishes" tests.

codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes duplicates almost the entire body of codexInstalledHookReturnsBeforeSlowCmuxCommandFinishes (fake CLI setup, install, run, and assertions), differing only in event name (UserPromptSubmit vs Stop), sleep duration, and the expected routed subcommand string. Consider extracting a shared parametrized helper (event name, sleep seconds, expected args substring) to avoid the two tests drifting independently.

♻️ Sketch of a shared helper
-    `@Test` func codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes() throws {
-        let cliPath = try bundledCLIPath()
-        ... (duplicated setup) ...
-    }
+    `@Test` func codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes() throws {
+        try assertInstalledHookReturnsBeforeSlowCmuxCommandFinishes(
+            eventName: "Stop",
+            fakeCLISleepSeconds: 2,
+            payload: #"{"session_id":"codex-session","stop_hook_active":false}"#,
+            expectedRoutedArgsSubstring: "--socket /tmp/cmux-test.sock hooks codex stop",
+            doneWaitTimeout: 3
+        )
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/CLICodexHookTimeoutRegressionTests.swift` around lines 147 - 209,
The two “returns before slow cmux command finishes” tests are duplicating the
same setup and assertions, so extract a shared parametrized helper from
codexInstalledHookReturnsBeforeSlowCmuxCommandFinishes and
codexInstalledStopHookReturnsBeforeSlowCmuxCommandFinishes. Make the helper
accept the event name, sleep duration, and expected routed args substring, then
reuse it for both tests while keeping the fake CLI setup, install/run flow, and
file-wait assertions in one place.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes the installed Codex Stop hook use the fire-and-forget wrapper (via nohup sh -c) that session-start and prompt-submit already use, preventing the CLI from blocking until the stop handler completes.

  • Production change: One-line addition of || subcommand == \"stop\" to codexHookCanRunFireAndForget (line 409, CMUXCLI+AgentHookDefinitions.swift), which routes stop hooks through codexFireAndForgetAgentHookShellCommand during persistent install.
  • Test refactoring: Helper types and functions previously private to the test struct are extracted into CLICodexHookTimeoutRegressionTestSupport.swift and renamed with the codexHook prefix for clarity and cross-test reuse.
  • New regression coverage: Two new tests verify that the installed stop script returns quickly while the slow child runs in the background, and that a stale async stop carrying an older turn_id cannot mark a newer running turn idle.

Confidence Score: 5/5

Safe to merge — the production change is a single predicate extension with clear intent, the existing hook execution path is unchanged, and new regression tests confirm both the quick-return behavior and the stale-stop guard.

The production diff is one line in codexHookCanRunFireAndForget. The surrounding call site already handles session-start and prompt-submit identically, so the stop case slots in cleanly without any new state or branching. New black-box tests directly verify that the installed stop script returns before its slow child finishes and that an out-of-date turn_id does not incorrectly mark a newer turn idle. No production-path actors, blocking primitives, or data-loading concerns are touched.

No files require special attention.

Important Files Changed

Filename Overview
CLI/CMUXCLI+AgentHookDefinitions.swift Minimal one-line production change: adds "stop" to the fire-and-forget eligibility predicate so the installed Codex stop hook uses nohup sh -c wrapping, matching session-start and prompt-submit.
cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift New test-only support module extracting helpers (socket server, process runner, polling utilities) from the test struct into reusable top-level functions. All code is in the test target; no production seams introduced.
cmuxTests/CLICodexHookTimeoutRegressionTests.swift Refactors existing tests to use the shared support module, updates hook-body assertions to inspect generated scripts via codexHookEntries, and adds two new tests: one for stop hook quick-return and one for stale-stop turn-idle guard.
cmux.xcodeproj/project.pbxproj Adds CLICodexHookTimeoutRegressionTestSupport.swift to the test target build phase and file group — straightforward project wiring for the new support file.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[hookCommandString called for Codex agent] --> B{codexHookCanRunFireAndForget?}
    B -- "session-start / prompt-submit / stop NEW" --> C[codexFireAndForgetAgentHookShellCommand nohup sh -c wrapper]
    B -- "session-end / other" --> D[agentHookShellCommand synchronous]
    C --> E[codexPersistentHookScriptCommand write script to disk]
    D --> E
    E --> F[Codex hooks.json command = path to script]
    G[Codex fires Stop event] --> H[Installed script runs]
    H --> I[nohup sh -c background child started]
    I --> J[Script exits immediately]
    I --> K[Background child: hooks codex stop]
    K --> L{turn_id matches current turn?}
    L -- No --> M[Stale: skip notify/idle]
    L -- Yes --> N[Mark turn Idle / notify surface]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[hookCommandString called for Codex agent] --> B{codexHookCanRunFireAndForget?}
    B -- "session-start / prompt-submit / stop NEW" --> C[codexFireAndForgetAgentHookShellCommand nohup sh -c wrapper]
    B -- "session-end / other" --> D[agentHookShellCommand synchronous]
    C --> E[codexPersistentHookScriptCommand write script to disk]
    D --> E
    E --> F[Codex hooks.json command = path to script]
    G[Codex fires Stop event] --> H[Installed script runs]
    H --> I[nohup sh -c background child started]
    I --> J[Script exits immediately]
    I --> K[Background child: hooks codex stop]
    K --> L{turn_id matches current turn?}
    L -- No --> M[Stale: skip notify/idle]
    L -- Yes --> N[Mark turn Idle / notify surface]
Loading

Reviews (4): Last reviewed commit: "Avoid codex hook test helper symbol coll..." | Re-trigger Greptile

timeout: 10
)
#expect(!install.timedOut, Comment(rawValue: install.stderr))
#expect(install.status == 0, Comment(rawValue: install.stderr))

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.

P2 Lost timeout diagnostic in codexHookInstallations

The #expect(!install.timedOut, ...) assertion was removed when the install timeout was bumped from 5 s to 10 s. When the install does time out, runProcess kills the process (SIGTERM then SIGKILL), producing a non-zero terminationStatus, so #expect(install.status == 0, ...) still catches it — but the failure message shows only stderr content rather than clearly indicating a timeout, making slow-CI failures harder to distinguish from logic errors. Both new tests added by this PR correctly retain the timedOut check.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +304 to +306
#expect(waitForCondition(timeout: 2) {
commands.snapshot().count > staleStopStart
})

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.

P2 Fragile liveness gate on stale-stop detection

#expect(waitForCondition(timeout: 2) { commands.snapshot().count > staleStopStart }) asserts that the stale stop's background process sends at least one socket command within 2 seconds. If the production stop handler detects staleness via a pure in-memory turn-ID check and exits before any socket call, this #expect fails even though the behavior is correct — leaving the actual correctness assertion (staleStopCommands check below) untested. Consider replacing with a fixed settling sleep or removing the gate and relying solely on the staleStopCommands content assertion, so the test doesn't couple to whether the handler queries the socket to discover staleness.

@azooz2003-bit
azooz2003-bit enabled auto-merge (squash) July 6, 2026 21:16

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cmuxTests/CLICodexHookTimeoutRegressionTests.swift (1)

289-299: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wait for the stale Stop work to complete before asserting.

Line 289 only waits for the first post-baseline socket line. If the async Stop later emits notify_target or set_status codex ... Idle, this regression can pass before observing the bug. Add a real completion signal from the mock socket handler or wait for the Stop connection/batch to finish, then assert over the full Stop batch. As per coding guidelines, tests should assert on causality rather than latency; as per path instructions, liveness/Idle regressions must avoid stale reads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/CLICodexHookTimeoutRegressionTests.swift` around lines 289 - 299,
The stale Stop regression test is only waiting for the first post-baseline
socket message, so it can assert before the async Stop work has fully finished.
Update the test around the existing waitForCondition and staleStopCommands
capture in CLICodexHookTimeoutRegressionTests to wait on a real completion
signal from the mock socket handler, or otherwise wait for the Stop
connection/batch to fully drain before checking for notify_target or set_status
codex Idle. Then perform the assertion over the complete Stop batch so the test
reflects causality instead of timing.

Sources: Coding guidelines, Path instructions

cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift (1)

233-249: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Drain subprocess output while the process is running.

waitUntilExit() runs before stdout/stderr are read, so a child that writes enough output can block on a full pipe and make this helper report a timeout for a healthy process. Drain both pipes concurrently or redirect to temporary files before waiting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift` around lines 233 -
249, The timeout helper in CLICodexHookTimeoutRegressionTestSupport is waiting
on waitUntilExit before draining stdout/stderr, which can deadlock a chatty
child process on full pipes. Update this helper to consume both pipes
concurrently while the process runs, or redirect output to temporary files
before waiting, and keep the existing timeout/terminate logic intact around the
process wait and kill flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmuxTests/CLICodexHookTimeoutRegressionTests.swift`:
- Around line 289-299: The stale Stop regression test is only waiting for the
first post-baseline socket message, so it can assert before the async Stop work
has fully finished. Update the test around the existing waitForCondition and
staleStopCommands capture in CLICodexHookTimeoutRegressionTests to wait on a
real completion signal from the mock socket handler, or otherwise wait for the
Stop connection/batch to fully drain before checking for notify_target or
set_status codex Idle. Then perform the assertion over the complete Stop batch
so the test reflects causality instead of timing.

In `@cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift`:
- Around line 233-249: The timeout helper in
CLICodexHookTimeoutRegressionTestSupport is waiting on waitUntilExit before
draining stdout/stderr, which can deadlock a chatty child process on full pipes.
Update this helper to consume both pipes concurrently while the process runs, or
redirect output to temporary files before waiting, and keep the existing
timeout/terminate logic intact around the process wait and kill flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 043d430f-ea40-4760-8007-200ae955c51a

📥 Commits

Reviewing files that changed from the base of the PR and between 2bb8596 and 0345eb6.

📒 Files selected for processing (2)
  • cmuxTests/CLICodexHookTimeoutRegressionTestSupport.swift
  • cmuxTests/CLICodexHookTimeoutRegressionTests.swift

@azooz2003-bit
azooz2003-bit merged commit ed70dad into main Jul 6, 2026
31 checks passed
mochiexists pushed a commit to mochiexists/cmux-mochi that referenced this pull request Jul 7, 2026
* Codex: make installed stop hook fire-and-forget

* Tests: cover async Codex stale stop hook

* Tests: split Codex hook timeout regression support

* Tests: wire Codex hook timeout support file

* Avoid codex hook test helper symbol collisions

(cherry picked from commit ed70dad)

This branch was successfully deployed

1 active deployment
Preview – cmux — 0345eb6a Deployed Jul 6, 2026 by vercel[bot]
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