Skip to content

Clean stale release theme overrides for channel builds - #4502

Closed
austinywang wants to merge 28 commits into
mainfrom
issue-4501-stale-theme-cleanup
Closed

austinywang wants to merge 28 commits into
mainfrom
issue-4501-stale-theme-cleanup

Conversation

@austinywang

@austinywang austinywang commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4501

Summary

  • add a regression for stale release-bundle cmux theme blocks shadowing nightly after themes clear
  • strip stale managed theme blocks from com.cmuxterm.app/config.ghostty when non-release channel builds run cmux themes set or cmux themes clear

Testing

  • Not run locally per repo instructions; CI will run required checks.

Note

Medium Risk
Changes theme/config file writes and Ghostty reload color-scheme logic across CLI and app; mistakes could affect channel users' themes or terminal appearance, though behavior is heavily regression-tested.

Overview
Channel builds (nightly/staging/debug) no longer inherit stale # cmux themes blocks from the release app-support config when they have no channel config of their own. Config resolution now scans multiple Application Support roots, strips only the managed theme block (or deletes the file if nothing else remains) before release fallback, and CLI themes runs that cleanup on entry, after clear, and surfaces cli.themes.error.cleanupFailed when cleanup cannot complete.

Ghostty reload treats true light/dark theme pairs as appearance-driven: resolves the active theme at load, locks terminal color scheme during cmux theme reloads, and reapplies surface schemes before refresh to reduce flashes. CI bumps actions/checkout to v6.0.2 in the perf activation workflow.

Reviewed by Cursor Bugbot for commit 1c13f5a. Bugbot is set up for automated code reviews on this repo. Configure here.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Cleans stale release-managed “# cmux themes” blocks for channel builds (nightly/staging/debug) and resolves light/dark theme pairs by appearance while keeping the terminal scheme stable. Fixes #4501 to stop channel themes being shadowed and reduce flicker on startup and reloads.

  • Bug Fixes

    • Centralize cleanup across all user Application Support roots; strip the managed block (or delete the file if only the block remains) from release configs (config.ghostty and legacy config); only when a channel build would fall back to release; abort with a localized, sanitized error on failure.
    • Use multi-root cmux config loading in the app and CLI (Ghostty and CJK scan paths), preferring channel configs when present and avoiding stale release fallback.
    • Resolve light:...,dark:... pairs at startup and reload by appearance; inline the resolved theme; lock terminal surface color scheme during reload to preserve the runtime scheme; for cleared-theme reloads, use the requested appearance.
  • Dependencies

    • CI perf activation workflow now uses actions/checkout@v6.0.2.

Written for commit 1c13f5a. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Stale release-managed theme blocks are now removed when updating or clearing themes for a different build, preserving unrelated settings and surfacing an error message if cleanup fails.
  • Tests
    • Added regression tests verifying stale managed blocks are removed on theme set/clear and that listings reflect the cleared state.
  • Documentation
    • Added a localized error message for cleanup failures.

Review Change Stack

@vercel

vercel Bot commented May 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 26, 2026 10:17pm
cmux-staging Building Building Preview, Comment May 26, 2026 10:17pm

@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a helper that removes stale release-managed theme override blocks when the active bundle identifier differs from the configured theme override bundle; the helper is invoked at the start of writeManagedThemeOverride and clearManagedThemeOverride. Two regression tests and a localization string were added.

Changes

Stale Theme Override Cleanup

Layer / File(s) Summary
Stale cleanup helper and integration
CLI/CMUXCLI+ThemeSupport.swift, Resources/Localizable.xcstrings
New removeStaleReleaseManagedThemeOverrideIfNeeded() detects when the active bundle differs from the configured theme override bundle, loads the release override config.ghostty, strips managed # cmux themes start/end blocks, and deletes or rewrites the file as needed. It's called at the start of writeManagedThemeOverride and clearManagedThemeOverride. Adds cli.themes.error.cleanupFailed localization.
Regression tests for stale block removal
cmuxTests/CMUXCLIErrorOutputRegressionTests.swift
Adds two tests that create separate release and nightly config.ghostty files with managed theme blocks, run themes set and themes clear targeting nightly, and verify the release config no longer contains the managed block while unrelated settings remain; themes list asserts current theme values are null.

Sequence Diagram(s)

sequenceDiagram
  participant CMUXCLI
  participant removeStaleReleaseManagedThemeOverrideIfNeeded
  participant ReleaseConfigFile
  CMUXCLI->>removeStaleReleaseManagedThemeOverrideIfNeeded: invoke before write/clear
  removeStaleReleaseManagedThemeOverrideIfNeeded->>ReleaseConfigFile: load release config (cmuxThemeOverrideBundleIdentifier)
  removeStaleReleaseManagedThemeOverrideIfNeeded->>ReleaseConfigFile: strip managed themes block
  removeStaleReleaseManagedThemeOverrideIfNeeded->>ReleaseConfigFile: delete file if empty or rewrite normalized contents
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#4484: Both PRs modify CLI/CMUXCLI+ThemeSupport.swift behavior around override bundle identifiers and config target paths; this PR adds stale-block cleanup while #4484 made override paths bundle-aware.

Poem

🐇 I nibbled through configs, tidy and bright,
Cleared old theme ghosts that hid from the light,
Nightly and release no longer clash,
Files now sparkle — no more cache,
A hop, a fix, and everything’s right.

🚥 Pre-merge checks | ✅ 16 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: cleaning stale release theme overrides when non-release channel builds run, which is the primary objective of this PR.
Linked Issues check ✅ Passed The code changes fully address #4501 by implementing cleanup of stale managed theme blocks from com.cmuxterm.app/config.ghostty when non-release builds run themes set/clear, and adding regression tests to verify the fix.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing #4501: cleanup logic in CLI+ThemeSupport.swift, regression tests, and localization key for the cleanup error message.
Cmux Swift Actor Isolation ✅ Passed CLI code using synchronous FileManager operations in main() context - no implicit MainActor isolation issues or background access patterns introduced.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing-based synchronization primitives introduced. Production code only uses synchronous file I/O and string processing for stale theme override cleanup.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift code and localization. Rule covers TypeScript, JavaScript, shell, non-Swift scripts; Swift is explicitly excluded per rule scope.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns in production code. Main Swift file uses synchronous error-throwing APIs only. Test DispatchQueue usage is allowed per guidelines.
Cmux Swift @Concurrent ✅ Passed No async functions or @concurrent annotations were added. All new code performs synchronous file I/O operations (removeItem, write), which are appropriate for CLI command execution.
Cmux Swift File And Package Boundaries ✅ Passed Focused bug fix: 53-line addition to existing 478-line extension file for stale override cleanup. Meets allowed-case criteria: small addition, clear extraction path, Ghostty integration glue.
Cmux Swift Logging ✅ Passed No prohibited logging patterns (print, debugPrint, dump, NSLog) found. Changes are CLI code with proper localized CLIError output, which is an allowed category per swift-logging.md rules.
Cmux User-Facing Error Privacy ✅ Passed Error message complies with privacy rules. No vendor names, credentials, tokens, raw errors, or sensitive details exposed. Exception handling wraps original errors in sanitized localized message.
Cmux Full Internationalization ✅ Passed New user-facing error uses String(localized:defaultValue:) with key "cli.themes.error.cleanupFailed", which has complete translations for all 20 locales in Localizable.xcstrings.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI code changes; modifications are limited to CLI command implementation (Foundation-based), test code, and localization strings. SwiftUI state layout check is not applicable.
Cmux Architecture Rethink ✅ Passed Targeted correctness fix with clear invariant: removes stale config only when bundle ID changes. No timing/dispatch/locks/observers, single call path, CLI-only code, proper error handling.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR changes only theme override cleanup logic in CLI/CMUXCLI+ThemeSupport.swift, tests, and localization. No window code (NSWindow, NSPanel, NSWindowController, Window, WindowGroup) added or modified.
Description check ✅ Passed The PR description includes a clear summary of changes, testing approach, and confirms CI validation. Required checklist items are addressed or marked as N/A per repo instructions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4501-stale-theme-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes stale # cmux themes managed blocks from release (com.cmuxterm.app) configs shadowing nightly/debug/staging channel builds when those channels have no channel-specific config. Cleanup is centralized in CmuxGhosttyConfigPathResolver and triggered at config discovery, and at the start of cmux themes set, themes clear, and the bare themes picker; failed cleanup aborts the command with a localized error.

  • CmuxGhosttyConfigPathResolver gains a multi-app-support-directory API, a removeStaleReleaseManagedThemeOverrideIfNeeded helper that strips or deletes the release config's managed block when a channel bundle falls back to it, and helper functions shared with CMUXCLI+ThemeSupport.
  • GhosttyTerminalView adds light:...,dark:... pair resolution at startup and reload (inlining the resolved theme name into config load), and prepareTerminalSurfacesForConfigurationReload to pre-apply and lock the surface color scheme before ghostty_app_update_config fires, reducing appearance flicker.
  • Regression tests cover nightly themes set, themes clear, the bare picker, cleanup failure abort, and resolver edge cases; CI bumps actions/checkout to v6.0.2.

Confidence Score: 5/5

Safe to merge — all cleanup paths are guarded and idempotent, error messages are properly sanitized and localized, and the surface color-scheme pre-apply correctly branches on main vs. background thread.

The stale-block removal is idempotent and guarded by bundle-identifier checks at every entry point; no cleanup runs for the release bundle itself or bundles outside the allowed-fallback set. The localized error for cleanup failures sanitizes all NSError details. Thread handling in prepareTerminalSurfacesForConfigurationReload uses MainActor.assumeIsolated on the main thread and async dispatch off it, matching the pattern of the sibling scheduleSurfaceRefreshAfterConfigurationReload helper. Regression tests cover set, partial set, clear, picker, failure abort, and resolver edge cases at the process level.

No files require special attention.

Important Files Changed

Filename Overview
Sources/CmuxApplicationSupportDirectories.swift Core cleanup logic added to CmuxGhosttyConfigPathResolver: multi-directory configURLs, removeStaleReleaseManagedThemeOverrideIfNeeded (strip/delete), and deduplication helpers. Logic is well-guarded and idempotent.
Sources/GhosttyTerminalView.swift Theme pair resolution at startup/reload via loadResolvedCmuxThemePairOverrideIfNeeded; prepareTerminalSurfacesForConfigurationReload correctly uses MainActor.assumeIsolated on main thread and DispatchQueue.main.async off-main. Multi-directory API threading throughout.
CLI/CMUXCLI+ThemeSupport.swift removeStaleReleaseManagedThemeOverrideIfNeeded wired at three points in clearManagedThemeOverride; error wrapping sanitizes NSError details into the localized message.
CLI/CMUXCLI+Themes.swift Pre-emptive stale-release cleanup added at the top of the themes dispatch function, covering the bare picker, set, and clear paths before any routing occurs.
Resources/Localizable.xcstrings New cli.themes.error.cleanupFailed key with 19 locale translations — exceeds coverage of existing strings.
Sources/AppDelegate.swift Adds applyTerminalSurfaceColorSchemeForGhosttyConfigReload to fan out color-scheme pre-application to all terminal panels before a config reload fires.
cmuxTests/CMUXCLIErrorOutputRegressionTests.swift Four new process-level regressions: nightly themes set, partial-light set, cleanup-failure abort, and themes clear with subsequent list check.
cmuxTests/GhosttyConfigPathResolverTests.swift Unit tests cover strip-on-fallback, delete-when-only-managed-block, skip-when-nightly-config-exists, and legacy config filename cases.
Sources/GhosttyConfig.swift Switches from single FileManager.default appSupportDirectory to CmuxApplicationSupportDirectories.userDirectories for scan-paths and config-URL APIs.
.github/workflows/perf-activation.yml actions/checkout bumped from pinned v4 SHA to pinned v6.0.2 SHA — routine dependency update.

Sequence Diagram

