Skip to content

Fix Codex resume notification rebinding - #9185

Merged
austinywang merged 3 commits into
mainfrom
issue-9181-codex-resume-notifications
Jul 30, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-9181-codex-resume-notifications

Conversation

@austinywang

@austinywang austinywang commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep Codex hook injection and cmux surface/workspace context intact across every launch and transient socket failure without predicting resume state from argv
  • prefer the live Codex wrapper PID on lifecycle events and replay authoritative rollout task_complete events through the shared Stop reducer, with native Stop deduplication
  • fail closed when an excluded session is absent from the store, and make binding/delivery failures observable in release builds with cached, scrubbed, persistently throttled logging/Sentry telemetry

Root-cause verification

Live evidence contradicted the proposed end-to-end diagnosis in #9181. The argv parser limitation, nil excludedUpdatedAt fallthrough, debug-only delivery logging, and context-stripping passthrough were real defects, but they were not what dropped the reproduced picker-resume notification.

With an isolated Codex 0.145.0 picker resume, Codex emitted SessionStart, the original session ID and rollout file were reused, and the store rebound to the live PID. The rollout then recorded a healthy task_complete. Delivery was lost later: the detached Stop path exited before the generic hook reducer reached agentHook.start.

The rollout identity check confirmed resume appended to the original file: session_id_match=yes same_inode=yes appended=yes matching_rollouts=1.

Architecture

The wrapper no longer scrapes resume argv or gates hook installation on launch-time socket health. Every Codex entrypoint receives the same hooks and retains cmux context, while fresh launches still rely on Codex's authoritative SessionStart and therefore do not mint fallback GUI phantoms.

Codex lifecycle events use the live wrapper PID when available. The transcript monitor converts authoritative rollout task_complete records into the existing generic Stop path rather than implementing a second notification reducer, and late native Stops are deduplicated. This shared path covers explicit UUID, --last, picker, in-TUI resume, and future resume forms.

Tests

No local xcodebuild or XCUITest was run, per repository instructions. Localization audit: no user-facing strings were added or changed; new messages are developer-only unified logging/Sentry telemetry.

Closes #9181

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Updates Codex launch instrumentation, hook failure reporting, transcript stop replay, and resumed-session notification tests. The Xcode project registers the new Swift sources and test suite.

Changes

Codex resume reliability

Layer / File(s) Summary
Wrapper entrypoint instrumentation
Resources/bin/cmux-codex-wrapper
Removes argv-based resume detection and socket gating, clears inherited launch markers, and preserves cmux context when hook-argument injection fails.
Hook failure state and reporting
CLI/AgentHookFailureStage.swift, CLI/CMUXCLI+AgentHookFailureReporting.swift, CLI/ClaudeHookSessionStoreFile.swift, CLI/cmux.swift, CLI/CodexHook*
Adds failure-stage/data types, throttled failure persistence and telemetry, Codex PID selection, and failure reporting at target resolution and notification delivery.
Transcript monitor stop replay
CLI/CodexTranscript*, CLI/cmux.swift
Carries the last assistant message through transcript health results and replays monitor completion through the generic Stop hook.
Resume notification and wrapper validation
cmuxTests/CLICodexResumeNotificationTests.swift, tests/test_codex_wrapper_resume_hooks.py
Tests stale-PID rebinding, fail-closed notifications, monitor replay deduplication, resume routes, fork launches, restore tokens, stale sockets, and injection failures.
Xcode source registration
cmux.xcodeproj/project.pbxproj
Registers the new Swift implementation and test files in project references, groups, build files, and source phases.

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

Possibly related issues

  • manaflow-ai/cmux-dev-artifacts#7050: Covers the same resumed-session notification, monitor recovery, and deduplication behavior.
  • manaflow-ai/cmux-dev-artifacts#7043: Directly references the added resumed-session notification test suite.

Possibly related PRs

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error New Codex hook/session/replay value models and reporting helpers were added to app-target CLI files, even though they’re reusable domain logic and already fit a package boundary. Move the Codex hook/session/replay models and failure-reporting helper into a small package (likely Packages/macOS/CMUXAgentLaunch), exposing a narrow value API such as CodexHookFailureCandidate.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #9181 by removing argv-based resume detection, rebinding via real hook/session state and live PID, and failing closed on missing records.
Out of Scope Changes check ✅ Passed The new types, wrapper changes, tests, and failure-reporting logic all support the resume rebinding fix; no clear unrelated additions stand out.
Cmux Swift Actor Isolation ✅ Passed No touched production type is under @MainActor; new structs/enums are plain value types, the logger is nonisolated, and the store remains lock-protected.
Cmux Swift Blocking Runtime ✅ Passed No new production Swift blocking primitives appeared; added code is data/replay plumbing, and the only lock use is existing store flock-based state handling.
Cmux Browser Automation Off-Main ✅ Passed No changed file touches browser automation router/policy; diff is Codex resume hooks and PBX wiring only, so the rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed PASS: The diff only adds replay/error-reporting around existing codex monitor/store code; no new RestorableAgentSessionIndex.load() or main-actor sync agent-history load is introduced.
Cmux Cache Substitution Correctness ✅ Passed Codex now prefers fresh inferred CMUX_CODEX_PID, falls back only when inference is absent, and stale/missing-record paths fail closed.
Cmux No Hacky Sleeps ✅ Passed No new fixed sleeps, polling loops, or timer-based coordination were introduced in the changed shell/runtime code; the wrapper remains event-driven.
Cmux Algorithmic Complexity ✅ Passed New scans are single-pass or capped: session lookups are O(n) once, and the only sorts are on maps capped at 16 and 64 items.
Cmux Swift Concurrency ✅ Passed Changed Swift code adds only synchronous helpers/types; no new DispatchQueue, Task, Combine, or async-callback patterns appear in the diff.
Cmux Swift @Concurrent ✅ Passed PASS: the PR adds only synchronous helpers/structs; no changed async or actor-isolated functions use/omit @concurrent, and no UI-bound async work was introduced.
Cmux Swiftpm Lockfiles ✅ Passed PR only adds Swift source files and pbxproj file/build entries; no .gitignore, Package.swift, or package-reference edits, and no Package.resolved diff is needed.
Cmux Swift Logging ✅ Passed New Swift logging uses a nonisolated private Logger and sanitized OSLog fields; no added print/debugPrint/dump/NSLog or sensitive data exposure in changed runtime code.
Cmux User-Facing Error Privacy ✅ Passed PASS: The new failure reporting stays in internal OSLog/telemetry, masks session IDs, and adds no new user-visible error copy.
Cmux Full Internationalization ✅ Passed No production user-facing text was added; changes are tests, comments, model enums, and developer-only logging/telemetry.
Cmux Swiftui State Layout ✅ Passed Touched Swift files are CLI/data models; diff adds no SwiftUI state/layout patterns like ObservableObject, GeometryReader, or lazy row store refs.
Cmux Architecture Rethink ✅ Passed PASS: Changes are local correctness fixes with clear owners; no new sleeps, observers, delayed dispatch, or split lifecycle ownership were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only adds CLI/data structs/tests; no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes, so the auxiliary-window rule isn’t triggered.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/config/test/script files; none match artifact or scratch-directory patterns in the rule.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No changed Swift file is under a production Sources/ path, so the no-test/debug-seam rule doesn’t apply.
Cmux No Ambient Global State ✅ Passed No new ambient global state found: added code is extension methods plus ordinary enums/structs; the only file-scope logger is a private immutable let, not a singleton or mutable global.
Title check ✅ Passed The title is concise and accurately summarizes the main change: Codex resume notification rebinding.
Description check ✅ Passed The description covers summary, root-cause analysis, architecture, and testing; it only omits template extras like demo video, review trigger, and checklist.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-9181-codex-resume-notifications

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.

@austinywang
austinywang marked this pull request as ready for review July 29, 2026 23:21

@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: 3

🤖 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 `@CLI/CMUXCLI`+AgentHookFailureReporting.swift:
- Around line 4-7: Mark the file-scoped agentHookDeliveryLogger declaration as
nonisolated, preserving its existing Logger initialization and
subsystem/category values.
- Around line 26-37: Update the failure reporting around agentHookDeliveryLogger
and telemetry.captureError to scrub/filter the error before it reaches any
observability sink. Log the redacted error with private or hashed OSLog privacy
instead of public, and ensure the raw error is not included in shared
cli_socket.error context or Sentry serialization; preserve the existing agent,
session, and delivery-stage metadata.

In `@tests/test_codex_wrapper_resume_hooks.py`:
- Around line 199-217: Extend the socket-state coverage in
test_every_resume_route_is_instrumented to iterate over missing, stale, and
live, and update test_direct_fork_is_instrumented to assert the live state
alongside stale. Keep the existing entrypoint routes and assertions unchanged.
🪄 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 Plus

Run ID: 85723cf3-a8ba-473b-9205-46cbd52a935d

📥 Commits

Reviewing files that changed from the base of the PR and between 93bfd5e and bd98599.

📒 Files selected for processing (6)
  • CLI/CMUXCLI+AgentHookFailureReporting.swift
  • CLI/cmux.swift
  • Resources/bin/cmux-codex-wrapper
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CLICodexResumeNotificationTests.swift
  • tests/test_codex_wrapper_resume_hooks.py

Comment thread CLI/CMUXCLI+AgentHookFailureReporting.swift Outdated
Comment thread CLI/CMUXCLI+AgentHookFailureReporting.swift
Comment thread tests/test_codex_wrapper_resume_hooks.py
@austinywang
austinywang force-pushed the issue-9181-codex-resume-notifications branch from bd98599 to 077b090 Compare July 30, 2026 00:59

@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: 2

🤖 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 `@CLI/cmux.swift`:
- Line 30946: Update preferredAgentHookEventPID so non-codex hooks (resume,
stop, approvalResponse, and notification) prefer inferredPID whenever no live
mapped process exists, using mappedPID only when the mapped process is live and
falling back to the stored PID when inferredPID is unavailable.

In `@tests/test_codex_wrapper_resume_hooks.py`:
- Around line 263-276: Update test_injection_failure_preserves_cmux_context to
also assert that CMUX_CODEX_PID and CMUX_AGENT_LAUNCH_KIND remain present and
correct in observed_env when injection fails, matching the identity-context
checks used by assert_session_entrypoint_is_instrumented while preserving the
existing workspace and surface assertions.
🪄 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 Plus

Run ID: 51546cea-1245-4b44-b8fe-13a0349e84b0

📥 Commits

Reviewing files that changed from the base of the PR and between bd98599 and 077b090.

📒 Files selected for processing (6)
  • CLI/CMUXCLI+AgentHookFailureReporting.swift
  • CLI/cmux.swift
  • Resources/bin/cmux-codex-wrapper
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CLICodexResumeNotificationTests.swift
  • tests/test_codex_wrapper_resume_hooks.py

Comment thread CLI/cmux.swift
Comment thread tests/test_codex_wrapper_resume_hooks.py
@austinywang
austinywang force-pushed the issue-9181-codex-resume-notifications branch from 077b090 to 713c875 Compare July 30, 2026 01:11
@cursor

cursor Bot commented Jul 30, 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.

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

♻️ Duplicate comments (1)
tests/test_codex_wrapper_resume_hooks.py (1)

264-277: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Still missing Codex identity assertions on injection failure.

test_injection_failure_preserves_cmux_context verifies workspace/surface bindings survive, but doesn't check CMUX_CODEX_PID/CMUX_AGENT_LAUNCH_KIND the way assert_session_entrypoint_is_instrumented does for the success path. The fake codex script always logs these regardless of injection outcome, so this gap is easy to close and matters for the "preserve context during failures" resilience goal this PR targets. This was already flagged on a prior commit and remains unaddressed.

♻️ Proposed addition
     expect(observed_env.get("CMUX_WORKSPACE_ID") == "22222222-2222-2222-2222-222222222222",
            f"inject-failure: workspace binding was stripped: {observed_env}", failures)
+    expect(observed_env.get("CMUX_CODEX_PID") not in {None, "", "__UNSET__"},
+           f"inject-failure: missing Codex process identity: {observed_env}", failures)
+    expect(observed_env.get("CMUX_AGENT_LAUNCH_KIND") == "codex",
+           f"inject-failure: missing launch kind: {observed_env}", failures)
🤖 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 `@tests/test_codex_wrapper_resume_hooks.py` around lines 264 - 277, Update
test_injection_failure_preserves_cmux_context to assert that observed_env
retains the expected CMUX_CODEX_PID and CMUX_AGENT_LAUNCH_KIND values, matching
the identity checks in assert_session_entrypoint_is_instrumented while
preserving the existing surface and workspace assertions.
🤖 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.

Duplicate comments:
In `@tests/test_codex_wrapper_resume_hooks.py`:
- Around line 264-277: Update test_injection_failure_preserves_cmux_context to
assert that observed_env retains the expected CMUX_CODEX_PID and
CMUX_AGENT_LAUNCH_KIND values, matching the identity checks in
assert_session_entrypoint_is_instrumented while preserving the existing surface
and workspace assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e4acd2ca-84f5-43b4-9efc-e5c343ec125d

📥 Commits

Reviewing files that changed from the base of the PR and between 077b090 and 713c875.

📒 Files selected for processing (3)
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CLICodexResumeNotificationTests.swift
  • tests/test_codex_wrapper_resume_hooks.py

@austinywang
austinywang force-pushed the issue-9181-codex-resume-notifications branch from 713c875 to 98c288c Compare July 30, 2026 01:20

@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: 2

🤖 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/CLICodexResumeNotificationTests.swift`:
- Line 17: Replace the Date() calls in CLICodexResumeNotificationTests with the
existing deterministic test-clock mechanism, using one fixed fixture timestamp
consistently for both the test and child process. Ensure all affected timestamp
assertions and inputs avoid reading the host wall clock.

In `@tests/test_codex_wrapper_resume_hooks.py`:
- Line 36: Add an explicit harness control alongside inject_args_available and
set IN_CMUX=1 for the affected test cases so the passthrough branch is exercised
deterministically. Update the fallback logic in cmux-codex-wrapper to preserve
the required CMUX_* context instead of clearing it when IN_CMUX is enabled, and
ensure the tests assert that context remains available.
🪄 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 Plus

Run ID: ef0a7192-8b6a-41ae-aada-bcfa0322c210

📥 Commits

Reviewing files that changed from the base of the PR and between 713c875 and 98c288c.

📒 Files selected for processing (3)
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CLICodexResumeNotificationTests.swift
  • tests/test_codex_wrapper_resume_hooks.py

Comment thread cmuxTests/CLICodexResumeNotificationTests.swift Outdated
Comment thread tests/test_codex_wrapper_resume_hooks.py
@austinywang
austinywang force-pushed the issue-9181-codex-resume-notifications branch from 50f12f3 to 398823c Compare July 30, 2026 02:04
@austinywang
austinywang force-pushed the issue-9181-codex-resume-notifications branch from 398823c to 2453522 Compare July 30, 2026 02:15
…-9181-codex-resume-notifications

# Conflicts:
#	CLI/ClaudeHookSessionStoreFile.swift
#	CLI/cmux.swift
#	cmux.xcodeproj/project.pbxproj
@austinywang
austinywang merged commit 8b087a9 into main Jul 30, 2026
6 checks passed
@austinywang
austinywang deleted the issue-9181-codex-resume-notifications branch July 30, 2026 02:55
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.

Resumed Codex sessions never re-bind to their live pid, silently killing all cmux notifications (wrapper only recognizes codex resume <uuid>)

1 participant