Skip to content

cmux NEXT: a second copy hands off to the running one; no restored Chromium tab loads in WebKit - #25918

Merged
teamleaderleo merged 25 commits into
feat-cmux-nextfrom
hotswap-opus/single-instance
Oct 27, 2026
Merged

teamleaderleo merged 25 commits into
feat-cmux-nextfrom
hotswap-opus/single-instance

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Leo: "why does cmux-next's browser keep refreshing and reloading? this happened before with austin using google maps"

#25791 stopped the hibernation discards and crash auto-reloads. Its field log of the reported GitHub reload showed one more cause, which it only logged: a second copy of the same app.

Cause (field log)

  • 13:25:16: /Applications/cmux NIGHTLY.app (bundle com.cmuxterm.app.nightly, the NEXT nightly) starts through LaunchServices and keeps running.
  • 16:42:02: an inbound ssh session starts on the Mac.
  • 16:42:06: a second copy runs the bundle's main executable directly from that session. Its TCC attribution names sshd-session as the responsible process, and its HIServices BringForward line says launchedByLS=0. It registers with LaunchServices next to the first copy. talagentd logs "Service already exists … Eliding writes from previous service" for the first copy's pid, so the second copy even took over the first copy's window-restoration store.
  • 16:42:11: CEF start failed: CefInitialize failed. The first copy holds the Chromium profile at <Application Support>/<bundle id>/Chromium. The control socket also fails: "another process is listening on …/cmux-nightly.sock".
  • Every restored Chromium tab then loaded its URL in WebKit, which has none of Chromium's cookies, so a signed-in GitHub settings page came up as github.com/login?return_to=….
  • 16:44:38: the second copy exits.

In the last 24 h that was the only copy of a cmux bundle not started by LaunchServices. Nothing in the app or the CLI execs the main binary (createsNewApplicationInstance is only used for third-party apps and the Computer Use helper), and no Sparkle, daemon or login-item path relaunches it. The ssh command that started it isn't in the log.

Fix

  1. One copy per bundle per user (CmuxNextUpdater/Launch/SingleInstanceGuard, SingleInstanceHandoff).
    • The check runs in CmuxNextApp.main right after the environment freeze. That is before the relaunch-focus record, the daemon prestart, any session read, CmuxApplication and CEF.
    • The claim is an exclusive flock on <Application Support>/<bundle id>/instance.lock:
      • it is held for the life of the process (close-on-exec), and the kernel releases it whenever the process ends;
      • two launches racing each other can't both get it;
      • it is per user (per home directory);
      • a file error launches as before.
    • A launch that finds the claim taken never starts as a second copy. It runs a plain NSApplication (accessory, with no saved-window state) that only collects the launch's files and URLs. It starts a helper for them and exits 0.3 s after the last file, URL and helper, so a late event gets its own helper (after 3 s it exits once what it holds has a helper). The helper waits for that process to exit (caffeinate -w, as RollbackSwap.relaunchCommand does), then runs open -b <bundle id> <urls>.
    • LaunchServices then delivers the URLs to the running copy and brings it forward; a bare launch reopens it, as a Dock click does. If that copy quit in the meantime, LaunchServices starts a new copy, which takes the claim like any launch.
    • An agent launch (CMUX_NEXT_NO_ACTIVATE=1) hands nothing on and exits, because any open would bring the user's copy forward.
    • These are not guarded:
      • DEV and staging bundles (com.cmuxterm.app.debug*, com.cmuxterm.app.staging*): harnesses run a tagged bundle next to another copy of it;
      • CMUX_NEXT_ALLOW_SECOND_INSTANCE=1.
    • DEV, NIGHTLY and stable have different bundle ids, so they have different locks and still run side by side.
    • Sparkle's relaunch is unaffected: its installer waits for the old pid to exit before it opens the new build, so the lock is free by then. The relaunch-focus record (cmux NEXT: an automatic update no longer pulls you into the app; it relaunches behind the app you are in #25648) is read after the guard, so a hand-off never uses it up.
    • LSMultipleInstancesProhibited is not set: it does nothing against a direct exec, which is the observed cause.
    • Gates: one reviewed blocking-allow (an arch-ratchet.json exception) for the claim's launch-time mkdir and flock, which run on the main thread before NSApp exists.
  2. No silent WebKit restore (TabContentCache+Deferred, CEFUnavailableReason.keepsChromiumTabsOutOfWebKit).
    • If Chromium failed to start (startFailed), a Chromium tab becomes a "Chromium unavailable" page in the tab's own profile. Its URL never loads in WebKit.
    • CefInitialize runs once per process, so the notice asks the user to quit and reopen cmux.
    • Reload or a typed address asks the engine again. While Chromium still can't start, the tab waits on the requested address and writes it to the tab's record, so the restart opens it (not for incognito tabs). The writes are chained per tab, and quit's record flush waits for them last, including one typed during the flush.
    • A stand-in tab that moved to its daemon tab retries under the daemon tab.
    • Builds without Chromium (notBundled) or still downloading it keep their WebKit fallback and notice.

