Skip to content

Run the Codex monitor Stop replay outside the hook handler frame - #14715

Merged
teamleaderleo merged 2 commits into
mainfrom
fix/cli-agent-hook-stack
Sep 25, 2026
Merged

teamleaderleo merged 2 commits into
mainfrom
fix/cli-agent-hook-stack

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes a latent stack overflow (SIGBUS) in the cmux CLI's Codex monitor path.

Root cause. cmux hooks codex monitor watches the rollout, and when the turn completes it replays a Stop event. On main that replay called runGenericAgentHook from a closure passed into runCodexTranscriptMonitor, which was itself called from inside runGenericAgentHook. So two copies of the hook handler frame were live at once. That frame is very large (about 175 KB of inlined locals, measured in the lane working on #13161), and the whole CLI runs on a Swift concurrency pool thread: CMUXTermMain.main() async awaits the nonisolated CMUXCLI.run(), which in Swift 5 mode hops to the global cooperative executor, whose threads have a 512 KB stack. Two nested handler frames plus the run() dispatch frames overflowed it. The CLI product test hit this twice while #13161 was growing the handler.

Fix (non-recursive replay).

  • runCodexTranscriptMonitor no longer takes a replay callback. It returns the CodexTranscriptMonitorStopReplay? for a healthy completion.
  • runGenericAgentHook is now a thin dispatcher. For codex monitor it emits the same codex-hook.monitor breadcrumb, runs the monitor, and after the monitor returns it runs the Stop event with the replay payload. Every other event goes straight to the handler.
  • The old body is now runGenericAgentHookEvent, marked @inline(never) so the compiler cannot merge its frame into the dispatcher. runCodexTranscriptMonitor is @inline(never) too, so its locals are gone before the replayed Stop runs.

Why the frame is now bounded. Only the dispatcher calls runGenericAgentHookEvent, and the handler body never calls back into runGenericAgentHook. The monitor frame has fully unwound before the Stop replay starts. So at most one handler frame is on the stack at any time, no matter how large the handler grows.

Behavior. Unchanged, with one ordering detail: the monitor removes its own lease file when it returns, which is now just before the replayed Stop instead of just after. Stop only retires leases (marks them), and nothing reads a lease except retire, prune, and the monitor's own retired check, so the end state is the same.

Testing

  • Existing coverage: cmuxCLITests/CLICodexResumeNotificationTests "The rollout monitor recovers a dropped resumed Stop hook" runs the bundled CLI with hooks codex monitor against a completed rollout and asserts the replayed Stop notifies. That is the path that crashed, and it now runs with one handler frame.
  • No new stack-depth test: the cooperative pool stack size cannot be set from a test, so a deterministic overflow test would only prove the frame size of one compiler build.

🤖 Generated with Claude Code

The codex monitor replayed Stop by calling runGenericAgentHook from a
closure inside runGenericAgentHook, so two copies of that very large
frame were live at once on the CLI's 512 KB cooperative-pool stack.
The monitor now returns the replay and a thin dispatcher runs the Stop
event after the monitor frame unwinds. The handler body is @inline(never)
so it is never merged into the dispatcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 25, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Warning

Some tools did not complete. Review the errors below.

🔧 OpenGrep (1.30.0)
CLI/cmux.swift

OpenGrep scan timed out


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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit 6693884 into main Sep 25, 2026
15 of 16 checks passed
@teamleaderleo
teamleaderleo deleted the fix/cli-agent-hook-stack branch September 25, 2026 20:42
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for ad18184348, merged 2026-09-25 20:42:54 UTC

  • Not verified at merge: ci-status (not reported), Web complexity (in progress)
  • Verified: web-validation, Testbox broker trust boundary
  • Skipped by policy: web-build, web-database-tests, web-tests
  • Full suite: runs on main after merge.

Labeled merged-unverified: if main breaks near this merge, look here first.

@github-actions github-actions Bot added the merged-unverified A judging check was not green at merge; see the merge receipt comment label Sep 25, 2026
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 25, 2026
b63ab49 Fail remote-tmux review when a wait is a timer instead of an event (manaflow-ai#11264)
b436c92 Fail closed when CLI forwarding loops back to the GUI binary (manaflow-ai#8788)
136eb2a fix(ios): make the last intermittent CmuxMobileShell tests deterministic (manaflow-ai#14721)
dc7f5bd fix(ios): keep terminal composer dock at bottom (manaflow-ai#14702)
cb4429b ci: add owned_build_state.py warm-keys for warm admission routing (manaflow-ai#14717)
be1ab5e ci(ios): charge in-flight auto runs simulators only when they took the fleet (manaflow-ai#14716)
5110582 ci: clear fixed DerivedData by renaming it aside first (manaflow-ai#14710)
1b8a603 fix(ios): unregister terminal output streams by registration identity; fix stale render-grid tests (manaflow-ai#14711)
3cae7dd refactor: move the browser WebKit support layer into CmuxBrowser (manaflow-ai#14398)
6693884 Run the Codex monitor Stop replay outside the hook handler frame (manaflow-ai#14715)
133a083 ci(e2e): take the owned Mac's gui token just before testing in the build (manaflow-ai#14705)
4c36ace ci: re-run every job of an E2E run whose build did not succeed (manaflow-ai#14712)
ab5ac72 ci: start owned E2E builds from the Mac's kept state, upload after tests (manaflow-ai#14692)
6a3d2d0 test(ios): align Mac switch and pool tests with build-scoped identity (manaflow-ai#14708)
0b16a8b Keep closePanel's unmapped fallback from closing another panel's tab (manaflow-ai#14704)
69c9518 close-surface: reject a blank --workspace or --window (manaflow-ai#14706)
8476043 ci: route warm admission by static runner labels, never write labels (manaflow-ai#14696)
537d53f Guard direct GhosttyKit setup against incompatible Zig (manaflow-ai#4706)
4b200e1 Restore Pi wakeup alerts across reloads (manaflow-ai#12861)
9b291c0 ci: resolve an owned Mac's kept Swift packages offline (manaflow-ai#14709)
31c9105 Stop attaching the unverified per-resume relay MAC (manaflow-ai#14694)
5443f43 Merge pull request manaflow-ai#13565 from manaflow-ai/feat-recover-forgotten-computers
e817240 Add app.equalizeSplitsOnCreate to balance panes on new splits (manaflow-ai#14703)
a358095 fix: never use a shell bootstrap executable in a resume binding (manaflow-ai#5848)
3f177af Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
8b4c97b Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
dce9509 ci: update iOS checkout routing assertion
5dbf06c ci: update CLA workflow digest after main pin
44c00fb Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
d22189d Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
eeca51a fix: make event reconnect policy instance based
9e29c96 Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
d860564 Model first Cloud receipt before remote graph discovery
87f1305 Verify forgotten Mac recovery with the real paired store
2237f8c Merge main and retain upstream test repairs
9ccaa7a Avoid type-check timeout in process fixture
a848b5e Simplify AppKit accessibility test setup
82a9769 Isolate process generation fixture from runner TTY state
4c129bb Enable and restore AppKit assistive access in mounted tree test
9a9acca Enable accessibility output in the hosted SwiftUI test fixture
5ce39d0 Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
a29a7ec Read proxy accessibility children and text through one attribute bridge
12938b6 Test accessibility walkers against modern and legacy proxy nodes
04a30c5 Revoke the original pairing in the targeted dial authority regression
ff1888e Import the extracted Cloud module in the team picker
9c9f259 Merge main and adopt the verified focus recovery fixture
5d41fc8 Align hosted UI fixtures with runtime paths and presentation lifecycle
87dbbd9 Preserve unknown legacy restore liveness after main integration
41a8d4b Revert "Establish running agent state in auto-resume fixtures"
6c7d14b Revert "Use the running-agent fixture for second-restore cwd coverage"
9d276b5 Route Kiro permission-mode fixtures through the mock delivery target
cdb4eae Restore the host app delegate after registration tests
4bdc410 Use the running-agent fixture for second-restore cwd coverage
0b58cbc Establish running agent state in auto-resume fixtures
20c6415 Wait for mounted project content in accessibility test
3ba7b6a Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
2c3c680 Make reparent focus test geometry deterministic
d2f563b Stabilize canonical cache recipe guard
cc60792 Merge current main into forgotten Mac recovery
7dc515f Merge branch 'main' into feat-recover-forgotten-computers
cfd4773 Preserve per-instance forgotten Mac recovery
6a95584 Canonicalize recovered directory identities
f083246 Preserve queued forgotten Mac refreshes
e5202a6 test(ios): use canonical UUID duplicate fixture
ca7ee53 fix(ios): serialize forgotten Mac recovery retries
db851c2 Merge remote-tracking branch 'origin/main' into feat-recover-forgotten-computers
35269cb fix(ios): rehydrate Macs after forget recovery
d0266b7 Merge current CI workflow identity guard into device recovery
45b7c72 fix(i18n): distinguish signed-in Mac recovery from connectivity
730260c Merge remote-tracking branch 'origin/feat-recover-forgotten-computers' into feat-recover-forgotten-computers
a17f8dd fix: complete forgotten Mac recovery without replaying revocation
d64766a test: reproduce revocation during device recovery registration
faf5b55 test: keep recovery authority limited to enrollment
a9c437a test: cover duplicate forget and recovery lifecycle gaps
033e7a0 chore: normalize project ordering
28f63ec fix: recover forgotten Macs from revocation events
7b8e322 fix: recover forgotten Mac registrations
f1aca40 test: validate recovery enrollment proof
62b4e14 test: cover authenticated forgotten-device recovery
9cb2f97 test: cover recovery after device forget

# Conflicts:
#	.github/workflows/app-host-test-rerun.yml
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci-macos.yml
#	.github/workflows/ci-queue-janitor.yml
#	.github/workflows/ci.yml
#	.github/workflows/nightly.yml
#	.github/workflows/seed-derived-data.yml
#	.github/workflows/test-e2e.yml
#	.github/workflows/test-ios.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged-unverified A judging check was not green at merge; see the merge receipt comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant