Repository navigation
cmux NEXT: per-PR kill switches (registry, expiry lint, signed Swift reader) - #24212
Conversation
…Swift reader (failing) The lint refuses an entry without a PR number or expiry and fails past expiry; the Swift reader turns off only what a verified flags.json lists for the ring, and nothing otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
killSwitch("pr-N") (CmuxNextCompat) answers whether the signed notes/flags.json beside the feed
turned PR N's change off; KillSwitchReader (CmuxNextUpdater) reads it at launch, when a check ends
and when the ring moves, verified with the content-signing key, and turns nothing off without a
verified document. release-notes/next/kill-switches.json declares each switch with its PR, owner
and expiry; scripts/cmux-next/check-kill-switches.py (cmux-next checks) refuses an entry without a
PR number or expiry, fails past expiry, and checks call sites and flags.json against it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 2241229 to compare; do not merge until green; see lane-rules |
|
Found 7 test failures on Blacksmith runners: Failures
|
|
Bead |
…int reads whole calls Review fixes: a read carries a generation, so an older read for a ring the updater moved back to never overwrites the newer list; KillSwitchReader.start(updater:) keeps the app's reader in KillSwitchReader.app and stops the one before; the lint finds a call split over lines and counts every evaluation, not files; the reader tests keep the weakly held updater alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
… task Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
…s and sees qualified calls Second review fixes: reads no longer overlap, so an older fetch can't rewrite the verified cache after a newer one; a read for another ring runs after the one in flight, and a read whose ring moved applies nothing. The lint masks comments and string contents before finding calls (reading the literal argument back from the source) and counts CmuxNextCompat.killSwitch(...) too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
|
|
…t, definition lines keep offsets Third review fixes, plus the Python test execution registry entry for the new lint test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
|
Review (Codex gpt-5.x, xhigh, independent, run on cmux16 from the diff): SHIP at 4e7a799 Four rounds. Rounds 1 to 3 asked for FIX (read ordering and generations, the reader's owner, one read at a time so verified-cache writes can't reorder, and lint tokenizing: multiline, qualified, comments, strings, interpolation, nested Swift comments, per-evaluation counting). All of those are fixed in 1f55d68, 4026da4, 14637db and 4e7a799. Round 4: "No findings. The three prior lint issues are fixed correctly, and the full diff reveals no blocker or should-fix bugs." |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
Bugbot needs on-demand usage enabledBugbot 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. |
|
Orphaned: no activity for 12 h, and no lane holds a lease on it. The orphan sweep could not merge |
…rings/kill-switches # Conflicts: # tests/test-execution.toml
|
|
|
|
… a same-ring read during a fetch (failing) Review of f53b67c (gpt-6.1-sol): `let check = killSwitch; check("pr-999")` passes the lint, a flags.json root that is not an object crashes it, and a read asked while the same ring's fetch is out joins that fetch and keeps its stale answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
Restore the CI lint and registry wiring, and fence in-flight reads when a reader is stopped or replaced.\n\nAgent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c Agent-Session: https://cmux.com/deeplink/agent?id=aid-4777d4f75452817a742b
Reject boolean and fractional flags documents before signing or publishing them.\n\nAgent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c Agent-Session: https://cmux.com/deeplink/agent?id=aid-4777d4f75452817a742b
Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c
Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c
Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c
Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c
|
Shipped in:
|
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/40822487758 at cc6b506 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/40823474936 at c8b6445 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
…reader) (#24212) * test(kill-switches): per-PR kill switch registry lint and the signed Swift reader (failing) The lint refuses an entry without a PR number or expiry and fails past expiry; the Swift reader turns off only what a verified flags.json lists for the ring, and nothing otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * feat(next): per-PR kill switches read from the signed flags document killSwitch("pr-N") (CmuxNextCompat) answers whether the signed notes/flags.json beside the feed turned PR N's change off; KillSwitchReader (CmuxNextUpdater) reads it at launch, when a check ends and when the ring moves, verified with the content-signing key, and turns nothing off without a verified document. release-notes/next/kill-switches.json declares each switch with its PR, owner and expiry; scripts/cmux-next/check-kill-switches.py (cmux-next checks) refuses an entry without a PR number or expiry, fails past expiry, and checks call sites and flags.json against it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * kill switches: newest read wins, the app's reader has an owner, the lint reads whole calls Review fixes: a read carries a generation, so an older read for a ring the updater moved back to never overwrites the newer list; KillSwitchReader.start(updater:) keeps the app's reader in KillSwitchReader.app and stops the one before; the lint finds a call split over lines and counts every evaluation, not files; the reader tests keep the weakly held updater alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * kill switches: build the reader's observation outside its weakly held task Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * kill switches: one read at a time; the lint skips comments and strings and sees qualified calls Second review fixes: reads no longer overlap, so an older fetch can't rewrite the verified cache after a newer one; a read for another ring runs after the one in flight, and a read whose ring moved applies nothing. The lint masks comments and string contents before finding calls (reading the literal argument back from the source) and counts CmuxNextCompat.killSwitch(...) too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * kill switches lint: interpolations are code, Swift block comments nest, definition lines keep offsets Third review fixes, plus the Python test execution registry entry for the new lint test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * test(kill-switches): a function reference, a malformed flags.json and a same-ring read during a fetch (failing) Review of f53b67c (gpt-6.1-sol): `let check = killSwitch; check("pr-999")` passes the lint, a flags.json root that is not an object crashes it, and a read asked while the same ring's fetch is out joins that fetch and keeps its stale answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * kill switches: the lint refuses a reference to killSwitch and a malformed flags.json; a read asked during a fetch reads again after it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * test(kill-switches): the same-ring read test swaps the document only once both fetches are out Without the wait, the reader's fetches could start after the swap and the test passed on the old reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue * fix(kill-switches): keep portable checks and scoped change size Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16a58-a194-77a2-8388-63f4c432541c&host=huge-blue * fix(kill-switches): keep portable checks and scoped change size Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16a58-a194-77a2-8388-63f4c432541c&host=huge-blue * ci: record kill switch singleton exception Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c * test: reject stale kill switch reads and malformed versions Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c * fix: close kill switch reader races Reject stale ring generations, cancel observation with updater teardown, and require strict integer version documents.\n\nAgent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c Agent-Session: https://cmux.com/deeplink/agent?id=aid-4777d4f75452817a742b * fix: wire and stop kill switch readers Restore the CI lint and registry wiring, and fence in-flight reads when a reader is stopped or replaced.\n\nAgent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c Agent-Session: https://cmux.com/deeplink/agent?id=aid-4777d4f75452817a742b * fix: validate nightly flag versions strictly Reject boolean and fractional flags documents before signing or publishing them.\n\nAgent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c Agent-Session: https://cmux.com/deeplink/agent?id=aid-4777d4f75452817a742b * test: reject malformed disabled feature lists Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c * fix: reject malformed disabled feature lists Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c * fix: allow omitted empty feature lists Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c * fix: keep defaults guard stable for switch validation Agent-Session: 01a16c64-8a5c-7b91-a64f-94c2bf076d8c --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Leo Li <t@e> Agent-Session: https://cmux.com/deeplink/agent?id=aid-289f5b14ddf157d9472f
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/40823842282 at 6ac0d64 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/40823925460 at 698b3ef failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
… capture backdrop is DEBUG/bench only (#28263) * fix(design): the capture backdrop panel is DEBUG and bench only, as its only callers are An independent exception review (reward-hacking audit, arch-ratchet tip rises) found that CaptureBackdropPattern.swift (8fb4805, #27514) ships its CaptureBackdropPanel, its willClose observer and NSWindow.setCaptureBackdrop in Release, although its only callers (DebugWindowSnapshot.captureBackdrop, the debug.capture_backdrop route in AppControl+Input) are `#if DEBUG || CMUX_BENCH_HOOKS`, and its exception said "DEBUG-only". The panel, its view and the two NSWindow members that make or read it now sit in the same `#if`; Release keeps the two read helpers that NSWindow+Snapshot uses, as constants (no backdrop: none to skip, nothing behind). The pattern enum stays (a value type, no observer). Release behavior: unchanged, since nothing could make a backdrop there. * arch-ratchet.json: 12 reviewed exceptions for the tip rises an independent reviewer judged needed; 9 unreviewed pending entries removed Reward-hacking audit (handoff/followups.md "arch-ratchet tip rises (no exceptions)"). An independent exception reviewer (it wrote none of the code and none of the old reasons) judged 14 candidates at 2391c5e: 12 EXCEPTION-OK, 2 FIX (AppDelegate.swift:514's quit flush, on fix/arch-rise-quit-flush; the capture backdrop, gated in 2391c5e and then accepted). The reviewer wrote each entry's receipt itself; each has author, owner and expiry 2026-11-30, one site per entry (allow 1): global_mutable_statics PaletteScope lock (#28036), CrashWatcher descriptor (#22844); stateful_singletons KillSwitches.shared (#24212), BlockingViolationTable.shared (f668c89); observer_sites DesktopWallpaperWatch :144 :147 :148 :149 (#25308), IPhoneHostService wake (#27297), DebugWindowRecord willClose (#23680), CaptureBackdropPattern willClose (#27514); blocking_allow_escapes SingleInstanceGuard (#25918), plus its escape_reviews entry (read by the rule of #28195). Removed: the 9 PENDING entries with no review that these replace or that cover no site any more (CrashWatcher, BlockingViolationTable, DesktopWallpaperWatch +7, SingleInstanceGuard, HomeReduceMotion (a FIX), kill switches +2, PaletteScope +4 (statics gone) and +1, CaptureBackdropPanel). No applied entry, total or measured_sha changes; no ratchet number is raised. --update still refuses until the fix PRs land; it is run after them.
… become instances (#28266) * arch: active-look glass scope, batch-close set and kill-switch reader become instances Lowers the arch-ratchet rows these sites raised with no reviewed exception (audit-rh, cmux-ad); no behavior change. - CmuxNextDesign/GlassPanelView.swift: the seven ActiveLookScope statics (windows, pending, candidate, materialSite, installed, counts, kinds; 1335e10, #25704) are one ActiveLookScope instance. The AppKit getter hook that its first ActiveLookGlassEffectView installs captures it. AppServices makes it once and hands it to the app's ThemeStore (ThemeStore.activeLook). The main window gets it through WindowRootView(activeLook:), every window-kit window through NSWindow.install (scope.store.activeLook) and WindowSurfaceView, and the backdrop through WindowMaterialView(activeLook:) and GlassPanelView(activeLook:). The hook itself is unchanged. - CmuxNextAppCore/WorkspaceSelectionCloseFlow.swift: the static running set (a439d29, #24341) is WorkspaceSelectionCloseFlow.Running, one per app (AppServices.selectionCloses), passed to every batch close and read by Close Workspace. It stays app-wide, as the static was. - CmuxNextUpdater/KillSwitchReader.swift: the static weak app reader and start(updater:) (52c918f, #24212) are gone; UpdaterService stops its previous reader through its own killSwitchReader property. - CmuxNextApp/MainMenu+Placements.swift: removes the unused helpGroups, groupedItems and otherItems (dead since #24615), which pays for the AppServices wiring (app_lines -24). Tests construct their own ActiveLookScope (InactiveWindowTintTests), and one more check proves a backdrop with no scope keeps plain glass. Not compiled or run here (no Swift on the agent VM); fleet compile-check of the pushed head follows. * arch: thread the active-look scope to every window owner, no ThemeStore slot; one getter hook per process Review of 723ce04 (FIX): ThemeStore.activeLook put the one ActiveLookScope in a public settable slot on ThemeScope.app.store, a static by another name, read by six window owners through NSWindow.install. - ThemeStore.activeLook is removed. AppServices holds the scope (AppServices.activeLook) and passes it down. - NSWindow.install(kind:content:scope:activeLook:) takes the scope as a parameter with no default, and every caller passes it: - the main window (WindowController), from AppServices; - browser popups (BrowserPopupWindows, from AppServices) and sign-in panels (WebAuthSessionWindows from AppDelegate, then WebAuthSessionWindow, then BrowserPopupWindow); - the onboarding tool window (OnboardingService, then OnboardingWindowPresenter, then OnboardingWindowController); - Debug Settings (DebugSettingsService, then DebugSettingsWindowController); - DevTools and the page info windows, which are child windows: they pass their owner window's NSWindow.backdropActiveLook, which reads the owner's own WindowMaterialView (as they already take the owner's themeScope); - BrowserDebugWindow passes nil: it has no entry point. - ActiveLookScope.install adds a marker method to NSWindow with class_addMethod. Only the first install can add it, so the getter hook is installed once per process and a later scope (a test's) never stacks a second hook over the first. A stacked hook could push the reader past the 16-frame forcingDepth. - Tests pass activeLook: nil where they built these windows. Not compiled here (no Swift on the agent VM); the fleet compile-check of the pushed head follows.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo-ask: ask-8ae00152f6
Leo, 2026-10-22: "shouldn't there be ways to make things more resilient? if we're like bb (or better) and are able to hot reload and hot swap and have richer changelog situations, shouldnt we be able to just do partial rollbacks on the fly?"
Design: partial-rollback.md, section 2 (per-PR kill switches). This is PR 1 of 3 for that section: the registry, the lint and the Swift reader.
What it does
killSwitch("pr-N")(CmuxNextCompat/KillSwitches.swift): a risky change keeps its old path and asksif killSwitch("pr-23456") { old } else { new }. It returns true only when the signed flags document has turned that PR's change off. The key is aStaticString, so it has to be a literal.CmuxNextUpdater/KillSwitchReader.swift,ReleaseNotes/KillSwitchDocument.swift,ReleaseNotesStore.flags()): reads thenotes/flags.jsonthat every nightly already publishes and signs. It uses the release-notes fetch, so it gets the same Ed25519 check and verified cache. It applies theDisabledFeaturesentries namedpr-<N>for the app's ring: the cohort isupdates.channel, and a cohort's list replaces the default's, as cmux-tui'sfeature_flags.rsdoes.release-notes/next/kill-switches.json, empty for now):{"pr-N": {"pr": N, "owner", "expires": "YYYY-MM-DD", "default": "new-path"}}.scripts/cmux-next/check-kill-switches.py, withtests/test_cmux_next_kill_switches.py): rejects missing or mismatched PR numbers, missing owner/expiry, expired or over-90-day entries, a non-new-pathdefault, non-literal/undeclared/duplicate call sites, undeclared entries, and flags that turn off an undeclaredpr-N.The registry is empty, so this PR changes no default behavior.
KillSwitchReader.follow()uses oneObservationStreamover updater ring and flow phase, with cancellation and no timer or polling. Release-notes work stays on the existing concurrent path. The reader is outsideUpdaterServiceand starts at the updater composition seam.What follows (PRs 2 and 3): the web reader/bootstrap and a per-merge signed
flags-publishworkflow. Those need separate review of web wiring and trust policy. This PR does not add call sites, signing secrets, workflow permissions, or a publishing path.No capture: no visible change.
Changelog
none
Testing
The original failing tests were committed first; the final head is rebased onto
feat-cmux-nextat2241229c6708f7cb07bd2507f86806e49468febc.Observed on final head
06b2074b13339f58bd537524bbc25fa4a1bee877:python3 tests/test_cmux_next_kill_switches.py: 24 passed.python3 scripts/cmux-next/check-kill-switches.py: ok.python3 scripts/cmux-next/prepush-gate.py: no new violations; the requiredstateful_singletons +2exception is recorded inplans/cmux-next/arch-ratchet.json.python3 scripts/ci/validate_test_execution_registry.py --repo-root .: valid (549 tests; only pre-existing unregistered-test warnings).git diff --check origin/feat-cmux-next...HEAD: clean.Observed / Not run
Observed:
cmux-ci compile-check --app --recipe-version 2completed successfully for06b2074b13339f58bd537524bbc25fa4a1bee877as joba788f9a14f35e4d105fd035b(artifactsha256:ba6d822f3c3dc804bc028e7d4c0e624158c25d296233002307000e3b229309dc).SHIP at 06b2074b13339f58bd537524bbc25fa4a1bee877.swift test mobile-core (portable)red is in untouchedPackages/iOS/CmuxMobileShellModel; it is a base finding and is not caused by this updater-only change. Required checks are complete (requiredMissing: []); remaining guard jobs are non-required.Not run:
CmuxNextAppTests/KillSwitchesTestscannot run until the existing base test-target compile red (AgentPaneTabRowHolesTests.swift:103,HoleRecordingPage does not conform to PageChromiumPage) is repaired; this PR does not touch that test fixture.Risk:
risk:highbecause this is updater-path code. The change only consumes an already-signed, already-verified document and does not touch signing, install, secrets or workflow permissions.Agent session: agent session