sequenceDiagram
    participant CLI as cmux CLI
    participant Resolver as CmuxGhosttyConfigPathResolver
    participant FS as Filesystem
    participant App as GhosttyApp

    Note over CLI: themes set / clear / bare picker
    CLI->>Resolver: removeStaleReleaseManagedThemeOverrideBeforeFallbackIfNeeded
    Resolver->>FS: configURLs(nightly) → empty?
    alt nightly has own config
        Resolver-->>CLI: return (skip cleanup)
    else nightly falls back to release
        Resolver->>FS: read release config.ghostty
        Resolver->>FS: strip managed block (or delete if empty)
        Resolver-->>CLI: done
    end
    CLI->>FS: write managed theme override (nightly config)

    Note over App: startup / reload
    App->>Resolver: loadConfigURLs(nightly, [appSupportDirs])
    alt nightly config exists
        Resolver-->>App: [nightly config URL]
    else fallback to release
        Resolver->>FS: try? removeStaleReleaseManagedThemeOverrideIfNeeded
        Resolver-->>App: [release config URL] (cleaned)
    end
    App->>App: loadResolvedCmuxThemePairOverrideIfNeeded
    App->>App: prepareTerminalSurfacesForConfigurationReload
    App->>App: ghostty_app_update_config
Loading

Reviews (21): Last reviewed commit: "Use multi-root cmux config loading in ap..." | Re-trigger Greptile

Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated
Comment thread cmuxTests/CMUXCLIErrorOutputRegressionTests.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLI/CMUXCLI`+ThemeSupport.swift:
- Around line 290-298: The catch-all in
removeStaleReleaseManagedThemeOverrideIfNeeded currently throws a generic
CLIError and discards the underlying error; update the generic catch to include
the original error details by constructing the CLIError message with
String(describing: error) (e.g., CLIError(message: "Unable to clean stale cmux
theme override: \(String(describing: error))")), leaving the existing catch-let
CLIError path unchanged and keeping the call to
removeStaleReleaseManagedThemeOverride() as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dd094446-6e3c-4e5c-a78c-a09a655b7065

📥 Commits

Reviewing files that changed from the base of the PR and between e88d25e and a975b91.

📒 Files selected for processing (1)
  • CLI/CMUXCLI+ThemeSupport.swift

Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLI/CMUXCLI`+ThemeSupport.swift:
- Around line 296-300: The cleanup failure currently injects the raw underlying
error into the user-facing CLIError (see CLIError usage and the localized key
"cli.themes.error.cleanupFailed" and the local variable format), which can leak
internal filesystem/bundle details; change the thrown CLIError to only contain a
sanitized, non‑sensitive message (e.g., the localized string without
interpolating String(describing: error)). If you need diagnostic details, send
the raw error to a debug/internal logger (not to the CLIError) using the
existing logging facility instead of exposing it in the thrown error.

In `@Resources/Localizable.xcstrings`:
- Line 19: The localization entry for the key cli.themes.error.cleanupFailed in
Resources/Localizable.xcstrings contains copied English text for many
non-English locales; replace each non-English "Unable to clean stale cmux theme
override: %@" value with proper translations for that locale (or mark the locale
for proper translation workflow) so no locale slot contains copied English,
TODO, placeholder or machine markers; update the corresponding "stringUnit" ->
"value" for each affected locale (e.g., ja, fr, de, es, ko, zh-Hans, etc.)
ensuring the format specifier "%@" is preserved.
- Line 19: The localization entry cli.themes.error.cleanupFailed currently
includes an interpolated %@ which can leak internal details; remove the %@ from
the user-facing string so it becomes a fixed localized message (e.g., "Unable to
clean stale cmux theme override.") and ensure the calling code logs the raw
error details only to debug/diagnostic logs (not the user-facing message).
Update the localization key cli.themes.error.cleanupFailed to omit interpolation
and modify the cleanup code that emits this message to call
processLogger.debug/processLogger.errorWithDetails (or equivalent) with the raw
error while showing the fixed localized string to users.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b1c2e6a-7ccd-46dc-9f8a-56a62fe46938

📥 Commits

Reviewing files that changed from the base of the PR and between a975b91 and fcf0009.

📒 Files selected for processing (2)
  • CLI/CMUXCLI+ThemeSupport.swift
  • Resources/Localizable.xcstrings

Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated
Comment thread Resources/Localizable.xcstrings Outdated
Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated
Comment thread CLI/CMUXCLI+Themes.swift Outdated
Comment thread Sources/CmuxApplicationSupportDirectories.swift Outdated
Comment thread Sources/CmuxApplicationSupportDirectories.swift Outdated
@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression where a stale # cmux themes block left in the release config.ghostty by a previous release-channel session could shadow nightly/staging/debug appearance after themes clear. It centralizes managed-block markers and strip logic in CmuxGhosttyConfigPathResolver, wires cleanup into themes set, themes clear, bare themes picker, and themes list entry points, and also resolves light:/dark: theme pairs against the current appearance on initial load and reload.

  • Adds removeStaleReleaseManagedThemeOverrideIfNeeded to CmuxGhosttyConfigPathResolver with strip-or-delete semantics, called silently during config URL discovery and mandatorily at all themes CLI entry points for non-release channels that would fall back to release config.
  • Fixes runtimeColorSchemeForConfigLoad to use the requested appearance for cleared-theme and paired-theme reloads, adds loadResolvedCmuxThemePairOverrideIfNeeded for initial load, and changes prepareTerminalSurfacesForConfigurationReload to dispatch asynchronously off the main thread to avoid a deadlock surface from the previous DispatchQueue.main.sync.
  • Adds cli.themes.error.cleanupFailed to Localizable.xcstrings with translations for all 20 supported locales, and expands regression coverage with four new process-level and three new unit-level tests.

Confidence Score: 4/5

Safe to merge once the two unlocalized CLIError messages are routed through String(localized:defaultValue:) with catalog entries.

The stale-block cleanup logic is well-structured, idempotent, and thoroughly tested. The two new guard-exit CLIErrors — "Unable to resolve Application Support directory" — are raw English literals that bypass the localization system, surfacing verbatim in all locales, whereas cleanupFailed in the same PR was correctly localized across all 20 catalog locales.

CLI/CMUXCLI+ThemeSupport.swift and CLI/CMUXCLI+Themes.swift — both contain the new unlocalized error messages that need catalog entries added to Resources/Localizable.xcstrings.

Important Files Changed

Filename Overview
CLI/CMUXCLI+ThemeSupport.swift Adds stale release managed-theme cleanup wired into clearManagedThemeOverride; two new CLIError messages not localized.
CLI/CMUXCLI+Themes.swift Adds pre-themes-command cleanup guard for fallback channels; one new unlocalized CLIError message.
Sources/CmuxApplicationSupportDirectories.swift Centralizes block markers and adds strip/delete cleanup, existingConfigURLs to CmuxGhosttyConfigPathResolver.
Sources/GhosttyTerminalView.swift Adds pair-theme initial load resolution, async surface prep before config reload, and a private dead-code GhosttyConfig overload.
Sources/AppDelegate.swift Adds applyTerminalSurfaceColorSchemeForGhosttyConfigReload fan-out helper.
Resources/Localizable.xcstrings Adds cli.themes.error.cleanupFailed with complete translations for all 20 supported locales.
cmuxTests/CMUXCLIErrorOutputRegressionTests.swift Comprehensive process-level regression coverage: themes set, partial-light set, cleanup-failure abort, themes clear, and picker stale-block cleanup.

Reviews (14): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/GhosttyTerminalView.swift
@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes stale release-channel # cmux themes blocks shadowing nightly/staging/debug builds after themes clear by centralising block-marker constants and regex removal in CmuxGhosttyConfigPathResolver, then stripping (or deleting) the release config's managed block whenever a non-release channel build runs themes set, themes clear, or the interactive picker. It also tightens Ghostty config reload to resolve true light:…/dark:… theme pairs against the current appearance, and replaces a DispatchQueue.main.sync deadlock surface with a main-thread-safe pre-apply path.

  • CLI cleanup: removeStaleReleaseManagedThemeOverrideIfNeeded is called at the top of runThemes (covers set, clear, and picker) and inside clearManagedThemeOverride; cleanup failures surface the new fully-translated cli.themes.error.cleanupFailed and abort without leaking paths.
  • App-side cleanup: CmuxGhosttyConfigPathResolver.configURLs silently best-efforts the same strip/delete when a non-release build falls back to the release config at load time.
  • Reload improvements: prepareTerminalSurfacesForConfigurationReload pre-applies the effective color scheme (sync on main, async off-main); loadResolvedCmuxThemePairOverrideIfNeeded injects an inline resolved-theme override at startup and reload for true light/dark pairs; cleared-theme reloads now correctly use the requested appearance instead of the stale terminal scheme."

Confidence Score: 5/5

Safe to merge; the bug fix is well-targeted, the cleanup logic is idempotent and covered by extensive process-level and unit regressions, and the previous deadlock surface in the reload path has been correctly replaced.

The stale-block cleanup is idempotent, error-contained, and covered by four new process-level regressions plus three new unit tests that exercise strip, delete, leave-alone, abort-on-failure, and the interactive-picker path. The app-side silent swallow is intentional best-effort. The only finding is a pair of guard-failure error strings that follow an existing pre-PR convention and are in an essentially unreachable codepath on a working macOS system.

No files require special attention; the two new non-localized guard-failure messages in CLI/CMUXCLI+ThemeSupport.swift and CLI/CMUXCLI+Themes.swift follow the pre-existing convention and are in a near-unreachable codepath.

Important Files Changed

Filename Overview
CLI/CMUXCLI+ThemeSupport.swift Adds removeStaleReleaseManagedThemeOverride + wrapper; delegates regex removal to CmuxGhosttyConfigPathResolver; two new guard-failure CLIError messages are hardcoded English and not in the string catalog.
CLI/CMUXCLI+Themes.swift Wires removeStaleReleaseManagedThemeOverrideBeforeFallbackIfNeeded at the top of runThemes so both themes set and the interactive picker strip the stale release block before writing; the Application Support guard throws a non-localized CLIError.
Sources/CmuxApplicationSupportDirectories.swift Centralises block markers and regex removal; adds removeStaleReleaseManagedThemeOverrideIfNeeded (strips or deletes the release config) and existingConfigURLs; silently swallows cleanup errors on the app-side fallback path, which is intentional best-effort behavior.
Sources/GhosttyTerminalView.swift Adds prepareTerminalSurfacesForConfigurationReload (sync on main, async off-main, replacing the prior .sync deadlock surface), resolves true light/dark pairs against current appearance at load and reload, and passes resolvedColorScheme through updateDefaultBackground/applyDefaultBackground to preserve the effective terminal color scheme for theme pairs.
cmuxTests/CMUXCLIErrorOutputRegressionTests.swift Adds four new regressions: nightly themes set, partial-light themes set, cleanup-failure abort, and bare interactive picker — all verify stale release block removal and no leaking of paths or internal state.
cmuxTests/GhosttyConfigPathResolverTests.swift Adds unit tests for the strip, delete, and leave-alone cases of cmuxAppSupportConfigURLs under nightly fallback; covers the three main branches of the new resolver logic.
cmuxTests/GhosttyConfigTests.swift Adds unit tests for cleared-theme reload color scheme, theme-pair appearance resolution, and resolved-override value extraction; all purely functional with no production behavior concerns.
Resources/Localizable.xcstrings Adds cli.themes.error.cleanupFailed with full translations across all supported locales.
.github/workflows/perf-activation.yml Bumps actions/checkout from v4 to v6.0.2 with a pinned SHA; no logic changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[cmux themes set/clear/picker] --> B{Non-release\nbundle?}
    B -- No --> E[Write/clear nightly config]
    B -- Yes --> C{Nightly config\nalready exists?}
    C -- Yes --> E
    C -- No --> D[removeStaleReleaseManagedThemeOverrideBeforeFallbackIfNeeded]
    D --> D1{Cleanup\nsucceeded?}
    D1 -- No --> F[Abort: cli.themes.error.cleanupFailed]
    D1 -- Yes --> E
    E --> G{themes clear?}
    G -- Yes --> H[clearManagedThemeOverride\nalso calls removeStaleRelease]
    G -- No --> I[writeManagedThemeOverride]

    J[App: loadDefaultConfigFiles] --> K{Non-release\nbundle?}
    K -- No --> M[Load release config as-is]
    K -- Yes --> L[removeStaleRelease best-effort\nstrip or delete release block]
    L --> M
    M --> N[loadResolvedCmuxThemePairOverrideIfNeeded]
    N --> O[ghostty_app_update_config]
    O --> P[prepareTerminalSurfacesForConfigurationReload]
Loading

Reviews (16): Last reviewed commit: "Wire resolved scheme for paired cmux con..." | Re-trigger Greptile

Comment thread Sources/GhosttyTerminalView.swift
Comment thread Sources/GhosttyTerminalView.swift
Comment thread CLI/CMUXCLI+Themes.swift
Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated
Comment on lines +303 to +313
private func removeStaleReleaseManagedThemeOverride(activeBundleIdentifier: String?) throws {
guard let activeBundleIdentifier = (activeBundleIdentifier ?? currentCmuxAppBundleIdentifier())?
.trimmingCharacters(in: .whitespacesAndNewlines),
!activeBundleIdentifier.isEmpty,
activeBundleIdentifier != Self.cmuxThemeOverrideBundleIdentifier else {
return
}

guard let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw CLIError(message: "Unable to resolve Application Support directory")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The error message "Unable to resolve Application Support directory" is thrown as a CLIError directly, which causes it to be re-thrown unchanged in removeStaleReleaseManagedThemeOverrideIfNeeded (via the catch let error as CLIError { throw error } branch), completely bypassing the localized cli.themes.error.cleanupFailed wrapper. The raw string reaches the user un-localized and exposes an internal macOS directory name as an implementation detail.

Suggested change
private func removeStaleReleaseManagedThemeOverride(activeBundleIdentifier: String?) throws {
guard let activeBundleIdentifier = (activeBundleIdentifier ?? currentCmuxAppBundleIdentifier())?
.trimmingCharacters(in: .whitespacesAndNewlines),
!activeBundleIdentifier.isEmpty,
activeBundleIdentifier != Self.cmuxThemeOverrideBundleIdentifier else {
return
}
guard let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw CLIError(message: "Unable to resolve Application Support directory")
}
private func removeStaleReleaseManagedThemeOverride(activeBundleIdentifier: String?) throws {
guard let activeBundleIdentifier = (activeBundleIdentifier ?? currentCmuxAppBundleIdentifier())?
.trimmingCharacters(in: .whitespacesAndNewlines),
!activeBundleIdentifier.isEmpty,
activeBundleIdentifier != Self.cmuxThemeOverrideBundleIdentifier else {
return
}
guard let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw CLIError(message: String(
localized: "cli.themes.error.cleanupFailed",
defaultValue: "Unable to clean stale cmux theme override."
))
}

Rule Used: Flag production user-facing text that is not fully... (source)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by routing the Application Support resolution failure in the stale-theme cleanup path through the existing localized cleanup failure message instead of surfacing the raw internal string.

— Claude Code

Comment thread CLI/CMUXCLI+ThemeSupport.swift Outdated
Comment thread CLI/CMUXCLI+ThemeSupport.swift
Comment thread Sources/CmuxApplicationSupportDirectories.swift Outdated
Comment thread Sources/GhosttyTerminalView.swift
Comment thread Sources/CmuxApplicationSupportDirectories.swift Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 747a3bd. Configure here.

Comment thread Sources/CmuxApplicationSupportDirectories.swift

This branch was successfully deployed

1 active deployment
Preview – cmux — 1c13f5ae Deployed May 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stale theme config from previous nightly builds causes theme changes to not apply

3 participants