Skip to content

fix(ios): accept the Mac's push key exchange (device id) and allow Simulator push verification - #14292

Merged
azooz2003-bit merged 2 commits into
mainfrom
feat-push-sim-verify
Sep 25, 2026
Merged

azooz2003-bit merged 2 commits into
mainfrom
feat-push-sim-verify

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Encrypted pushes still showed "cmux / An agent needs your attention" after #14039, #14110, and #14267. A Release com.cmux.app running on an isolated Simulator, paired with a nightly Mac, logged the real cause:

[ai.manaflow.cmux:phone-push-key-exchange] key exchange reply rejected: mac_device

The phone compared the key-exchange reply's mac_device_id with the host status mac_device_id, but they identify different things. The status carries the team-directory computer identity (authorizedDeviceID ?? v2DeviceID in MobileHostTransportAuthorization), while the reply carries MobileHostIdentity.deviceID(), the physical identity that push tuples use. Every exchange was rejected, so the phone never pinned the Mac's key, and the notification extension failed each push with sender_not_pinned.

Changes:

  • Reply validation: moves to MobilePhonePushKeyExchangeResponse.mismatchedFields, which compares account, instance tag, and build namespace. The pin still uses the reply's physical device ID, which matches the tuple the Mac encrypts with.
  • Exchange diagnostics: a rejected or skipped exchange now logs which field failed. Before, it logged only "attempt failed", which is why three earlier rounds missed this.
  • Simulator APNs environment: a Release build on the Simulator registers its token as sandbox. The Simulator only issues sandbox tokens, so without this APNs rejected every push and the official app could not be push-verified there.

Testing

  • CMUXMobileCore: new MobilePhonePushKeyExchangeResponseTests cover a reply whose device ID differs from the status, which is accepted, and contradicting account, tag, or namespace fields, which are named.
  • CmuxMobileShell builds for arm64-apple-ios17.0-simulator.
  • End-to-end run on the Simulator with a Release com.cmux.app: CI builds with the App Store lane's settings, ad-hoc signed so the release entitlements are simulated. The app signs in with the personal account, pairs with a nightly Mac, then receives a push. Before this change the run logged the mac_device rejection above. With this change, the same Simulator session connected with no key-exchange rejection, and the nightly Mac pinned the Simulator's installation b926b6cf. After fix(web): store sandbox for production-bundle installs that declare it #14296 deployed, the install re-registered as sandbox. A real cmux notify from the nightly Mac returned APNs 200 for that install. The Simulator's notification extension decrypted the push without logging a failure reason, and the lock-screen banner read "Push test 19:42:06: If you can read this line, the phone decrypted the push."

🤖 Generated with Claude Code

azooz2003-bit and others added 2 commits September 24, 2026 14:01
…nge rejections

A Release build running in the Simulator registered its device token as
production, but the Simulator only mints sandbox tokens, so APNs rejected
every push and the official app could never be push-verified there.

The push key exchange also failed silently: a rejected reply or missing
context logged only "attempt failed". It now names the mismatched or
missing field (never its value) so a device log shows why the Mac's key
was not pinned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone compared the key-exchange reply's mac_device_id with the host
status mac_device_id. The status carries the team-directory computer
identity, the reply carries the physical device identity that push
tuples use, so every exchange was rejected ("key exchange reply
rejected: mac_device" in a Simulator run of the Release com.cmux.app)
and the Mac's key was never pinned.

Reply validation moves onto MobilePhonePushKeyExchangeResponse
(mismatchedFields) and compares only account, instance tag, and build
namespace; the pin keeps the reply's physical device id, which matches
the tuple the Mac encrypts with.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@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 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e73bda51-6f79-42f6-84e0-26c5483e9a77

📥 Commits

Reviewing files that changed from the base of the PR and between fb1759a and c885739.

📒 Files selected for processing (4)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobilePhonePushKeyExchange.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobilePhonePushKeyExchangeResponseTests.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PhonePushKeyExchange.swift
  • ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Phone push key exchange no longer requires a Mac device ID and now reports mismatched reply fields. APNs environment selection now uses sandbox for Simulator builds as well as DEBUG builds.

Changes

Phone push key exchange

Layer / File(s) Summary
Reply mismatch contract and tests
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobilePhonePushKeyExchange.swift, Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobilePhonePushKeyExchangeResponseTests.swift
Adds mismatchedFields to identify account, instance tag, and namespace mismatches. Tests cover matching and conflicting identity fields.
Exchange context and reply handling
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PhonePushKeyExchange.swift
Removes the Mac device ID requirement and comparison. Logs missing context fields, reply mismatch names, and caught error details. Existing failure and retry handling remains in place.

APNs environment selection

Layer / File(s) Summary
Simulator APNs environment
ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift
Selects sandbox when DEBUG is set or the app runs in the Simulator. Other builds continue to select production.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to c8857

No actionable merge-blocking risk is established for the push key exchange or Simulator APNs changes. Normal validation can proceed before merging.

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description provides a detailed Summary and Testing section, including added tests and an end-to-end Simulator result. However, it omits the required Demo Video or screenshots and the repository C… Add the required Demo Video or screenshots for this behavior change. Include the Checklist and address each applicable item, especially deterministic soak coverage, docs or changelog updates, localization if applicable, and bot-review statu…
✅ Passed checks (23 passed)
Check name Status Explanation
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 Cloud Persistent Session And Early Input ✅ Passed PASS: The pull request changes only iOS push key exchange validation, diagnostics, APNs Simulator environment selection, and related tests. The authoritative diff contains no Cloud terminal creation, …
Cmux Swift Actor Isolation ✅ Passed PASS. The PR adds only a pure comparison method to the existing Codable, Equatable, Sendable value model MobilePhonePushKeyExchangeResponse. It adds no implicit MainActor model, service protocol, …
Cmux Swift Blocking Runtime ✅ Passed The production diff adds no blocking or timing primitive. The only Task.sleep(for:) remains the existing retry backoff at the same location and is not introduced or expanded. Other changes add field…
Cmux Browser Automation Off-Main ✅ Passed The pull request changes only iOS/mobile push key exchange and APNs environment code. Neither rule-scoped browser automation file changed, and the patch contains no browser routing, WebKit, AppKit, or…
Cmux Expensive Synchronous Load ✅ Passed The PR changes push key-exchange validation, diagnostics, tests, and Simulator APNs environment selection. The authoritative diff adds no agent-history loader, filesystem scan, JSON/JSONL parsing, tra…
Cmux Cache Substitution Correctness ✅ Passed The diff does not replace a fresh authoritative read with a cached or opportunistic value. It validates the fresh exchangePhonePushKey response against the current host-status fields, then passes th…
Cmux No Hacky Sleeps ✅ Passed PASS: The pull request changes only Swift source and Swift tests. The custom check applies to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts. The visible Task.sleep is existing S…
Cmux Algorithmic Complexity ✅ Passed The production changes use only fixed-size collections. mismatchedFields compacts three fields, and missing-context diagnostics compact four fields. The exchange retry loop is explicitly limited to …
Cmux Swift Concurrency ✅ Passed PASS. The diff adds no background Dispatch queues, Combine state, completion-handler APIs, or new fire-and-forget Tasks. The existing phone push exchange Task remains stored in `phonePushKeyExchangeRe…
Cmux Swift @Concurrent ✅ Passed The PR introduces no @concurrent or nonisolated async declaration. mismatchedFields and apnsEnvironment are synchronous helpers. The existing network helper remains in the @MainActor extensi…
Cmux Swift Package Boundaries ✅ Passed PASS. The changed key-exchange domain API is in the existing CMUXMobileCore SwiftPM target, with tests in its test target. The exchange orchestration is in the existing CmuxMobileShell SwiftPM tar…
Cmux Swiftpm Lockfiles ✅ Passed The PR changes only Swift source and test files. The authoritative diff contains no Package.swift, Package.resolved, .gitignore, workflow, Xcode project, or workspace changes. It therefore introduces …
Cmux Swift Logging ✅ Passed The added diagnostics use the existing OSLog phonePushKeyExchangeLog.error at lines 41–43, 101–103, and 120–122. They log only field names and error type publicly; the error description uses `.priva…
Cmux User-Facing Error Privacy ✅ Passed The changed messages are emitted only through OSLog.Logger in MobileShellComposite+PhonePushKeyExchange. The diff does not route them to app UI, product CLI output, API responses, or the in-app di…
Cmux Full Internationalization ✅ Passed The PR does not introduce user-facing UI or web text. The added prose is emitted through OSLog for key-exchange diagnostics, and the added account/mac_* values and sandbox/production values …
Cmux Swiftui State Layout ✅ Passed PASS. The PR changes push key-exchange models, exchange handling, tests, and APNs environment selection. The diff adds no SwiftUI views, ObservableObject state, layout readers, lazy/list row store ref…
Cmux Architecture Rethink ✅ Passed PASS. The diff contains a local pure validation helper, diagnostic logging, and a required targetEnvironment(simulator) APNs bridge. It does not add locks, observers, polling, delayed dispatch, dupl…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS. The PR changes shared push-exchange logic, iOS mobile-shell logic, iOS authentication configuration, and tests. The diff adds or changes no NSWindow, NSPanel, NSWindowController, SwiftUI Window,…
Cmux Source Artifacts ✅ Passed All four changed paths are hand-written Swift source or test files under established Sources and Tests directories. The diff adds no logs, screenshots, recordings, caches, build output, dependency…
Cmux No Test Or Debug Seam In Production Source ✅ Passed No prohibited test or debug seam was added. The new production member MobilePhonePushKeyExchangeResponse.mismatchedFields(...) has a real production caller in `MobileShellComposite+PhonePushKeyExcha…
Title check ✅ Passed The title clearly identifies both primary changes: accepting the Mac push key exchange and enabling Simulator push verification.
Full details: Description check

Explanation

The description provides a detailed Summary and Testing section, including added tests and an end-to-end Simulator result. However, it omits the required Demo Video or screenshots and the repository Checklist, including deterministic soak coverage, documentation status, and bot-review confirmation.

Resolution

Add the required Demo Video or screenshots for this behavior change. Include the Checklist and address each applicable item, especially deterministic soak coverage, docs or changelog updates, localization if applicable, and bot-review status.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@azooz2003-bit
azooz2003-bit merged commit 2f7bd16 into main Sep 25, 2026
66 of 67 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-push-sim-verify branch September 25, 2026 02:45
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 25, 2026
cbe0bd9 ci: seed the macOS 15 pool with its own Xcode (manaflow-ai#14315)
5fab6f5 refactor: move CmuxWebView into CmuxBrowser behind an injected host (manaflow-ai#14321)
8475872 Merge pull request manaflow-ai#14335 from manaflow-ai/13458-safe-device-rollout
2f7bd16 fix(ios): accept the Mac's push key exchange (device id) and allow Simulator push verification (manaflow-ai#14292)
fd66cc7 ci: give an owned Mac's second compile slot its own canonical root (manaflow-ai#14338)
af4097b ci: build cmuxTests without the compilation cache so it rebuilds incrementally (manaflow-ai#14349)
fe61107 ci: replay input times onto an owned Mac's kept DerivedData (manaflow-ai#14346)
f7b8848 Freeze the historical socket migration in the rollback fixture
73c3a07 fix(web): store sandbox for production-bundle installs that declare it (manaflow-ai#14296)
c04616b Merge remote-tracking branch 'origin/main' into 13458-safe-device-rollout
459d89c ci: read the owned pools' free machines live through the org route App (manaflow-ai#14350)
2b7afe3 ci: give the iOS upload workflows the R2 cache URL (manaflow-ai#14347)
359f14c test: tie the E2E stale-snapshot case to OWNED_MAX_AGE_MINUTES (manaflow-ai#14348)
1fcef82 Update CI guard expectations and require the passing layout regression
9b5a251 Merge remote-tracking branch 'origin/main' into 13458-safe-device-rollout
60ab69a Exercise remote mirror pane replacement in the workspace regression
7a0ba5d Merge remote-tracking branch 'origin/main' into 13458-safe-device-rollout
1649314 Preserve remote Mac workspaces across sidebar creation and pane replacement
52fec11 Observe asynchronous remote cleanup in the creation regression
a93af4d Reproduce remote workspace deletion when its local placeholder is replaced
bc0a0ad Test sidebar workspace creation preserves the remote Mac target
93aff4d ci: quote development Worker revision arguments
24475c2 Merge remote-tracking branch 'origin/main' into 13458-safe-device-rollout
57331a9 fix: make Devices rollout preserve SQLite rollback compatibility
448eeb2 test: reproduce unsafe Devices rollout assumptions

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci-macos.yml
#	.github/workflows/ci.yml
#	.github/workflows/ios-appstore-upload.yml
#	.github/workflows/ios-testflight.yml
#	.github/workflows/iroh-v2-production-drift.yml
#	.github/workflows/iroh-v2.yml
#	.github/workflows/seed-derived-data.yml
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