Repository navigation
Merge upstream cmux main @ 4e9d779888 (2026-10-01) - #61
Conversation
The main merge kept this branch's older pointer; main's sources need BonsplitContrastPalette and TabPresence from the newer one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…granted plan Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: cover SwiftPM warning in package lane tolerance Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: ignore warning text in package lane error check Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ocks (#6443) * Improve setup prerequisite errors * Tighten GhosttyKit lock handling * Clarify Xcode setup preflight * Sanitize setup error output * Print sanitized lock mkdir errors * Register GhosttyKit lock before cleanup * Harden GhosttyKit lock diagnostics * Move the Xcode preflight ahead of setup mutations Main now preflights the Metal toolchain before any setup mutation, and it already stops a Command Line Tools-only machine. Run one xcodebuild -version check first so a missing Xcode or an unaccepted license gets a direct message, and drop the developer-directory path glob, which could reject valid Xcode installs. Co-authored-by: archit-goyal <go4archit@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Retry when a stale GhosttyKit lock disappears --------- Co-authored-by: Leo Li <cheerleaderleo@outlook.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The subnav read teams from the Hexclave SDK cache, which the dashboard's own team mutations never refresh, so a deleted team stayed in the Settings subnav beside every team page. It now reads the team catalog query that create, rename, leave, and delete already invalidate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: cover current base ref for registry guard Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: resolve registry guard base ref at runtime Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The current card said "$50/mo" while the others said "$50 per month". The picker now takes the subscription's Stripe price as data and renders every card as amount + "per month" (per seat for Team), adding "billed annually" for yearly prices. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…958) * test: cover popover closes whose didClose never arrives reaches popoverWillClose but never popoverDidClose. The click-away test simulates that fault and fails on main: nothing bounds the close, so the presenter stays closing with isShown true and the next toggle cannot present a new popover. A second test fails the same way and pins that a repeated willClose keeps the first deadline. The toggle-close test pins that a programmatic close is never reported as an external dismissal, whether didClose or the deadline ends it. The presenter takes the clock its close deadline will run on, so the tests advance a manual clock instead of sleeping; SidebarTestManualClock gains a sleeper count for deadline-bounded polls. Also cover the checklist section restoring close animation when its popover survives an anchor reparent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sidebar): finish popover closes whose didClose never arrives #14895 found that on some owned Mac minis an animated NSPopover close reaches popoverWillClose but never popoverDidClose, and fixed only the anchor-detach close by turning its animation off. An ordinary user close (click-away, Esc, toggle) still animates, so on those hosts the presenter stayed closing with isShown true: the next toggle closed the stuck popover again instead of presenting, and a click-away was never written back. popoverWillClose now arms a one-second deadline, the close fade plus a margin, on a MainActorDeferredActionScheduler. A repeated willClose for the same popover keeps the first deadline. If didClose has not arrived by then, the presenter abandons the stuck popover (drops its delegate, closes it without animation, orders its window out) and runs the same completion didClose would. The next popover is new, animates normally, and gets its own hosting controller so a late teardown of the abandoned one cannot take its content view. Notifications from an abandoned popover are ignored. Animation stays on everywhere else. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sidebar): re-present cancels a pending close fallback; drop test seams Showing a hidden popover again now cancels the close fallback armed by its earlier willClose, so the stale deadline cannot abandon the popover that is visible again. Tests read the presenter's popover through @testable import instead of DEBUG-only accessors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * dogfood: tour the checklist popover's click-away and toggle closes Opens the checklist popover from the sidebar summary line, closes it by clicking away and by toggling, and reopens it after each close, so the PR media shows the presenter never stays stuck closing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sidebar): re-present also resets the programmatic-close flag A programmatic close superseded by a re-present no longer marks the next click-away as programmatic. The re-present test now checks the fallback was cancelled, the reparent test closes its popover, and the dogfood tour clicks away farther from the popover. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sidebar): ignore a superseded close's late didClose after re-present Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * dogfood: click the checklist summary line by position The summary line is not reachable by identifier from the UI test, so the tour clicks where it draws and records the sidebar tree for diagnosis. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add forward-only submodule CI guard Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: route the submodule guard job through the runner variables The new workflow-guard-submodule-forward-only job pinned runs-on: ubuntu-24.04, which the repo-variable guard rejects: runner choice has to stay a repo-variable flip so Blacksmith and the paid overflow pool can be swapped without editing workflows. Use the same expression the five sibling jobs in this file already use, which also keeps fork pull requests on a hosted runner. One defect, three red checks: CI fast guards, guards / workflow-guard-tests / ci and guards / workflow-guard-tests / preflight all failed on this single line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fix submodule guard comparison and routing Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Use shallow synthetic parent for submodule guard Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…955) * test: pay the first-exec check for fixture executables before timing them macOS 26 blocks the first run of every newly written executable, a copy included, while syspolicyd assesses it (Gatekeeper scan, notarization lookup, XProtect): 0.15-0.4 s on an idle Mac, seconds on a loaded fleet mini. The Claude wrapper tests wrote fresh fakes for every case and ran them first inside the wrapper's own budgets, 1 s for the hook settings generator and 0.75 s for the claude --help probe. When the first-run check ate the budget the wrapper fell back to minimal hooks or cached an empty catalog, and the lane failed with a different message each time: 12 of 45 glaeda runs, 0 of 138 Blacksmith runs. The mutual shim test ran fresh wrapper copies and shims first inside each 5 s guard the same way. Every fixture now exits at once under CMUX_TEST_PRIME_EXEC and is run once that way when written, as #15768 did for the Hermes fixtures. No budget or guard changes. The cancellation case records a failure instead of raising ProcessLookupError when the wrapper exits before the interrupt, so one bad case no longer hides the rest of the results. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: prime the per-run wrapper copies in the Claude hooks test Review follow-ups: - run_wrapper and the env and auth probes copy the wrapper to a fresh path on every call, and several checks time that copy's first exec under a 2 s or 5 s process timeout. Prime each copy with PATH=/usr/bin:/bin, where the wrapper finds no claude and exits before writing anything; the runner's PATH could reach a real claude. - The cancellation check waited only for the help PID log to exist, but the shell creates it before printf writes both PIDs; wait for both lines. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…anup failure (#15958) * test: a receipt whose PID another executable reused must read as stale App-host receipts stay on disk after the host exits, and a shared runner can reuse that PID within minutes: in full-suite run 36680987910 shard 5/7 the PID space wrapped twice during the job. The new case gives the receipt's PID to /bin/sleep, which does not hold the receipt descriptor, and requires cleanup to succeed without authorizing or signaling it. On the current script it fails with the CI message: app-host receipt does not match the PID executable vnode. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): treat a reused app-host receipt PID as a stale receipt cmux_verify_app_host_receipt failed hard when the receipt's PID was alive but ran another executable, while the same file already treats that as exited elsewhere. The receipt descriptor is O_CLOEXEC, so no exec keeps it: a PID that runs another executable and does not hold the receipt is a new process. Such a receipt now verifies as stale (2), which authorizes and signals nothing; a PID that still holds the receipt under another executable still fails, and the live-target scan still fails on any app host without a verified receipt. Shard 5/7 of run 36680987910 failed cleanup this way after all 711 tests passed. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): count a receipt PID as reused only when lsof confirms it The reuse check took any failure of the receipt descriptor check as proof, including lsof failing or returning malformed data, so an uninspectable PID under another executable read as a stale receipt (review on #15958). The descriptor check now returns 3 when a live PID confirmably does not hold the receipt (lsof exit 1), and only that or an exited PID counts as reuse; every other inspection failure still fails cleanup. A new case makes lsof fail on the receipt query for a reused PID and requires cleanup to fail; the looser check passes it as stale. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): require lsof to list a PID before calling its receipt absent lsof exits 1 for an error as well as for a search that found nothing, so an empty filtered query did not show that a live PID lacks the receipt (review on #15958). The descriptor check now returns 3 only when lsof also lists the PID's open files and none is the receipt descriptor; an exit 1 without that listing stays an inspection failure. A new case makes both lsof queries exit 1 for a reused PID and requires cleanup to fail; the previous check passed it as stale. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ci): cover receipt checks that must not read as PID reuse Three cases the receipt-absence check got wrong: - lsof names the PID but lists no open files, which proves nothing; - the receipt is open on a descriptor other than the recorded one, so the PID still holds it; - the reused PID exits between the receipt query and the listing, which is an exit, not an inspection failure. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): read receipt absence only from a full listing of the PID - Count the receipt as held on any descriptor, and require the listing to name at least one open file before it shows the receipt is gone. - Return "exited" when the PID dies between the two lsof queries. - Report lsof's diagnostic when the reuse check cannot decide, and log each receipt it skips as stale. - Merge the two comments that described different return codes. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): keep lsof's diagnostic when listing a receipt PID fails The reuse check already prints what it captured when it cannot decide, so the listing no longer throws lsof's own error away. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ci): drive the recovery verifier with a reused receipt PID Removing the reuse check from cmux_verify_stale_app_host_receipt left every case green. Recovery reads receipts an earlier job left, where PID reuse is likeliest, so cover it: the receipt reads as stale and the PID is not signaled. Without the check the case fails with the vnode mismatch. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: require a pinned agent-chat CI type check Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci: type-check agent-chat with pinned TypeScript Pin TypeScript 5.9.2, invoke the installed compiler, and run a separate preflight type check after a single frozen install. Include DOM.AsyncIterable for the existing ReadableStream iteration. Builds on manaflow-ai/cmux#12351 and manaflow-ai/cmux#12201. Co-Authored-By: Austin Wang <austinwang115@gmail.com> Co-Authored-By: Somesh Lingwal <someshlingwal1@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Austin Wang <austinwang115@gmail.com> Co-authored-by: Somesh Lingwal <someshlingwal1@gmail.com>
…g the Release build (#15944) * test: a stalled cmux-tui client download must not hang the installer Serve the manifest from a local TLS server that answers and then sends nothing, which is what a dead HTTP/2 stream looks like to curl, and require the real installer and curl to give up within the attempt budget. Without a stall bound the installer waits until a 60 s watchdog kills it; in CI run 36685498203 the same stall held the Release build's helper install for 46 minutes until the job timed out. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): bound each cmux-tui client download attempt by progress curl's --retry had no connect timeout or low-speed limit, and its retries reuse the stalled connection, so a dead HTTP/2 stream held one attempt for about twenty minutes. The Release build of full-suite run 36685498203 spent 46 minutes in Install Release helpers and was cancelled at its 60 minute limit, failing macOS status. Give each attempt its own curl process with a connect timeout, a 60 s stall limit and a 10 minute ceiling, as download-with-retry.sh does. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): reject malformed download budgets and bound the stall-server wait Validate CMUX_TUI_CLIENT_DOWNLOAD_ATTEMPTS and CMUX_TUI_CLIENT_DOWNLOAD_STALL_SECONDS as positive integers with a clear error, and wait for the test's stall server by deadline with an explicit failure instead of an iteration count (review on #15944). Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): drop the per-attempt time cap and assert why a stalled download ends Review on #15944. The 600 s ceiling restarted each 40 MB slice from byte zero, so a slow but moving developer download that used to finish now failed; the stall bound alone ends a dead stream. A slice download that fails every attempt exits instead of falling through to a misleading sha256 mismatch, and a malformed budget names its variable and value. The stall test now requires curl's stall error (28) on both attempts, the installer's 'after 2 attempts' error and two server connections, so an early failure for any other reason no longer passes it. Refs manaflow-ai/cmux#15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#14302 changed `MobileAuthComposition.tokenStore` to return `.memory` instead of `.none` when the bundle identifier does not resolve, so authenticated operations no longer trap inside the Stack SDK. The assertion in `missingAppIdentityCannotPersistTokens()` still required `.none`, so the iOS simulator lane fails on main and on every pull request that routes it. Require `.memory` instead. The test's intent is unchanged: a missing app identity must not select a store that persists credentials or shares them with another bundle, and memory storage does neither. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…patch-only (#15852) * Fix four automation-blocking regressions in the iOS connectivity gate Found by driving the relay-only release gate end to end on real staging: - MobileIrxSettingsController.irohSettingsUpdates() never yielded the initial snapshot, so the gate runner's path-policy check subscribed after the transport settled and hung to its deadline. - The irx settingsSnapshot() never populated selectedTransportPath (it stayed .unavailable while an admitted relay session was live); a new IrxConnection.selectedPath() accessor feeds it, classifying a relay as managed only when it matches a signed credential, fail-closed. - No script set CMUX_MOBILE_SOAK_OPEN_SELECTED_WORKSPACE, so gate-mode launches sat on the workspace list and readiness starved on selectedTerminalID; mobile-dev-launch now defaults it on in gate mode. - The What's New sheet covers the workspace UI on every fresh automated install; a DEBUG-only CMUX_UITEST_SUPPRESS_WHATS_NEW knob suppresses presentation without touching acknowledgement markers. Also logs each path-check snapshot so the next silent stall names itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add the iOS e2e gate: two-machine workflow draft and terminal driver scripts/e2e/ios-e2e-run.sh drives sign-in-adjacent terminal use on a live paired sim/Mac and asserts BOTH sides of every step (Vision OCR of the rendered screen; the tagged Mac socket for what the real shell executed): echo round trip, output burst plus verified scrollback, alt-screen enter/exit, Ctrl-C, background/foreground replay, and input after reconnect. scripts/e2e/mac-host.sh holds a CI Mac runner on a done-file with a hard timeout (no GitHub API polling). ios-e2e.yml is the 4-job two-runner topology (Tailscale as control plane only); its pull_request trigger stays commented out until the check is approved for promotion. Verified twice back to back on tag e2eci against remote staging over the real relay, after the in-app gate probe passed with path=managed_relay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix iOS e2e workflow guard requirements * Fix iOS e2e workflow review blockers * Use guard-compatible workflow trigger syntax * ci(ios-e2e): run a fresh per-run backend on a Blacksmith Linux runner Replace the shared dev-backend VM stub with a backend job that builds and serves this revision's web/, iroh-v2 and presence Workers (Durable Objects in local workerd) and Postgres, published on the tailnet by Tailscale Serve. The macOS jobs start in parallel and wait on its health endpoints; the iOS job releases both holders over Tailscale SSH. node_modules live on Blacksmith sticky disks and the web build is cached by the web/ tree SHA. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): keep the PR trigger note outside the on: block The macOS runner guard reads every line under on:, so the commented pull_request note made it refuse the new block-mapping trigger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Run iOS E2E Mac and simulator on one runner * Include backend port lease helper in CI * Keep iOS E2E backend client in cmux repo * Install AXe for iOS E2E runner * Fix GCP backend status output * ci(ios-e2e): per-run backend with its own Iroh relay, OIDC-only tailnet Replace the shared dev-VM backend with a backend job on a Blacksmith Linux runner: iroh-v2 and presence in local workerd, Postgres, and the upstream iroh-relay 1.0.2 (the version cmux-relay wraps), published by Tailscale Serve. iroh-v2 signs relay credentials with a per-run key for the per-run relay, so no production relay key reaches CI. No web/: nothing on the path under test calls it. Every tailnet join uses GitHub OIDC in the ios-e2e environment instead of the stored Tailscale OAuth secret. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(ios-e2e): document the per-run backend, relay and OIDC trust boundary Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): extract iroh-relay by its archive path Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): no cross-branch-writable caches in the secret-holding backend job Blacksmith sticky-disk keys are repository-wide, so another branch could plant node_modules code that this job runs with the Stack server key. Use branch-scoped actions/cache for Bun's package cache instead, and verify the relay tarball's digest on every run, not only on download. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): give the backend's tailscaled a state directory for tailscale cert Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): run wrangler on Node 22 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): tear the per-run backend down on every exit backend-up.sh down stops the relay and both wrangler/workerd trees, removes the Postgres container and Serve listeners, and deletes the files holding the Stack server key, the per-run relay key and the TLS key. It tolerates a partial up; the workflow runs it after the log upload, always. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): serve the backend with a fixed-name certificate, no per-run issuance A per-run Tailscale certificate cost 37 s and would exhaust Let's Encrypt's weekly limit for the tailnet domain; a private CA cannot serve the relay because the iOS Iroh client checks built-in public roots. One certificate for cmux-e2e-backend.<tailnet>, issued once and stored in the ios-e2e environment, now covers all origins. No node keeps the name; runners map it in /etc/hosts. tls-forward.mjs terminates TLS on the tailnet address and forwards bytes; Postgres serves the same certificate. down and unhosts remove the hosts lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): verify Postgres TLS up front; skip the route checkout on dispatch Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): refuse to join the tailnet from a runner that already runs Tailscale On a self-hosted fleet host the Tailscale action would re-register the host's own node under tag:ci and log it out at job end, dropping the host from the tailnet. Both joins now fail first, labeled infra-preflight, on such a host. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): boot the newest iPhone on the newest iOS runtime; cache Tailscale on macOS The last iPhone-family device type was an iPod touch with no runtime on the image, so the simulator never booted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(ios-e2e): suppress What's New and open the paired terminal on launch Run 36681682225 signed in and paired, then the fresh install stopped on the What's New sheet and the driver never reached a terminal. The launcher only enables both switches in release-gate mode; set them for this lane. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(ci): cover per-run iOS E2E backend scripts * fix(ci): use approved macOS fallback for iOS E2E * fix(ci): route fork iOS E2E runs to macOS 26 * test(ci): cover backend cleanup without origin config * fix(ci): allow backend cleanup without origin name * test(ci): cover backend host cleanup without origin config * fix(ci): allow host cleanup without origin name * test(ci): require machine-readable iOS E2E failures * fix(ci): emit parseable iOS E2E failure markers * test(ci): cover empty backend hold state Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(ci): make backend preflight failures reachable Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * revert(ios): keep transport selection fail-closed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com> Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
* test(agent-chat): cover duplicate Claude child close Add a regression for two parallel Claude spawn-tool children. The second child must remain running after the first child's PostToolUse and duplicate SubagentStop events. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(agent-chat): ignore duplicate Claude subagent stop Claude brackets child runs with the spawn tool's PostToolUse, and its SubagentStop hook arrives as a second close without a request id. Skip that duplicate event only for Claude so the existing FIFO fallback and SubagentStart/SubagentStop behavior for Codex, pi, and OMP remain unchanged. This source-specific guard is smaller and safer than adding deduplication state to closeChild. Correct the custom sidebar documentation to describe FIFO closing and the request id field the code actually decodes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(agent-chat): cover Claude background child lifecycle Add a regression showing that a Claude child must stay running after the spawn tool returns and settle only when SubagentStop arrives. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(agent-chat): close Claude children on subagent stop Claude's spawn tool PostToolUse fires when a foreground or background child detaches, so it is not a completion signal. Ignore it for Claude and close the child on the SubagentStop hook instead. Keep the existing post-tool and FIFO behavior for Codex, pi, and OMP, and document the corrected lifecycle. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(ios): cover terminal composer input traits Signed-off-by: Alejandro Florez <soyeladice@gmail.com> * fix(ios): disable composer text rewriting traits Signed-off-by: Alejandro Florez <soyeladice@gmail.com> --------- Signed-off-by: Alejandro Florez <soyeladice@gmail.com>
* Deduplicate Cloud attachment recovery requests * test: cover idempotent cloud mirror fixture close Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: make cloud mirror fixture close idempotent Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ck (#16004) * test: pin the Cloud sidebar's chrome to the right sidebar's own metrics The Cloud surfaces sit inside the right sidebar but cannot import the app target that owns `RightSidebarChromeMetrics`, so each of them carries its own copy of the numbers. The copies have drifted: the Cloud banners use a 12pt outer inset against the sidebar's 8 and a 5pt vertical against its 4, and the Cloud tree reserves a 12pt trailing column against the sidebar's 6. `CloudSidebarChromeMetrics` states the sidebar's numbers once for the package. These tests run in the app target, where both types are visible, so the copy cannot drift from the original without failing. They are red until the Cloud tree is moved onto the shared trailing column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: put the Cloud sidebar on the right sidebar's chrome columns The Cloud banners sat on a 12pt outer inset while the mode bar, the Vault grouping pills and the Vault search row sit on 8, so the Cloud surfaces stepped in from the sidebar they are part of. The Cloud tree reserved a 12pt trailing column against the sidebar's 6, so machine rows stopped short of the header's controls and titles truncated 6pt early. All of these now read `CloudSidebarChromeMetrics` instead of repeating a literal. `CloudTreeLayoutMetrics.referenceInset` and the spacing lab's `referenceInset` follow `CloudTreeRowGrid`'s trailing padding rather than restating it, which is what would otherwise have let the outline document and the hosted row content drift apart on this change. The operation activity row keeps its 8pt vertical padding: it carries a progress indicator rather than a line of text, and shortening it is a look rather than a mismatch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): keep titleWidth's expectation on the inset, align two more sidebar rows Review of #15307 caught a suite left red by the previous commit: `titleWidthReservesControls` asserted a literal 240, which is `420 - 92 - 76 - 12`. Moving `referenceInset` to the row grid's 6 makes that 246. The assertion now derives from `referenceInset` so the sidebar's chrome can move again without rewriting arithmetic here. The doc comments on `CloudTreeLayoutMetrics` and in that test claimed the outline document reserves this inset. It does not: `documentWidth` is `max(0, viewportWidth)`, and `titleWidth` has no caller outside the test. Both now say what is true, which is that the default keeps a dormant helper from being wired up at a stale number. Two more rows in the same vertical stack were still on a 10pt outer inset while the banners beside them moved to 8: the team picker's fleet status row and the "machines unavailable" notice. Aligned both. The three Cloud views that live in the app target now read `RightSidebarChromeMetrics` directly instead of the package's copy. The copy exists because CmuxCloud cannot import the app target; app-target code has no such problem, and pointing it at the copy invents drift where there was none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(cloud): state the title-width clamp instead of leaning on the old inset `titleWidth(rowWidth: 180, …)` came out at exactly zero only because the reference inset was 12, so the assertion read as a clamp test and was really arithmetic. Moving the inset to the sidebar's 6 made it 6 and the test failed with no clamping behaviour changed. Now the narrow case is written against `referenceInset` like the wide one, and the clamp gets its own case at a width that actually underflows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): make CloudSidebarChromeMetrics a value, not a static namespace scripts/lint_swift_namespaces.py rejects an all-static public surface. The metrics are now an Equatable, Sendable struct with a .sidebar instance for the numbers the app ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): align the team-change error row with the chrome bar above it The Cloud header row went on `.rightSidebarChromeBar()`, which insets it by `RightSidebarChromeMetrics.barHorizontalPadding` (8). The team-change error row sits directly under it and kept a hardcoded 10, so the message and its Close button stood 2pt inboard of the header they belong to. Read both paddings from the metric instead, which is what the rest of this branch does. The vertical value is unchanged at 4; it now names the metric rather than repeating the number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): put the tree's trailing column on the header's own inset Review of this branch found the tree moved to the wrong column. The commit that introduced CloudSidebarChromeMetrics took the tree's trailing column from headerTrailingPadding (6) on the theory that the header above it sits there. It does not. CloudTeamPickerHeader calls a plain rightSidebarChromeBar(), whose trailingPadding defaults to barHorizontalPadding (8). The two call sites that opt into 6 are the mode bar and the Vault grouping bar, neither of which is above the Cloud tree. So the row hover buttons went from 4pt inboard of the header's refresh and + buttons to 2pt outboard of them: still misaligned, and the doc comment claimed they lined up. The branch also contradicted itself, since the team-change error row was aligned to 8 two commits later for exactly the reason the tree was aligned to 6. The tree now follows barHorizontalPadding, so the header, the error row and the row accessories all stop on the same column. headerTrailingPadding had no reader left and is removed rather than kept as a copy documenting a relationship that does not hold. Also in the same stack, MachinesCloudStatus was still on a literal 10. It is the status slot rendered directly under the error row, so the header read 8 / 8 / 10 after the previous commit. It now names the metrics. CloudTreeMachineBand takes trailingPadding - 2, whose headroom fell from 10 to 4 when the column moved off 12. The DEBUG spacing lab's slider starts at 0, so that expression can go negative; clamped at 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): preserve machine name ends when narrow Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cloud): collapse machine actions in a narrow header Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* test: require xcstrings refusals to preserve both sides
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: materialize xcstrings merge conflicts
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: reject malformed catalog shapes visibly
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: clarify generated-file merge drivers
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: preserve separate xcstrings conflict hunks
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: isolate xcstrings conflict hunk assertions
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: preserve multi-hunk xcstrings conflicts
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(xcstrings): close the last fail-open write, correct the driver-trust note
A review of this branch found one refusal path that still ends where the bug
started. `_materialize_conflict` caught only `OSError` around a call that both
renders and writes, so a failure to open `%A` printed to stderr and returned
normally, and a non-`OSError` from `conflict_text` escaped `main` entirely.
Either way the process exits nonzero with `%A` byte-identical to ours: git
records the path as unmerged, the file on disk has no markers, and it reads as
"no disagreement here" and gets staged. That is the exact shape of #15415, so
the claim that every refusal writes a visible conflict was not yet true.
Now the text is rendered before the file is opened, both steps catch
`Exception`, and when a conflict cannot be written at all `_blank` truncates
`%A` to zero bytes. A driver cannot ask git to abort, so an empty file is the
loudest signal left, and it is never valid JSON, so anything that parses the
catalog fails rather than accepting ours as a merge of theirs. Reopening can
fail for the same reason the first write did; then the message names the file
and says it is still ours. The success write gets the same treatment, since a
failure there loses just as quietly.
Two tests, both confirmed to fail against the previous driver: a refusal whose
render raises leaves `%A` empty rather than ours, and a `%A` that cannot be
written at all reports that it must not be committed. A third test for the
reported ours-equals-base shape passed against the old driver as well, so it
had no power and is not here.
The `.gitattributes` paragraph this branch added was also wrong. It said both
drivers run from the working tree, but `scripts/install-git-hooks.sh` installs
reviewed copies into `$GIT_COMMON_DIR/cmux-merge-drivers` and configures
absolute paths to those, which is what `tests/test_install_git_hooks.py`
asserts. The note now describes that, and why catch-up pins the drivers to
false instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: cover xcstrings conflict containment and omissions
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: pin per-key conflicts and all fallback sections
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: materialize xcstrings conflicts per key
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(xcstrings): refuse per-key conflicts that share a line
The per-key conflict path replaced text from the start of the key's
*line*, and never checked that two replacement ranges were disjoint. On
a catalog whose `strings` keys do not each sit on their own line that
lost data:
- a compacted catalog dropped the `{"sourceLanguage":...,"strings":{`
prefix into neither side of the conflict, so a human resolving it
had to retype it;
- two conflicting keys on one line clobbered each other, truncating
ours' text for the second key and emitting a `||||||| base` with no
opening marker.
Both now raise, which `main()` already catches and degrades to the
lossless whole-file conflict the driver used before per-key conflicts
existed. No catalog in the repo has shared-line keys today, but
`.gitattributes` registers this driver for every `*.xcstrings`, the
schema lint has no layout rule, and the docstring advertises compacted
leaf objects, so one hand-written catalog is enough.
Found by a review subagent on this PR; both cases are pinned by tests
that fail without the guards.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…reen (#14858) * Add built-in copy actions for working directory, project root, and screen cmux.copyWorkingDirectory, cmux.copyProjectRoot, and cmux.copyScreen work as surface tab bar buttons, cmux.json actions (including shortcuts), and command palette entries. Every entrypoint runs TerminalCopyActionRunner, which writes through the terminal pasteboard service and leaves the clipboard untouched (with a beep) when there is nothing to copy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fix copy-action test inference and replace the static text namespace TerminalCopyText becomes String extensions to satisfy the package namespace-type convention, and the config test gives its compactMap an explicit element type. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Address review: per-panel provenance, bounded root walk, stale-write guard - Copy actions target one terminal panel and read its directory through effectivePanelDirectory, so remote, cloud, and remote-tmux panels copy only a directory their host reported, and a missing or non-terminal target beeps instead of copying another pane's directory. The palette and shortcut paths pass the focused panel explicitly. - The git root walk runs off the cooperative pool with a 1.5 s deadline, and the delayed write only lands if the clipboard is unchanged since the action started. - Soften the workTreeRoot and visibleScreenClipboardText docs, replace a vacuous config assertion, add provenance and stale-write tests, and document the remote fallback on the custom-commands page. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Give the project-root walk its own queue and a 5 s budget The first bounded version shared the git status queue and a 1.5 s deadline that included queue wait; under CI load the walk timed out and the tests saw nil. The walk now runs on a user-initiated global queue. The stale-write guard already keeps a late result from clobbering a newer copy, so the deadline only needs to cover a hung mount. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Bound the project-root wait, stay quiet when a newer copy wins, resolve mirror panes - workTreeRoot runs on a dedicated serial BoundedBlockingRunner: the caller resumes from whichever of the walk or a timer finishes first, and a call made while a walk is stuck on a hung mount returns nil (copy falls back to the working directory) instead of parking another thread. - copyToStandardClipboard(_:ifUnchangedSince:) returns the write status; the runner no longer beeps when the user's newer copy wins. - Copy actions resolve their target through terminalInputTarget, so a focused remote-tmux window container copies its active inner pane's screen and remote cwd. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test that a copy-action shortcut passes through without a terminal With a browser panel focused, a bound cmux.copyWorkingDirectory, cmux.copyProjectRoot, or cmux.copyScreen shortcut currently beeps and reports the keystroke handled, so the browser never sees it. This test expects the shared action path to report it unhandled and leave the clipboard alone. It fails until the next commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Let copy-action shortcuts pass through when no terminal is focused The shared built-in action path now reports a copy action unhandled, without a beep, when the focused panel isn't a terminal. A bound shortcut's keystroke reaches the browser or other focused panel instead of being swallowed. The plus-menu and group-menu callers already beep on an unhandled action, and the palette and tab bar button keep calling the runner directly, so they still beep when there's nothing to copy. A terminal with nothing to copy still consumes the shortcut. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Tell a timed-out project-root walk apart from "not in a repository" workTreeRoot returned nil for three different cases: no repository, the walk timed out, or an earlier walk was still stuck. Copy Project Root copied the working directory for all three, so after one walk hung on a network mount every later press silently copied the cwd as if it were the root. BoundedBlockingRunner now reports finished, timedOut, or busy, workTreeRoot returns GitWorkTreeRootLookup (root, notInRepository, unavailable), and the action only falls back to the working directory for notInRepository. It beeps when the lookup is unavailable. Review fixes on the shared action path: - A copy action run from the group menu calls onExecuted even when there was nothing to copy, so the menu restores the selection it moved to the anchor workspace. - The group menu beeps when a copy action can't find a terminal, the way the plus menu already does, since the action path no longer beeps there. - The runner and root tests use their own runner instead of the shared static one, so they no longer depend on test order. The passthrough test drops its NSPasteboard change-count check, which any other process on the host could bump. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add a changelog line for the copy actions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Move the changelog line to Unreleased; steady the busy-runner test The changelog line had landed under the released 0.64.24 section. The busy-runner test gave its blocked job 50 ms to start; on a loaded machine the job could miss the deadline and never run, so the next call found the runner idle. It now waits 500 ms and asserts the runner is busy before the second call. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: add copy actions dogfood tour Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: target copy action palette row Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TeamsClient.shared is set at app start; tests that authenticate a fake coordinator never bootstrap it, and the sign-in poll dereferenced nil and timed out unrelated Cloud suites. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(ssh): reconcile agent status on projector startup * test(ssh): cover initial agent status projection * test(ssh): isolate status projection on main actor
* test: cover SOS socket replies * fix: preserve SOS replies in socket input
…5973) * test(flags): cover malformed review dates Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(flags): collect registries and reject invalid dates Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(flags): report review lead time Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(flags): file review drift issues Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(flags): reproduce scheduled report false all-clears Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(flags): keep scheduled review drift truthful Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(flags): open an issue when report fails Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…t (#16267) * test(ci): cmux-tui artifact publishing must run in the artifacts environment #16171 put the cmux-tui publish job in the release environment, whose policy allows only main and v* tags, so helper-branch pin publishes fail before any step runs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(ci): cmux-tui artifact publishing runs in the artifacts environment The artifacts environment holds only the R2 upload credentials and allows main, feat-cmux-next and cmux-tui-pin-* helper branches, so daemon pin publishes work again while signing, Sparkle, Homebrew and Apple secrets stay in release (main and v* tags only). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cloud): reusing a projection at its current placement changes nothing Reconcile reprojects every missing placement through SurfaceCatalog.project. When the reused pane already carries that placement, attachRemoteView still removes and reinserts it, bumps the projection revision twice, and requests the next reconcile of the same machine. Any disagreement between the plan and project() then becomes a main-actor livelock, which is how nightly b36a9b3 spun at 98% CPU and grew to tens of GB (fixed at the plan level by #16025). Fails on main: projectionVersions advances by 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): reattaching a projection's current placement is a no-op attachRemoteView rewrote a reused projection even when its remote workspace and tab were already the requested ones: it removed and reinserted it (clearing and resetting the panel directory, rerunning sidebar git probes, bumping the guest routing revision twice) and requested another reconcile of the machine. Since reconcile itself reprojects through project(), any plan that reports a shown pane as missing became an endless main-actor loop. Return early when the coordinates are unchanged, and apply a real change as one projections assignment so observers never see the pane unprojected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): setting a projection's current remote placement is a no-op Same guard as attachRemoteView for setRemotePlacement: skip views whose coordinates already match, and apply real changes as one projections assignment. Unchanged placements no longer bump the projection revision or post a catalog change that wakes the device layout coordinator. The test now states its fixture precondition explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cloud): reconciling one graph stops when every pass requests another A consumer that requests another reconcile without changing the accepted graph keeps CloudWorkspaceProjectionCoordinator's loop running forever on the main actor, which is how nightly b36a9b3 hung at 100% CPU and grew to tens of GB. Fails on main: the loop runs until the test stub stops asking (1000 passes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): bound reconciliation passes over one accepted graph CloudWorkspaceProjectionCoordinator re-ran reconcile while anything kept requesting it, with no progress check. Any consumer that asks for another pass without changing the graph (attachRemoteView before this PR, a plan that reports a shown pane as missing in #16025) held the main actor forever: nightly b36a9b3 pinned a core, grew to tens of GB, and could not even run its updater. Count passes over the same accepted CloudVMState. A converging graph needs two or three; after eight, stop, report a Sentry warning, and wait for the next graph or request, which starts a new count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): bound reconciliation by progress, not by passes over one graph Review of the previous bound: counting every pass over an unchanged graph could stop a reconcile that was still making progress (a staggered restore of several bound workspaces re-requests the same graph), stranding panes until the next graph. CloudWorkspaceReconcileBudget now stops after three consecutive passes that start from the same graph, projection revision and bindings (a pass that changed nothing cannot make the next one different), with a hard ceiling of 64 passes per graph for a loop that rewrites projections every pass, as nightly b36a9b3 did. Non-convergence is reported once per graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): report projection non-convergence once per daemon generation Review: keying the dedupe on the full CloudVMState retained a whole graph per machine for the process lifetime (cancel never cleared it) and still reported once per revision. Key on the cursor generation, include generation and revision in the event, and clear it when the machine is cancelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(l10n): document the French Actions discovery titles as invariant Same change as #16175: main's localization parity check fails on actions.discovery.menuTitle and dialogTitle (fr is identical to English), which blocks this PR's static preflight and every gate behind it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ope) (#16260) * fix: share OpenCodePaths with the CLI through CMUXAgentLaunch #16229 made CLI/cmux.swift call OpenCodePaths, but the enum lived in Sources/SessionIndexModels.swift, which only the app target compiles, so the CLI target fails with "cannot find 'OpenCodePaths' in scope". Move the unchanged path logic into CMUXAgentLaunch, which the app, the CLI and cmuxTests already import, and make its two entry points public. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pass the temporary-config flag to the Codex provider override parser #16201 made providerOverrides(from:) skip provider entries when the caller uses a temporary CODEX_HOME, but read `usesTemporaryConfig`, a parameter of build(configToml:usesTemporaryConfig:) that is not in scope there, so the CLI no longer compiles. Pass the flag through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: match temporary Codex config argument scope * Make OpenCodePaths a value type to satisfy package conventions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci: cmux-tui artifact publishing runs in its own artifacts environment (#16267) * test(ci): cmux-tui artifact publishing must run in the artifacts environment #16171 put the cmux-tui publish job in the release environment, whose policy allows only main and v* tags, so helper-branch pin publishes fail before any step runs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(ci): cmux-tui artifact publishing runs in the artifacts environment The artifacts environment holds only the R2 upload credentials and allows main, feat-cmux-next and cmux-tui-pin-* helper branches, so daemon pin publishes work again while signing, Sparkle, Homebrew and Apple secrets stay in release (main and v* tags only). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(cloud): make Cloud workspace reconciliation always settle (#16158) * test(cloud): reusing a projection at its current placement changes nothing Reconcile reprojects every missing placement through SurfaceCatalog.project. When the reused pane already carries that placement, attachRemoteView still removes and reinserts it, bumps the projection revision twice, and requests the next reconcile of the same machine. Any disagreement between the plan and project() then becomes a main-actor livelock, which is how nightly b36a9b3 spun at 98% CPU and grew to tens of GB (fixed at the plan level by #16025). Fails on main: projectionVersions advances by 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): reattaching a projection's current placement is a no-op attachRemoteView rewrote a reused projection even when its remote workspace and tab were already the requested ones: it removed and reinserted it (clearing and resetting the panel directory, rerunning sidebar git probes, bumping the guest routing revision twice) and requested another reconcile of the machine. Since reconcile itself reprojects through project(), any plan that reports a shown pane as missing became an endless main-actor loop. Return early when the coordinates are unchanged, and apply a real change as one projections assignment so observers never see the pane unprojected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): setting a projection's current remote placement is a no-op Same guard as attachRemoteView for setRemotePlacement: skip views whose coordinates already match, and apply real changes as one projections assignment. Unchanged placements no longer bump the projection revision or post a catalog change that wakes the device layout coordinator. The test now states its fixture precondition explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cloud): reconciling one graph stops when every pass requests another A consumer that requests another reconcile without changing the accepted graph keeps CloudWorkspaceProjectionCoordinator's loop running forever on the main actor, which is how nightly b36a9b3 hung at 100% CPU and grew to tens of GB. Fails on main: the loop runs until the test stub stops asking (1000 passes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): bound reconciliation passes over one accepted graph CloudWorkspaceProjectionCoordinator re-ran reconcile while anything kept requesting it, with no progress check. Any consumer that asks for another pass without changing the graph (attachRemoteView before this PR, a plan that reports a shown pane as missing in #16025) held the main actor forever: nightly b36a9b3 pinned a core, grew to tens of GB, and could not even run its updater. Count passes over the same accepted CloudVMState. A converging graph needs two or three; after eight, stop, report a Sentry warning, and wait for the next graph or request, which starts a new count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): bound reconciliation by progress, not by passes over one graph Review of the previous bound: counting every pass over an unchanged graph could stop a reconcile that was still making progress (a staggered restore of several bound workspaces re-requests the same graph), stranding panes until the next graph. CloudWorkspaceReconcileBudget now stops after three consecutive passes that start from the same graph, projection revision and bindings (a pass that changed nothing cannot make the next one different), with a hard ceiling of 64 passes per graph for a loop that rewrites projections every pass, as nightly b36a9b3 did. Non-convergence is reported once per graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): report projection non-convergence once per daemon generation Review: keying the dedupe on the full CloudVMState retained a whole graph per machine for the process lifetime (cancel never cleared it) and still reported once per revision. Key on the cursor generation, include generation and revision in the event, and clear it when the machine is cancelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(l10n): document the French Actions discovery titles as invariant Same change as #16175: main's localization parity check fails on actions.discovery.menuTitle and dialogTitle (fr is identical to English), which blocks this PR's static preflight and every gate behind it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(tests): name the app's window-chrome sidebar options explicitly #11539 reverted #14991's qualification in SidebarWidthPolicyTests, so SidebarMaterialOption.sidebar is ambiguous between CmuxSettings and the app's typealias to WindowChromeSidebarMaterialOption. Use the WindowChrome names again, as #14991 did. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Leo Li <cheerleaderleo@outlook.com> Co-authored-by: Austin Wang <austinwang115@gmail.com>
* test: cover authorized dev relay limit bypass * fix: exempt authorized dev relay clients from limits --------- Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
* test: reproduce late Agent Chat startup stealing session focus * fix: keep late Agent Chat startup replies from changing selection * Make Agent Chat fork and handoff actions recoverable (#15994) * test: reproduce stuck Agent Chat fork and handoff lifecycle * fix: make Agent Chat fork and handoff actions recoverable * fix(agent-chat): deduplicate reconnecting session actions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: reproduce Stop doing nothing before startup acknowledgement * fix: cancel pending agent starts by request ID and recover queued drafts * fix: preserve startup cancellation through provider initialization Latch Pi startup cancellation across initialization and retain request-to-session identity after request cache expiry so delayed Stop retries still reach the created session. Extend the server fixture for expired request IDs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(ci): notarization must use the team App Store Connect API key The nightly DMG and Computer Use helper notarization tests now require notarytool to authenticate with --key/--key-id/--issuer, a mode-600 key file decoded from ASC_API_KEY_P8_BASE64, no Apple ID credentials, and deletion of the decoded key on exit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci: notarize Mac builds with the team App Store Connect API key Release, nightly/RC and the v0.64.25 repair workflow now authenticate notarytool with --key/--key-id/--issuer instead of an Apple ID and app-specific password. scripts/ci/lib/notary-auth.sh decodes ASC_API_KEY_P8_BASE64 into a mode-600 file inside the caller's private temp dir, which an EXIT trap deletes. The notarize scripts fail before any upload when a key value is missing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(ci): read the fake notary key mode on Linux and macOS Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merges 259 first-parent upstream commits (upstream main 2026-09-29 to 2026-09-30), including shared terminal sizing across Mac, iPhone and Devices mirrors, Connected Devices on iOS, the iOS agent Feed, copy built-in actions, and guarded workspace close (--force). 25 files conflicted; resolved per SUPERMUX.md (upstream code plus the re-applied SUPERMUX fences). Semantic fixes folded in: - NotificationFeedHistoryRecord's explicit CodingKeys/init(from:) now carry upstream's new isAgentEvent (it would otherwise be dropped on encode). - TerminalCopyAction.swift gains a fenced .newClaudeHarness arm (#600). - iOS keeps the Notifications tab visible by default beside the new Feed (#601, open decision in SUPERMUX-UPGRADES.md). - iOS native-scroll delta applies upstream's gridDisplayScale. Registry: rows updated for moved code, #504 path moved under Debug/, #600 and #601 added (517-599 are held for the remote-workspaces branch). scripts/supermux-check-touchpoints.sh passes.
…ft stale The 2026-09-30 merge kept older wording for 18 upstream-owned strings (for example "Couldn't reach Stack" on sign-in errors, where upstream now says "Couldn't reach cmux") and re-added 21 keys upstream had deleted. Touchpoint #4b allows the fork to change only supermux.* keys plus the #84 exception, so the 18 values now match upstream/main and the 21 unreferenced keys are removed. Only member spans changed; the rest of the catalog keeps its formatting. After this, every non-supermux key equals upstream/main except settings.search.alias.setting.app.workspace-inherit-working-directory (#84).
What a supermux user notices after merging upstream main @ 4e9d779: shared terminal sizing across Mac, iPhone and Devices mirrors, Connected Devices on iOS, the iOS Feed beside the fork's Notifications tab, guarded workspace close, the new ⌃⌥⌘= and V shortcuts, watch-outs, and the one open decision (Feed vs. Notifications tab).
There was a problem hiding this comment.
10 issues found across 1976 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/ci-owned-pool-rescue.yml">
<violation number="1" location=".github/workflows/ci-owned-pool-rescue.yml:40">
P1: The periodic sweeper cannot discover these side-lane runs: it adopts marker artifacts and CI reruns, while these no-picker workflows upload no marker. Removing their `workflow_run` subscriptions leaves attempt-1 owned jobs without rescue; restore the subscriptions or add a marker the sweeper consumes.</violation>
</file>
<file name=".github/workflows/nightly.yml">
<violation number="1" location=".github/workflows/nightly.yml:1309">
P1: The `release` environment excludes `rc/**`, even though this workflow publishes every RC-branch push; RC runs will be blocked from signing and publishing. Add `rc/**` to the environment’s allowed deployment branches, or use an environment that permits RC refs.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ExplicitTerminalInput.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ExplicitTerminalInput.swift:56">
P1: The exactly-once route runs before this guard, so detached terminals can still receive and acknowledge input through `deliverExactlyOnce`. Check `terminalAllowsTraffic` before that call, as the paste paths do.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalScrollDelivery.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalScrollDelivery.swift:37">
P2: This check only gates calls before enqueueing; a scroll already pending when a detach event arrives is still dispatched after the in-flight request completes. Recheck attachment in `performTerminalScroll` before sending the RPC so queued TUI wheel input is not delivered after detach.</violation>
</file>
<file name="Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/WorktreeSeed/WorktreeSeedRepository.swift">
<violation number="1" location="Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/WorktreeSeed/WorktreeSeedRepository.swift:100">
P2: This marks `outer -> inner` as in-repository even when `inner` points to `/outside/secret`, because the final target component is never resolved. Resolve target symlink chains while retaining the dangling-target fallback.</violation>
</file>
<file name="Packages/macOS/CmuxCloudTui/Sources/CmuxCloudTui/CloudTuiFrontendProjectionRequests.swift">
<violation number="1" location="Packages/macOS/CmuxCloudTui/Sources/CmuxCloudTui/CloudTuiFrontendProjectionRequests.swift:21">
P2: When no projection exists, this omits the revision precondition, so concurrent first writes can both succeed and the later write can erase the earlier membership. Send revision `0` for `nil`; the caller can then retry the revision conflict against the latest projection.</violation>
</file>
<file name="CLI/CMUXCLI+AgentHibernation.swift">
<violation number="1" location="CLI/CMUXCLI+AgentHibernation.swift:21">
P2: This filter silently discards an incomplete `--workspace` option, so hibernate/wake proceeds without the requested workspace scope. Reject unparsed option tokens before sending the request.</violation>
</file>
<file name="Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamItem.swift">
<violation number="1" location="Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamItem.swift:65">
P2: The identity migration drops `reply` when it rebuilds an item with a canonicalized workstream ID, so remote terminal replies disappear from loaded history after restart. Preserve `item.reply` in `normalizedWorkstreamItem`.</violation>
</file>
<file name=".github/contributor/welcome.md">
<violation number="1" location=".github/contributor/welcome.md:9">
P2: This promises CI will run touched UI tests, but the UI-test lane is currently disabled unless `CI_UI_TESTS_ENABLED=1`, and it does not dispatch tests for fork PRs. Clarify that app-host suites are selected automatically while UI-test coverage depends on the lane being enabled and the PR being eligible.</violation>
</file>
<file name="Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator+TeamSelection.swift">
<violation number="1" location="Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator+TeamSelection.swift:92">
P2: This refresh can overwrite a team switch that completes while its fetch is in flight: the refresh applies its server selection without checking for a newer team mutation. Fence refresh results against team mutations or serialize refresh with selection so a stale response cannot restore the previous active scope.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Re-trigger cubic
| # vars.CI_NIGHTLY_TRUSTED_RUNNER a glaeda-runner-* label, attempt 1 of every | ||
| # push or schedule run on main asks for that one trusted mini, so every such | ||
| # run is watched (owned_pool_rescue.NIGHTLY_WORKFLOW_PATH). | ||
| # The side lanes have no picker and are already covered by the periodic |
There was a problem hiding this comment.
P1: The periodic sweeper cannot discover these side-lane runs: it adopts marker artifacts and CI reruns, while these no-picker workflows upload no marker. Removing their workflow_run subscriptions leaves attempt-1 owned jobs without rescue; restore the subscriptions or add a marker the sweeper consumes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/ci-owned-pool-rescue.yml, line 40:
<comment>The periodic sweeper cannot discover these side-lane runs: it adopts marker artifacts and CI reruns, while these no-picker workflows upload no marker. Removing their `workflow_run` subscriptions leaves attempt-1 owned jobs without rescue; restore the subscriptions or add a marker the sweeper consumes.</comment>
<file context>
@@ -32,19 +32,14 @@ run-name: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow
# vars.CI_NIGHTLY_TRUSTED_RUNNER a glaeda-runner-* label, attempt 1 of every
# push or schedule run on main asks for that one trusted mini, so every such
# run is watched (owned_pool_rescue.NIGHTLY_WORKFLOW_PATH).
+# The side lanes have no picker and are already covered by the periodic
+# sweeper. They stay in the script's target rules for explicit dispatches, but
+# no longer wake a fresh rescue through workflow_run.
</file context>
| build-sign-notarize-nightly: | ||
| # Production secrets: GitHub releases them only to protected refs (the | ||
| # environment's deployment branch policy: main, tags v*). | ||
| environment: release |
There was a problem hiding this comment.
P1: The release environment excludes rc/**, even though this workflow publishes every RC-branch push; RC runs will be blocked from signing and publishing. Add rc/** to the environment’s allowed deployment branches, or use an environment that permits RC refs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/nightly.yml, line 1309:
<comment>The `release` environment excludes `rc/**`, even though this workflow publishes every RC-branch push; RC runs will be blocked from signing and publishing. Add `rc/**` to the environment’s allowed deployment branches, or use an environment that permits RC refs.</comment>
<file context>
@@ -1304,6 +1304,9 @@ jobs:
build-sign-notarize-nightly:
+ # Production secrets: GitHub releases them only to protected refs (the
+ # environment's deployment branch policy: main, tags v*).
+ environment: release
needs: [decide, build-nightly-ghostty-cli-helper, build-nightly-app, resolve-nightly-cmux-tui-client]
if: needs.decide.outputs.should_build == 'true' && (github.event_name != 'schedule' || github.event.schedule == '47 8 * * *') && needs.decide.outputs.build_only != 'true'
</file context>
| ) { | ||
| return settlement == .delivered | ||
| } | ||
| guard terminalAllowsTraffic(surfaceID: terminalID.rawValue) else { return false } |
There was a problem hiding this comment.
P1: The exactly-once route runs before this guard, so detached terminals can still receive and acknowledge input through deliverExactlyOnce. Check terminalAllowsTraffic before that call, as the paste paths do.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ExplicitTerminalInput.swift, line 56:
<comment>The exactly-once route runs before this guard, so detached terminals can still receive and acknowledge input through `deliverExactlyOnce`. Check `terminalAllowsTraffic` before that call, as the paste paths do.</comment>
<file context>
@@ -53,6 +53,7 @@ extension MobileShellComposite {
) {
return settlement == .delivered
}
+ guard terminalAllowsTraffic(surfaceID: terminalID.rawValue) else { return false }
let target = workspaceMutationTarget(for: workspaceID)
guard let client = target.client else { return false }
</file context>
| /// stale scroll packets. | ||
| // SUPERMUX:end ios-terminal-alt-scroll-budget | ||
| public func scrollTerminal(surfaceID: String, lines: Double, col: Int, row: Int) async { | ||
| guard terminalAllowsTraffic(surfaceID: surfaceID) else { return } |
There was a problem hiding this comment.
P2: This check only gates calls before enqueueing; a scroll already pending when a detach event arrives is still dispatched after the in-flight request completes. Recheck attachment in performTerminalScroll before sending the RPC so queued TUI wheel input is not delivered after detach.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalScrollDelivery.swift, line 37:
<comment>This check only gates calls before enqueueing; a scroll already pending when a detach event arrives is still dispatched after the in-flight request completes. Recheck attachment in `performTerminalScroll` before sending the RPC so queued TUI wheel input is not delivered after detach.</comment>
<file context>
@@ -34,6 +34,7 @@ extension MobileShellComposite {
/// stale scroll packets.
// SUPERMUX:end ios-terminal-alt-scroll-budget
public func scrollTerminal(surfaceID: String, lines: Double, col: Int, row: Int) async {
+ guard terminalAllowsTraffic(surfaceID: surfaceID) else { return }
// Screen-anchored sessions own primary-screen scrolling: the gesture
// already moved the local mirror's viewport over locally accumulated
</file context>
| // The parent is resolved, not the target: the target may not exist, and | ||
| // every real component above it does. | ||
| let parent = target.deletingLastPathComponent().resolvingSymlinksInPath().standardizedFileURL | ||
| let resolved = parent.appendingPathComponent(target.lastPathComponent).standardizedFileURL.path |
There was a problem hiding this comment.
P2: This marks outer -> inner as in-repository even when inner points to /outside/secret, because the final target component is never resolved. Resolve target symlink chains while retaining the dangling-target fallback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/WorktreeSeed/WorktreeSeedRepository.swift, line 100:
<comment>This marks `outer -> inner` as in-repository even when `inner` points to `/outside/secret`, because the final target component is never resolved. Resolve target symlink chains while retaining the dangling-target fallback.</comment>
<file context>
@@ -74,12 +74,30 @@ public struct WorktreeSeedRepository: Sendable {
+ // The parent is resolved, not the target: the target may not exist, and
+ // every real component above it does.
+ let parent = target.deletingLastPathComponent().resolvingSymlinksInPath().standardizedFileURL
+ let resolved = parent.appendingPathComponent(target.lastPathComponent).standardizedFileURL.path
if resolved == resolvedRootPath { return true }
return resolved.hasPrefix(resolvedRootPath.hasSuffix("/") ? resolvedRootPath : resolvedRootPath + "/")
</file context>
| if let expectedProjectionRevision { | ||
| fields["expected_projection_revision"] = String(expectedProjectionRevision) |
There was a problem hiding this comment.
P2: When no projection exists, this omits the revision precondition, so concurrent first writes can both succeed and the later write can erase the earlier membership. Send revision 0 for nil; the caller can then retry the revision conflict against the latest projection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxCloudTui/Sources/CmuxCloudTui/CloudTuiFrontendProjectionRequests.swift, line 21:
<comment>When no projection exists, this omits the revision precondition, so concurrent first writes can both succeed and the later write can erase the earlier membership. Send revision `0` for `nil`; the caller can then retry the revision conflict against the latest projection.</comment>
<file context>
@@ -0,0 +1,26 @@
+ "generation": generation,
+ "projection": projection,
+ ]
+ if let expectedProjectionRevision {
+ fields["expected_projection_revision"] = String(expectedProjectionRevision)
+ }
</file context>
| if let expectedProjectionRevision { | |
| fields["expected_projection_revision"] = String(expectedProjectionRevision) | |
| fields["expected_projection_revision"] = String(expectedProjectionRevision ?? 0) |
| ) throws { | ||
| let (workspaceRaw, afterWorkspace) = parseOption(args, name: "--workspace") | ||
| let (surfaceOption, remaining) = parseOption(afterWorkspace, name: "--surface") | ||
| let positional = remaining.filter { !$0.hasPrefix("-") } |
There was a problem hiding this comment.
P2: This filter silently discards an incomplete --workspace option, so hibernate/wake proceeds without the requested workspace scope. Reject unparsed option tokens before sending the request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At CLI/CMUXCLI+AgentHibernation.swift, line 21:
<comment>This filter silently discards an incomplete `--workspace` option, so hibernate/wake proceeds without the requested workspace scope. Reject unparsed option tokens before sending the request.</comment>
<file context>
@@ -0,0 +1,56 @@
+ ) throws {
+ let (workspaceRaw, afterWorkspace) = parseOption(args, name: "--workspace")
+ let (surfaceOption, remaining) = parseOption(afterWorkspace, name: "--surface")
+ let positional = remaining.filter { !$0.hasPrefix("-") }
+ guard let surfaceRaw = surfaceOption ?? positional.first,
+ !surfaceRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
</file context>
| @@ -47,6 +60,9 @@ public struct WorkstreamItem: Identifiable, Codable, Sendable, Equatable { | |||
| public var status: WorkstreamStatus | |||
| public var payload: WorkstreamPayload | |||
| public var context: WorkstreamContext? | |||
| /// The terminal response associated with this exact event, when one was | |||
| /// submitted from mobile or another remote surface. | |||
| public var reply: WorkstreamReply? | |||
There was a problem hiding this comment.
P2: The identity migration drops reply when it rebuilds an item with a canonicalized workstream ID, so remote terminal replies disappear from loaded history after restart. Preserve item.reply in normalizedWorkstreamItem.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamItem.swift, line 65:
<comment>The identity migration drops `reply` when it rebuilds an item with a canonicalized workstream ID, so remote terminal replies disappear from loaded history after restart. Preserve `item.reply` in `normalizedWorkstreamItem`.</comment>
<file context>
@@ -47,6 +60,9 @@ public struct WorkstreamItem: Identifiable, Codable, Sendable, Equatable {
public var context: WorkstreamContext?
+ /// The terminal response associated with this exact event, when one was
+ /// submitted from mobile or another remote surface.
+ public var reply: WorkstreamReply?
/// PID of the agent process that emitted the event (hook's parent
/// pid). When non-nil, pending items get expired automatically as
</file context>
|
|
||
| - [Start here](https://github.com/manaflow-ai/cmux/blob/main/docs/start-here.md) covers what reviewers look for, what CI runs for you, and what happens next. | ||
| - If the CLA check asks, reply with the sentence it gives you. | ||
| - You don't need to run the app-host or UI tests locally. CI runs the ones your diff touches once a maintainer approves the first workflow run. |
There was a problem hiding this comment.
P2: This promises CI will run touched UI tests, but the UI-test lane is currently disabled unless CI_UI_TESTS_ENABLED=1, and it does not dispatch tests for fork PRs. Clarify that app-host suites are selected automatically while UI-test coverage depends on the lane being enabled and the PR being eligible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/contributor/welcome.md, line 9:
<comment>This promises CI will run touched UI tests, but the UI-test lane is currently disabled unless `CI_UI_TESTS_ENABLED=1`, and it does not dispatch tests for fork PRs. Clarify that app-host suites are selected automatically while UI-test coverage depends on the lane being enabled and the PR being eligible.</comment>
<file context>
@@ -6,5 +6,6 @@ A few things that help:
- [Start here](https://github.com/manaflow-ai/cmux/blob/main/docs/start-here.md) covers what reviewers look for, what CI runs for you, and what happens next.
- If the CLA check asks, reply with the sentence it gives you.
+- You don't need to run the app-host or UI tests locally. CI runs the ones your diff touches once a maintainer approves the first workflow run.
- The [verification ladder](https://github.com/manaflow-ai/cmux/blob/main/docs/contributor-verification.md) shows which checks fit your change. Say in the description which ones you ran.
- If we end up fixing the same problem another way, we'll credit you with a `Co-authored-by` trailer and link the fix here.
</file context>
| - You don't need to run the app-host or UI tests locally. CI runs the ones your diff touches once a maintainer approves the first workflow run. | |
| - CI selects touched app-host test suites automatically. UI tests run only when the UI-test lane is enabled and the PR is eligible; follow the [verification ladder](https://github.com/manaflow-ai/cmux/blob/main/docs/contributor-verification.md) for UI changes. |
| /// team and falls back like sign-in does otherwise. | ||
| func refreshTeams() async { | ||
| guard isAuthenticated else { return } | ||
| await refreshTeams(generation: sessionGeneration) |
There was a problem hiding this comment.
P2: This refresh can overwrite a team switch that completes while its fetch is in flight: the refresh applies its server selection without checking for a newer team mutation. Fence refresh results against team mutations or serialize refresh with selection so a stale response cannot restore the previous active scope.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator+TeamSelection.swift, line 92:
<comment>This refresh can overwrite a team switch that completes while its fetch is in flight: the refresh applies its server selection without checking for a newer team mutation. Fence refresh results against team mutations or serialize refresh with selection so a stale response cannot restore the previous active scope.</comment>
<file context>
@@ -82,4 +82,13 @@ public extension AuthCoordinator {
+ /// team and falls back like sign-in does otherwise.
+ func refreshTeams() async {
+ guard isAuthenticated else { return }
+ await refreshTeams(generation: sessionGeneration)
+ }
}
</file context>
# Conflicts: # SUPERMUX-TOUCHPOINTS.md # Sources/Devices/DeviceLink.swift
|
Merge receipt for
Labeled |
Merge upstream cmux main @ 4e9d779 (2026-10-01)
# Conflicts: # SUPERMUX-TOUCHPOINTS.md # Sources/Devices/DeviceLink.swift
Merges 259 first-parent upstream cmux commits (upstream main 2026-09-29 → 2026-09-30, through
4e9d779888). The headline upstream change is shared terminal sizing: a terminal viewed from the Mac, an iPhone, or another Mac through Devices now has one grid, sized by default to the smallest viewer, with bounds drawn in the divider color and Connected Devices… on iOS. User-facing notes and the one open decision are inSUPERMUX-UPGRADES.md(2026-10-01 section).Conflicts
25 files conflicted, resolved per SUPERMUX.md: upstream's code plus the re-applied
SUPERMUXfences. Most were mechanical (a fork line next to a new upstream argument, dependency, enum case, or CIif:). The ones that needed judgment:isAgentEventtoNotificationFeedHistoryRecord, whose fork fence carries explicitCodingKeys/init(from:). Without adding the key there, the flag would be silently dropped from persisted history; it is now encoded and decoded.--force). Upstream added a running-process confirmation to the socket, control and mobile close paths. It stays upstream's and runs before the fork's empty-home close; inTerminalController+MobileWorkspaceList.swiftthe fork fence is split in two around it.enqueueScrollMechanicsDeltakeeps its structure and now applies upstream'sgridDisplayScale, matching upstream's own delta path for grids scaled to fit.TerminalCopyAction.swift(new upstream file) switches exhaustively over the built-in actions; a fence (#600) adds.newClaudeHarness.SidebarWidthPolicyTests.swiftmoved to Swift Testing upstream; the right-sidebar minimum-width fences are re-expressed as#expect.RemoteDaemonRPCClientTimeoutIsolationTests.swift: upstream rewrote the second test without a PTY attach, so only the first test keeps the fork's queue fence.Localizable.xcstringsandproject.pbxproj: the clone's installed merge drivers are older copies and bailed, so both were merged manually (per-key union with upstream's current driver; union of added entries, thennormalize-pbxproj.py+check-pbxproj.sh). All 172 fork-added and 254 upstream-added pbxproj ids are present.Registry
Debug/).TerminalCopyAction.swift) and #601 (MobileDisplaySettings.swift). Numbers 517–599 are left free for the in-flight remote-workspaces branch, which already registers rows in that range.scripts/supermux-check-touchpoints.shpasses, and the fence inventory matches pre-merge except the intended changes above.Follow-up commits
fix(supermux): restore upstream catalog entries the previous merge left stale: the 2026-09-30 merge kept older wording for 18 upstream-owned strings (for example "Couldn't reach Stack" on sign-in errors) and re-added 21 keys upstream had deleted. Every non-supermux.key now equals upstream except the iOS: redial at once after the Mac's idle timeout instead of probing the dead session #84 exception.docs(supermux): upgrade notes for the 2026-10-01 upstream merge.Testing
CMUX_DEV_BACKEND_MODE=local CMUX_RELOAD_NO_GLOBAL_CLI_LINKS=1 CARGO_PROFILE_RELEASE_BUILD_OVERRIDE_STRIP=false ./scripts/reload.sh --tag sync-upsucceeded (merge commit; not launched).xcodebuild -workspace ios/cmux.xcworkspace -scheme cmux-ios -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 18 Pro' buildsucceeded../scripts/test-unit.sh build-for-testing(compile only, no tests run): the app target, including the restored catalog, compiles, and all 1,199cmuxTestsfiles were compiled with no errors in fork-touched files. The test target still fails on five files that are byte-identical to upstream main (CLIVMTransferTests,CloudWorkspaceLiveProjectionTests,LastSurfaceClosePreferenceTests,PaneResizeShortcutTests,WorkspaceCloseTabsContextMenuTests). That is upstream's own break at4e9d779888, with open fixes upstream (test: restore cmuxTests compile on main manaflow-ai/cmux#16285, #16245, #16306); the next merge should pick it up.swift test: SupermuxKit 1057/1058 in the full run (the timing-sensitivecloseRetainsEscalationUntilAnIgnoringProcessIsKilledfailed while the Mac build loaded the machine; its suite passes 16/16 alone), SupermuxMobileCore 88/88, SupermuxMobileKit 225/225, SupermuxMobileUI 205/205.test_ci_guard_workflow_structure.py,test_ci_workflow_guards_are_wired.py,test_ci_reusable_workflow_permissions.py,test_ci_workflow_path_filter_parity.py.test_ci_change_areas.pyfails the same way on a pristine upstream tree here (environmental).Not verified: app-hosted suites (not run locally by policy), any live behavior in the tagged Mac app or on a phone. Worth a dogfood pass: iPhone scrollback on a shared grid larger than the phone (the fork's bounded scroll geometry still uses the unscaled cell height), the Feed and Notifications tabs together, and a Devices mirror resizing its source terminal.
Changelog
Changed: Supermux now includes upstream cmux through 2026-09-30, including shared terminal sizing across Mac, iPhone and Devices mirrors, Connected Devices on iOS, and the iOS Feed (shown beside Notifications)