Tests

  • SingleInstanceGuardTests:
    • the decision: a taken claim never starts; held or unavailable launches;
    • an agent launch;
    • DEV, tagged DEV, staging and the opt-out are not guarded; release bundles are;
    • the real flock claim is exclusive;
    • each bundle id has its own lock;
    • the hand-off helper's command, and the helper actually run (with open swapped for echo) to show it passes the bundle id and only the URLs.
  • ChromiumUnavailableRestoreTests:
    • only startFailed holds;
    • a Chromium tab gets the stand-in with its URL and no WebKit page;
    • Reload asks again and still makes no WebKit page;
    • a typed address is kept and written to the record, and quit's flush waits for it;
    • typing B and then the record's own address back restores the last one;
    • an address typed while quit's flush waits is waited for too;
    • a plain Reload follows a changed record and writes nothing;
    • an incognito tab keeps its address across retries and writes nothing;
    • a moved stand-in retries under its daemon tab;
    • a notBundled build still opens the tab in WebKit.
  • ChromiumNewTabFirstFrameTests and CmdClickOpensRightTests still pass.

Risk

  • Medium: the app's launch path for release bundles.
  • A false hand-off needs a live process holding the per-user lock, and the kernel releases the lock when that process ends.
  • Not tested end to end:
    • a second live copy handing a URL to the first through LaunchServices;
    • the hand-off process may briefly show in the app switcher.
  • No UI screenshot (see the next line).

No capture: the only visible change is the text of the existing deferred-page notice bar. It shows only when CefInitialize fails because another live copy holds the Chromium profile, which no capture scene can stage.

Agent session: agent session

🤖 Generated with Claude Code

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 6d8f7bc: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr25918 -f ref=hotswap-opus/single-instance -f platform=macos -f backend_mode=local

web and size checks: green

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 27, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
nightly.yml must derive the embedded key and sign every appcast through the helper/
nightly.yml must derive the embedded key and sign every appcast through the helper
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-5l5uf created for this PR by the cmux coordinator.

@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

teamleaderleo and others added 7 commits October 26, 2026 22:44
…romium tab loads in WebKit

Leo: "why does cmux-next's browser keep refreshing and reloading? this
happened before with austin using google maps"

- A second copy of the same release bundle for the same user (a direct
  exec from an ssh session, `open -n`) sends its URLs and files to the
  running copy's pid, brings it forward, and exits, before it reads the
  relaunch-focus record, a session, the daemon or CEF. DEV and staging
  bundles and CMUX_NEXT_ALLOW_SECOND_INSTANCE=1 are not guarded.
- A restored Chromium tab whose Chromium failed to start shows
  "Chromium unavailable" with Reload to retry, and never loads its URL
  in WebKit (no Chromium cookies there: signed-in pages came back
  signed out).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…-main deadline, reopens if the owner quit; agent launches forward nothing; any Chromium record waits on a start failure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…ecovery in the hand-off; placeholder keeps its profile and says a restart is needed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…rule on CEFUnavailableReason; explicit QoS

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…ross retries; the reload test moves the record

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

… starts gets its own helper before the exit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…ts; a typed address on a Chromium-unavailable tab is a write quit awaits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

…'s own, so a later one wins over an earlier write still on its way

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
…s write is left, also one typed during it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

…ory flush, so one typed while pages report their history is kept

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Nightly proof on signed nightly-next 1.0.0-nightly.4058380124501, on a fleet Mac. The hand-off works, but it can hand off to the wrong copy of the app; that's described after the results.

The first copy was started with open, and a Chromium tab (Chrome/154) was opened on a page that had a JS marker set. Then Contents/MacOS/cmux was run directly as a second copy.

  • The second copy logged launch: another copy of this app runs; handing off and exiting before Chromium or the session start and exited 0 after 0.706 s.
  • The first copy kept running. Its tab did not reload: it kept the same marker and the same performance.timeOrigin, and it was still Chromium.
  • Neither copy logged "CEF start failed" or "reopens in WebKit".

Gap found: the hand-off's open -b <bundle id> did not bring the running copy forward. On that Mac an older copy with the same bundle id was also registered, and LaunchServices launched that one instead. The older copy has no guard, so its Chromium failed to start and its tab fell back to WebKit. A machine with two copies registered under one bundle id can hit this. I'm fixing it forward so the hand-off targets the running copy itself rather than whatever open -b resolves to.

Agent session: agent session

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 6d8f7bc5 Deployed Oct 27, 2026 by teamleaderleo via wait for same-key tree publisher #66413
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant