Skip to content

Fix bare preferredEditorCommand failing on the GUI PATH - #5868

Open
benegessarit wants to merge 15 commits into
manaflow-ai:mainfrom
benegessarit:fix-preferred-editor-gui-path
Open

benegessarit wants to merge 15 commits into
manaflow-ai:mainfrom
benegessarit:fix-preferred-editor-gui-path

Conversation

@benegessarit

@benegessarit benegessarit commented Jun 11, 2026 •

Copy link
Copy Markdown

Summary

Fixes #5817 — setting Settings → App → "Open Files With" to a bare command like code silently does nothing, and files keep opening in the OS default editor.

Root cause: the editor is spawned via /bin/sh -c from the GUI process, whose PATH is just /usr/bin:/bin:/usr/sbin:/sbin. On my machine code lives at /usr/local/bin/code (Cursor's CLI shim), so the shell exits 127 and the failure gets swallowed by the NSWorkspace.open fallback. The setting looks broken with no trace of why.

The fix:

  • append /usr/local/bin and /opt/homebrew/bin to the spawned editor's PATH when missing (inherited entries keep precedence, no duplicates) — same approach AgentForkSupport / AgentExecutableResolver already take for agent launches
  • log editor launch failures (executable basename + exit status only, path kept private) instead of silently falling back
  • PreferredEditorSettings moved out of cmuxApp.swift into its own file first, as a pure-move commit (one-major-type-per-file; trims the cmuxApp.swift budget entry)

Updated after review: exit status is now observed via process.terminationHandler instead of parking a GCD thread on waitUntilExit (a waiting editor like code -w can run for hours), the logger is a nonisolated file-scope declaration, and the deprecated url.path calls are migrated.

Testing

  • Red/green commit structure per the regression policy: failing tests for the new launchEnvironment(base:) seam first, then the fix.
  • cmux-unit → PreferredEditorSettingsTests: 8/8 passing locally, re-run after each review change.
  • Manual repro of the issue's replica on my machine: env -i PATH="/usr/bin:/bin:/usr/sbin:/sbin" /bin/sh -c "code <file>" exits 127; exits 0 once the two directories are appended.
  • swift_file_length_budget.py, normalize-pbxproj.py, and check-pbxproj.sh all pass. No user-facing strings added or changed (logging only).

Demo Video

Not a visual change — the repro commands above show the before/after.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed (n/a — no user-facing surface changed)
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved (none yet)

Summary by CodeRabbit

  • Bug Fixes
    • Improved launching files with the preferred editor by making common command-line application locations available while preserving the existing environment.
    • When the preferred editor cannot be launched or exits with an error, the failure is logged and the file is opened with the system’s default app.

One-major-type-per-file: relocate the enum unchanged into
Sources/PreferredEditorSettings.swift ahead of a behavior fix for manaflow-ai#5817.
No code changes; pbxproj wiring and file-length budget refreshed.
GUI apps inherit a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin), so a
bare preferredEditorCommand like 'code' (in /usr/local/bin or
/opt/homebrew/bin) exits 127 and silently falls back to the OS default
editor. Introduce a launchEnvironment(base:) seam (currently a
pass-through, so these tests fail) and cover the expected behavior:
standard CLI directories appended, inherited PATH order preserved, no
duplicates, other variables untouched, usable PATH when base has none.
…5817)

Append /usr/local/bin and /opt/homebrew/bin to the spawned editor's
PATH when missing (inherited entries keep precedence), so a bare
command like 'code' resolves the same way it does in a terminal. Log
non-zero editor exits and launch failures before falling back to
NSWorkspace.open, so a misconfigured command is no longer silent.

Verified against the issue's replica: /bin/sh -c "code <file>" exits
127 on the minimal GUI PATH and 0 once the CLI directories are
appended.
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

@benegessarit is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@benegessarit

Copy link
Copy Markdown
Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

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

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Advanced

Run ID: b6442f02-3b8d-48fe-bb14-181e581024a3

📥 Commits

Reviewing files that changed from the base of the PR and between a6dda81 and aad3fac.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorServiceTests.swift

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


📝 Walkthrough

Walkthrough

PreferredEditorService now augments the editor process PATH with common CLI directories while preserving inherited entries and other environment variables. It logs nonzero exits and launch failures before falling back to the system default handler. Tests cover PATH construction.

Changes

Preferred editor launch

Layer / File(s) Summary
Editor environment and failure handling
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift, Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorServiceTests.swift
The editor process receives an augmented PATH that preserves inherited entries and other environment variables. Launch failures and nonzero exits are logged before the system-default fallback. Tests cover PATH precedence, duplicate directories, preserved variables, and empty PATH values.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: blackdurumi

Merge Risk: ⚪ Minimal · up to aad3f

No merge-blocking issue is established in the preferred-editor PATH or fallback changes.

Security Architecture Review

Security architecture risk: 🔵 Low · up to aad3f

The change helps configured editor commands work without changing the app’s privileges or adding a remote entrypoint. It does, however, allow commands to resolve from two additional local directories whose ownership and trust have not been established.

Retained concerns

  • Low · security · inferred: Bare preferred-editor commands can now execute a matching binary from either appended CLI directory. The trust and ownership of those locations are unestablished, so executable provenance at this expanded boundary remains uncertain.
Security review details

Security Blast Radius

  • inferred — The incremental exposure is a local editor subprocess for a configured bare command that resolves in an appended directory. The evidence does not establish remote reachability, a privilege increase, or cross-user exposure.

Security Findings and Attack Paths

  • inferred — If an attacker can place or replace a matching executable in a newly searched directory, opening a file with that bare editor command could execute it. Directory write authority and an attacker-controlled path into this configuration have not been established, so this is a conditional path, not a verified compromise.

Trust Boundaries and Controls

  • observed — The command already crosses into a shell. File arguments are quoted, known terminal editors are rejected before launch, inherited PATH entries stay first, and failed launches fall back; none of these controls establishes the provenance of a newly resolved binary.

Resilience and Maintainability Implications

  • observed — Failure logging records a derived command name and status or launch error before fallback. A successfully launched but unintended executable would not trigger that failure path.

Hardening Proposals

  • proposed — Establish the intended ownership and write-access policy for the appended directories. If they are not trusted for editor execution, constrain bare-command resolution to trusted locations or require an explicit executable path.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error The production diff adds the file-scoped preferredEditorLogger without nonisolated at PreferredEditorService.swift:6. The package uses Swift 6 mode, and the logger is accessed from the `@Sendabl… Declare the logger explicitly nonisolated, for example: private nonisolated let preferredEditorLogger = Logger(subsystem: "com.cmuxterm.app", category: "PreferredEditor"). Keep the existing @MainActor hop for `systemOpener.openWithSyste…
Cmux Swift Logging ❌ Error The changed production file adds a file-scoped Logger as private let preferredEditorLogger = Logger(...). The repository rule explicitly requires nonisolated private let for file-scoped Logger c… Declare the logger as nonisolated private let preferredEditorLogger = Logger(subsystem: "com.cmuxterm.app", category: "PreferredEditor"), then run the relevant Swift checks.
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: fixing bare preferredEditorCommand failures caused by the GUI PATH.
Description check ✅ Passed The description includes the required Summary, Testing, Demo Video, and Checklist sections. It explains the root cause, resulting behavior, implementation, tests, manual verification, and review statu…
Linked Issues check ✅ Passed The PR addresses #5817. PreferredEditorService.launchEnvironment preserves inherited PATH entries and appends /usr/local/bin and /opt/homebrew/bin without duplicates. The spawned shell receives …
Out of Scope Changes check ✅ Passed The whole-PR diff is limited to the preferred editor service and its tests. The changes implement the linked issue's PATH resolution and failure-reporting behavior. No unrelated product behavior or us…
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The authoritative diff changes only PreferredEditorService.swift and its tests under FileOpen. It adds editor PATH construction, logging, fallback handling, and related tests. No Cloud termi…
Cmux Swift Blocking Runtime ✅ Passed The PR does not introduce or expand blocking runtime synchronization. The production diff adds PATH construction, logging, and a Process.terminationHandler callback; it does not add semaphores, bloc…
Cmux Browser Automation Off-Main ✅ Passed PASS: The pull request changes only PreferredEditorService.swift and its tests. The diff adds editor PATH construction, process termination logging, and fallback handling. It adds no browser.* soc…
Cmux Expensive Synchronous Load ✅ Passed The pull request changes only PreferredEditorService and its tests. The production change runs on @MainActor, but it only builds a PATH dictionary, configures Process, and observes editor termin…
Cmux Cache Substitution Correctness ✅ Passed The patch does not replace an authoritative read with a cache. It changes PreferredEditorService process environment construction, termination logging, and adds launchEnvironment tests. The change…
Cmux No Hacky Sleeps ✅ Passed PASS — the pull request changes only two Swift files: PreferredEditorService.swift and its Swift tests. The custom check applies only to production non-Swift changes in TypeScript, JavaScript, shell…
Cmux Algorithmic Complexity ✅ Passed PASS — The diff adds one PATH normalization pass over the inherited PATH and checks two fixed directories. The outer list is explicitly bounded to two entries, so this is linear in PATH length, not a …
Cmux Swift Concurrency ✅ Passed PASS — The authoritative diff adds PATH construction, logging, and tests. It does not add Dispatch queues, Combine state, completion-handler APIs under cmux control, or a new fire-and-forget task. The…
Cmux Swift @Concurrent ✅ Passed PASS: The diff introduces no nonisolated async function and no @concurrent annotation. The new launchEnvironment and publicCommandName helpers are synchronous and nonisolated. `PreferredEdit…
Cmux Swift Package Boundaries ✅ Passed The production change is in the existing SwiftPM target CmuxWorkspaces, at Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift. The target has a library prod…
Cmux Swiftpm Lockfiles ✅ Passed The pull request changes only PreferredEditorService.swift and its tests. The authoritative diff contains no Package.swift, Package.resolved, .gitignore, Xcode project/workspace, or workflow c…
Cmux User-Facing Error Privacy ✅ Passed The changed production path runs from the app's file-opening flows, but the new failure text is emitted only through OSLog. No alert, product CLI output, API response, or other cmux user-facing error …
Cmux Full Internationalization ✅ Passed PASS — The pull request changes only PreferredEditorService.swift and its tests. The added English text is in developer comments, test assertions, and production OSLog failure diagnostics; it is n…
Cmux Swiftui State Layout ✅ Passed The PR changes only PreferredEditorService.swift and its tests. The diff introduces no SwiftUI import, ObservableObject/@Published state, geometry measurement, lazy/list row store references, or…
Cmux Architecture Rethink ✅ Passed PASS. The reviewed diff adds a local PATH transformation, logging, and tests. PreferredEditorService remains the single owner of editor launch and system fallback behavior. The diff adds no mutabl…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The pull request changes only PreferredEditorService process-launch logic and its tests. The diff adds no NSWindow, NSPanel, NSWindowController, SwiftUI Window/WindowGroup, window id…
Cmux Source Artifacts ✅ Passed The PR changes only two intentional source-control paths: PreferredEditorService.swift and its unit test file. The diff adds hand-written Swift production logic and focused tests for PATH handling. …
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS. The only production Swift change is PreferredEditorService.swift. It adds launchEnvironment(base:) as a normal internal production helper, and open(_:) calls it to set the spawned editor p…
Full details: Cmux Swift Actor Isolation

Explanation

The production diff adds the file-scoped preferredEditorLogger without nonisolated at PreferredEditorService.swift:6. The package uses Swift 6 mode, and the logger is accessed from the @Sendable Process.terminationHandler, which runs outside the main actor. Under the repository rule, file-scoped Logger constants must opt out of implicit MainActor isolation. The @MainActor service itself is an allowed UI-bound type, and the test changes are allowed.

Resolution

Declare the logger explicitly nonisolated, for example: private nonisolated let preferredEditorLogger = Logger(subsystem: "com.cmuxterm.app", category: "PreferredEditor"). Keep the existing @MainActor hop for systemOpener.openWithSystemDefault.

Full details: Cmux Swift Logging

Explanation

The changed production file adds a file-scoped Logger as private let preferredEditorLogger = Logger(...). The repository rule explicitly requires nonisolated private let for file-scoped Logger constants in code affected by MainActor-by-default isolation. The new diagnostics otherwise use the permitted unified logging destination and expose only the command basename and exit/error details.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR

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

❤️ Share

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

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 11, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@benegessarit I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,260 of the 240,000 allowed lines of code this month. Reviews resume on 1 July 2026 (in 20 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details and diff
gh pr view 5868 --repo manaflow-ai/cmux --json title,body,files,additions,deletions,changedFiles
echo "---FILES---"
gh pr diff 5868 --repo manaflow-ai/cmux

Length of output: 19801



@benegessarit: Reviewed. The approach is solid — PATH augmentation targets the right root cause, the launchEnvironment(base:) seam is clean, fallback logging is correct, and the test coverage is good. A few observations:


Sources/PreferredEditorSettings.swift

url.path deprecation (macOS 13+ SDK)

URL.path (no arguments) is deprecated in favour of URL.path(percentEncoded: false) on macOS 13+. It's used in two places:

// Line 36
let path = url.path

// Line 50 (logger)
"… for \(url.path, privacy: .private); …"

Consider:

let path: String
if `#available`(macOS 13, *) {
    path = url.path(percentEncoded: false)
} else {
    path = url.path
}

Or, if the deployment target is already ≥ 13, just use url.path(percentEncoded: false) directly.


Redundant .filter { !$0.isEmpty } after split

Swift's split(separator:) already omits empty subsequences by default, so the filter on line 67 is a no-op:

var entries = (base["PATH"] ?? "")
    .split(separator: ":")
    .map { $0.trimmingCharacters(in: .whitespaces) }
    .filter { !$0.isEmpty }   // ← redundant; trim can still produce "" for "  " entries

The trim step can produce an empty string for a " " entry, so the filter is actually useful — but it operates on the trimmed strings, not the split result. The comment is just noting it's not redundant after all; worth a clarifying inline comment or renaming the filter's purpose.


command is shell-interpolated verbatim

process.arguments = ["-c", "\(command) \(shellQuote(path))"]

path is safely single-quoted, but command (read from UserDefaults) is interpolated as-is. This is intentional (multi-token commands like subl --wait need it), but if command contains an unescaped ' or ; it becomes unintentional shell. Worth a brief doc comment noting that command is trusted input (user-configured) and not shell-escaped by design.


cmuxTests/PreferredEditorSettingsTests.swift

Fragile round-trip equality in launchEnvironmentDoesNotDuplicateDirectoriesAlreadyOnPath

`#expect`(environment["PATH"] == loginShellPath)

This holds for well-formed PATH strings but would silently break if, say, an entry has surrounding spaces (split → trim → join isn't identity for "/opt/homebrew/bin : /usr/local/bin:…"). A more robust check:

let entries = (environment["PATH"] ?? "").split(separator: ":").map(String.init)
let expected = loginShellPath.split(separator: ":").map(String.init)
`#expect`(entries == expected)          // order-preserving, no-duplicate check
`#expect`(entries.count == expected.count)  // guards against silent duplication

Minor / non-blocking

  • pbxproj indentation around the new ProjectBuildSettingsTabView.swift / ProjectFilesTabView.swift lines looks like a pre-existing deeper-indent block — not introduced by this PR.
  • The launchEnvironment default parameter (ProcessInfo.processInfo.environment) is evaluated at call time, which is the correct behaviour for capturing the live GUI PATH.

Overall the fix is correct and well-tested. The items above are improvements rather than blockers.

@greptile-apps

greptile-apps Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the silent no-op when a bare command (e.g. code) is set as the preferred editor: GUI apps inherit a minimal PATH that lacks /usr/local/bin and /opt/homebrew/bin, so the shell exits 127 and the failure was swallowed by the OS-default fallback. The fix appends those two directories when absent and replaces the old DispatchQueue.global + waitUntilExit pattern with terminationHandler so long-running editors (e.g. code -w) no longer park a GCD thread.

  • PATH augmentation — launchEnvironment(base:) appends /usr/local/bin and /opt/homebrew/bin when missing, preserving inherited order; covered by four new unit tests including a no-duplication guard.
  • Non-blocking exit observation — terminationHandler is assigned before process.run(), matching the approach recommended in the previous review round; the fallback hop back to @MainActor uses Task { @MainActor in } at an AppKit callback boundary.
  • Failure logging — launch failures and non-zero exits are now routed to unified logging with the command basename .public and the file path .private, replacing the previous silent swallow.

Confidence Score: 5/5

Safe to merge. The core PATH fix and terminationHandler migration are correct, well-tested, and address the root cause without side effects.

The functional change is sound: PATH augmentation is additive and non-destructive, exit observation via terminationHandler is the right non-blocking shape, and failure logging is properly privacy-annotated. The two new static helpers lack nonisolated per project convention, but this does not affect current behavior since every call site today is @mainactor.

PreferredEditorService.swift — the two new static helpers (launchEnvironment, publicCommandName) should be nonisolated.

Important Files Changed

Filename Overview
Packages/CmuxFileOpen/Sources/CmuxFileOpen/PreferredEditorService.swift Adds GUI-safe PATH injection, terminationHandler-based exit observation, unified logging for failures, and migrates deprecated url.path. Two new pure static helpers (launchEnvironment, publicCommandName) are missing nonisolated per project convention.
Packages/CmuxFileOpen/Tests/CmuxFileOpenTests/PreferredEditorServiceTests.swift Adds four well-scoped unit tests for launchEnvironment covering GUI PATH augmentation, no-duplication, variable preservation, and empty-base fallback. All call sites are @MainActor-isolated, so the implicit @mainactor on launchEnvironment compiles correctly here.
.github/swift-file-length-budget.tsv Budget bookkeeping only: re-sorts rows and updates byte counts to reflect the PreferredEditorSettings extraction. No logic changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PreferredEditorService.open URL] --> B{UI-test capture\nconfigured?}
    B -- yes --> C[Write to capture file\nreturn]
    B -- no --> D{editor.resolvedCommand\nnot nil?}
    D -- no --> E[systemOpener.openWithSystemDefault]
    D -- yes --> F[Build Process\n/bin/sh -c command path]
    F --> G[launchEnvironment\nInject /usr/local/bin,\n/opt/homebrew/bin]
    G --> H[process.terminationHandler =\ncheck exit status]
    H --> I[process.run]
    I -- throws --> J[log error\nopenWithSystemDefault]
    I -- ok --> K{terminationHandler fires\nexitStatus == 0?}
    K -- yes --> L[done]
    K -- no --> M[log error\nTask @MainActor\nopenWithSystemDefault]
Loading

Reviews (10): Last reviewed commit: "fix: keep Ghostty submodule at main" | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent failure where a bare preferredEditorCommand (e.g. code) did nothing because GUI apps inherit a minimal PATH that omits /usr/local/bin and /opt/homebrew/bin; it also moves PreferredEditorSettings into its own file and adds logging around failed editor launches.

  • PATH augmentation: launchEnvironment(base:) appends /usr/local/bin and /opt/homebrew/bin when absent, keeping inherited entries at the front. The testable seam and four new behavioral tests match the red/green commit structure described in the PR.
  • Error visibility: Non-zero process exits and launch errors are now logged via OSLog before falling back to NSWorkspace.shared.open, making misconfigured commands diagnosable.
  • File extraction: PreferredEditorSettings is a pure move out of cmuxApp.swift with the fix's new behavior and doc comments added; no other logic changed.

Confidence Score: 4/5

The fix is targeted, well-tested, and the PATH augmentation logic is correct; one declaration in the new file diverges from the repo's established nonisolated logger pattern and should be addressed before merge.

The private static let logger on line 8 of PreferredEditorSettings.swift is missing nonisolated, unlike StartupBreadcrumbLog.swift which uses private nonisolated static let logger. The logger is accessed inside a DispatchQueue.global.async closure, so if the module carries @mainactor by default the access is an actor-isolation error under Swift 6 strict concurrency. Everything else — PATH augmentation, deduplication, shell quoting, logging messages, project wiring, and all four new tests — looks correct and clean.

Sources/PreferredEditorSettings.swift — the logger declaration on line 8.

Important Files Changed

Filename Overview
Sources/PreferredEditorSettings.swift New file containing moved + enhanced PreferredEditorSettings; adds PATH augmentation (launchEnvironment) and error logging, but the static logger is missing nonisolated, creating a potential Swift 6 actor-isolation error when accessed from the background DispatchQueue closure.
Sources/cmuxApp.swift Pure removal of the PreferredEditorSettings enum block; no logic changes, just a clean extraction to the new file.
cmuxTests/PreferredEditorSettingsTests.swift Adds four new tests covering the launchEnvironment seam: GUI PATH augmentation, deduplication, variable preservation, and empty/missing PATH fallback — all correct and thorough.
cmux.xcodeproj/project.pbxproj Wires the new PreferredEditorSettings.swift into both the app and test targets correctly.
.github/swift-file-length-budget.tsv Updates the budget for cmuxApp.swift from 5439 to 5388 lines, reflecting the extraction of PreferredEditorSettings.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PreferredEditorSettings.open url] --> B{UI test capture?}
    B -- yes --> C[Return early]
    B -- no --> D{resolvedCommand?}
    D -- nil --> E[NSWorkspace.shared.open]
    D -- command --> F[Build Process via /bin/sh]
    F --> G[launchEnvironment augments PATH]
    G --> H{process.run throws?}
    H -- throws --> I[logger.error + NSWorkspace fallback]
    H -- ok --> J[DispatchQueue.global.async waitUntilExit]
    J --> K{terminationStatus == 0?}
    K -- yes --> L[Editor opened successfully]
    K -- no --> M[logger.error + NSWorkspace fallback]
Loading

Reviews (2): Last reviewed commit: "Fix bare preferredEditorCommand failing ..." | Re-trigger Greptile

Comment thread Sources/PreferredEditorSettings.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a silent failure where a bare preferredEditorCommand (e.g. code) did nothing on macOS GUI launches because the inherited PATH (/usr/bin:/bin:/usr/sbin:/sbin) lacks /usr/local/bin and /opt/homebrew/bin; the fix augments the spawned editor's environment with those directories and adds Logger-based error reporting on non-zero exits.

  • Sources/PreferredEditorSettings.swift (new file): moves the enum out of the oversized cmuxApp.swift, adds the launchEnvironment(base:) seam that appends the two standard CLI directories when missing, and logs failures via com.cmuxterm.app / PreferredEditor before falling back to NSWorkspace.
  • cmuxTests/PreferredEditorSettingsTests.swift: four new @Test cases exercise the GUI-PATH augmentation, no-duplicate, variable-preservation, and empty-PATH fallback scenarios via the injectable base: parameter.
  • cmuxApp.swift / project.pbxproj / budget tsv: removal of the old enum body and corresponding project wiring + budget update.

Confidence Score: 4/5

The change is safe to merge; it adds a focused PATH augmentation and logging to an isolated utility enum with no effect on any other code path.

The core logic — splitting, deduplicating, and appending the two CLI directories — is straightforward and well-tested. The only comment is a style gap on the Logger declaration shape relative to the project preferred form.

Sources/PreferredEditorSettings.swift — minor logger-declaration style note; all other files are mechanical moves or test additions.

Important Files Changed

Filename Overview
Sources/PreferredEditorSettings.swift New file: moves PreferredEditorSettings from cmuxApp.swift and adds launchEnvironment(), PATH augmentation, and unified-logging fallback; one minor logger-declaration style gap vs. the project preferred shape
Sources/cmuxApp.swift Pure deletion of the old PreferredEditorSettings enum body; no logic changes, budget line updated to 5388
cmuxTests/PreferredEditorSettingsTests.swift Four new @test cases covering GUI-PATH augmentation, no-duplicate, variable preservation, and empty-PATH fallback; all test the injectable launchEnvironment(base:) seam cleanly
cmux.xcodeproj/project.pbxproj Adds PBXBuildFile and PBXFileReference entries for PreferredEditorSettings.swift in both the app and test targets; mechanically correct project wiring
.github/swift-file-length-budget.tsv Budget reduced from 5439 to 5388 for cmuxApp.swift, reflecting the moved PreferredEditorSettings enum; no other changes

Sequence Diagram

sequenceDiagram
    participant Caller
    participant PreferredEditorSettings
    participant Process as /bin/sh (editor)
    participant NSWorkspace

    Caller->>PreferredEditorSettings: open(url)
    alt preferredEditorCommand unset
        PreferredEditorSettings->>NSWorkspace: open(url) [system default]
    else command configured
        PreferredEditorSettings->>PreferredEditorSettings: launchEnvironment() - augment PATH
        PreferredEditorSettings->>Process: run() with augmented PATH
        alt process.run() throws
            PreferredEditorSettings->>PreferredEditorSettings: logger.error(...)
            PreferredEditorSettings->>NSWorkspace: open(url) [fallback]
        else process launched
            Process-->>PreferredEditorSettings: waitUntilExit() on background thread
            alt "terminationStatus != 0"
                PreferredEditorSettings->>PreferredEditorSettings: logger.error(exit status)
                PreferredEditorSettings->>NSWorkspace: open(url) on main [fallback]
            else "terminationStatus == 0"
                Note over PreferredEditorSettings: editor opened file successfully
            end
        end
    end
Loading

Reviews (3): Last reviewed commit: "Fix bare preferredEditorCommand failing ..." | Re-trigger Greptile

Comment thread Sources/PreferredEditorSettings.swift Outdated
…gui-path

# Conflicts:
#	.github/swift-file-length-budget.tsv
- Move the Logger to a nonisolated file-scope declaration per the
  repo's swift-logging shape (StartupBreadcrumbLog precedent), so the
  access from the background exit-status closure stays legal under
  Swift 6 strict concurrency / MainActor-by-default.
- Use url.path(percentEncoded: false) instead of the deprecated
  URL.path (deployment target is macOS 14).
- Document shellQuote and the settings key.
@benegessarit

Copy link
Copy Markdown
Author

Addressed review feedback:

  • Greptile P1 / CodeRabbit actor-isolation check: moved the Logger to a nonisolated file-scope declaration (matching the StartupBreadcrumbLog precedent), so the access from the background exit-status closure stays legal under Swift 6 strict concurrency / MainActor-by-default.
  • CodeRabbit: replaced deprecated url.path with url.path(percentEncoded: false) (deployment target is macOS 14), reusing the captured value in the log line.
  • Added doc comments for shellQuote and the settings key (docstring coverage).
  • Merged origin/main to resolve the swift-file-length-budget.tsv conflict (cmuxApp.swift grew on main; budget entry refreshed to the merged 5397 and the budget script passes).

PreferredEditorSettingsTests: 8/8 passing locally after the changes.

@coderabbitai review
@greptile-apps review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr diff 5868 --repo manaflow-ai/cmux

Length of output: 13386



@benegessarit: The addressed items all look correct — nonisolated file-scope Logger, url.path(percentEncoded: false) for the launch/log path, the launchEnvironment doc comment, and the budget refresh are all in good shape.

One small remaining nit: the test-capture early-return path still uses the deprecated form:

// Sources/PreferredEditorSettings.swift  ~line 27
if CmuxUITestCapture.appendLineIfConfigured(
    envKey: "CMUX_UI_TEST_CAPTURE_OPEN_PATH",
    line: url.path          // ← still deprecated on macOS 13+
) {

Since the deployment target is macOS 14, this can be aligned with the rest of the file:

line: url.path(percentEncoded: false)

On the launchEnvironmentDoesNotDuplicateDirectoriesAlreadyOnPath test — the existing string equality check is fine for well-formed PATH inputs like the one in the test; happy to withdraw that suggestion if you'd prefer to keep it as-is.

Everything else is clean. Once the one-liner above is addressed the PR looks good to go.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread Sources/PreferredEditorSettings.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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/PreferredEditorSettings.swift (1)

22-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent url.path usage: line 24 still uses the deprecated API.

Line 33 was updated to url.path(percentEncoded: false) per review feedback, but line 24 still uses the deprecated url.path. For consistency and to avoid deprecation warnings, both should use the modern API.

Proposed fix
         if CmuxUITestCapture.appendLineIfConfigured(
             envKey: "CMUX_UI_TEST_CAPTURE_OPEN_PATH",
-            line: url.path
+            line: url.path(percentEncoded: false)
         ) {
🤖 Prompt for 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.

In `@Sources/PreferredEditorSettings.swift` around lines 22 - 27, The call to
CmuxUITestCapture.appendLineIfConfigured(envKey:
"CMUX_UI_TEST_CAPTURE_OPEN_PATH", line: url.path) uses the deprecated URL API;
update the argument to use url.path(percentEncoded: false) so it matches the
other occurrence (and avoids deprecation warnings), i.e. locate the
appendLineIfConfigured call and replace url.path with url.path(percentEncoded:
false).
🤖 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.

Outside diff comments:
In `@Sources/PreferredEditorSettings.swift`:
- Around line 22-27: The call to
CmuxUITestCapture.appendLineIfConfigured(envKey:
"CMUX_UI_TEST_CAPTURE_OPEN_PATH", line: url.path) uses the deprecated URL API;
update the argument to use url.path(percentEncoded: false) so it matches the
other occurrence (and avoids deprecation warnings), i.e. locate the
appendLineIfConfigured call and replace url.path with url.path(percentEncoded:
false).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b33cd33-4353-44e1-ae8d-6d8cc9301991

📥 Commits

Reviewing files that changed from the base of the PR and between 34112d5 and a170a56.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (3)
  • Sources/PreferredEditorSettings.swift
  • Sources/cmuxApp.swift
  • cmux.xcodeproj/project.pbxproj

waitUntilExit on a .userInitiated global-queue thread blocks that
thread for the editor's entire lifetime (a waiting editor like
'code -w' can run for hours). Process.terminationHandler is the
non-blocking signal for exactly this; same fallback behavior. Also
migrate the remaining deprecated url.path call in the UI-test capture
branch to url.path(percentEncoded: false).
@benegessarit

Copy link
Copy Markdown
Author

@greptile-apps review

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/PreferredEditorSettings.swift (1)

34-49: ⚠️ Potential issue | 🟠 Major

Keep a strong reference to the launched Process so the failure fallback actually runs.

In PreferredEditorSettings.open(_:) (Sources/PreferredEditorSettings.swift, lines 34-49), the fallback path (preferredEditorLogger.error + NSWorkspace.shared.open(url)) is triggered only from process.terminationHandler on non-zero exit (e.g. /bin/sh exit 127). Right now the Process is a local variable and nothing retains it after process.run(), so the handler may never fire, reintroducing silent preferred-editor failures.

Keep in-flight editor processes alive until the termination handler runs, and remove them from the store when done (also remove them in the catch if process.run() throws).

Suggested shape
 enum PreferredEditorSettings {
+    private static let inFlightLock = NSLock()
+    private static var inFlightProcesses: [ObjectIdentifier: Process] = [:]
+
     static func open(_ url: URL) {
         ...
         let process = Process()
+        let token = ObjectIdentifier(process)
+        inFlightLock.lock()
+        inFlightProcesses[token] = process
+        inFlightLock.unlock()
+
         process.terminationHandler = { process in
+            inFlightLock.lock()
+            inFlightProcesses.removeValue(forKey: token)
+            inFlightLock.unlock()
             guard process.terminationStatus != 0 else { return }
             preferredEditorLogger.error(
                 "preferred editor command \(command, privacy: .public) exited \(process.terminationStatus, privacy: .public) for \(path, privacy: .private); falling back to the OS default handler"
             )
             Task { `@MainActor` in NSWorkspace.shared.open(url) }
         }
         do {
             try process.run()
         } catch {
+            inFlightLock.lock()
+            inFlightProcesses.removeValue(forKey: token)
+            inFlightLock.unlock()
             preferredEditorLogger.error(
                 "failed to launch preferred editor command \(command, privacy: .public): \(error.localizedDescription, privacy: .public); falling back to the OS default handler"
             )
             NSWorkspace.shared.open(url)
         }
     }
 }
🤖 Prompt for 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.

In `@Sources/PreferredEditorSettings.swift` around lines 34 - 49,
PreferredEditorSettings.open(_:) creates a local Process whose
terminationHandler may never fire because nothing retains it; keep a strong
reference to the launched Process (e.g., an in-flight Set/Dictionary property on
PreferredEditorSettings keyed by URL or PID) so the terminationHandler runs, add
the Process to that store before calling process.run(), remove it from the store
inside the terminationHandler after handling the non-zero exit and opening the
URL, and also remove it in the catch block if process.run() throws; update
references to the local process variable accordingly so ownership and cleanup
are explicit.
🤖 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 `@Sources/PreferredEditorSettings.swift`:
- Around line 45-54: The logs in PreferredEditorSettings.swift currently emit
the raw preferred-editor command as .public (see preferredEditorLogger and the
local variable command); change both logging sites (the termination-status error
and the catch block) to avoid logging the full command string — either redact it
or extract and log only non-sensitive metadata such as the command's executable
basename (FileManager or URL.lastPathComponent) and the
process.terminationStatus or error.localizedDescription, and mark those fields
.public while keeping any path/argument details .private or omitted so no
free-form shell text is persisted.

---

Outside diff comments:
In `@Sources/PreferredEditorSettings.swift`:
- Around line 34-49: PreferredEditorSettings.open(_:) creates a local Process
whose terminationHandler may never fire because nothing retains it; keep a
strong reference to the launched Process (e.g., an in-flight Set/Dictionary
property on PreferredEditorSettings keyed by URL or PID) so the
terminationHandler runs, add the Process to that store before calling
process.run(), remove it from the store inside the terminationHandler after
handling the non-zero exit and opening the URL, and also remove it in the catch
block if process.run() throws; update references to the local process variable
accordingly so ownership and cleanup are explicit.
🪄 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: be48d685-ac30-47ad-9286-78395eaeb93d

📥 Commits

Reviewing files that changed from the base of the PR and between a170a56 and d83b8fc.

📒 Files selected for processing (1)
  • Sources/PreferredEditorSettings.swift

Comment thread Sources/PreferredEditorSettings.swift Outdated
preferredEditorCommand is free-form shell text that can carry
sensitive paths or arguments; persisting it .public to Console is a
leak. Log the first token's basename instead (enough to identify
which editor failed), keep the exit status public and the file path
private.
@benegessarit

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

hhsw2015 pushed a commit to hhsw2015/cmux that referenced this pull request Jun 11, 2026
@vercel

vercel Bot commented Jun 11, 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 Jun 14, 2026 9:26pm

teamleaderleo and others added 2 commits September 25, 2026 07:46
main's history was rewritten after this branch was cut, so the branch
shares no merge base with it. Take main's tree wholesale here; the
editor PATH fix is ported onto the current PreferredEditorService in
the next commit.
…5817)

Port benegessarit's fix onto the current PreferredEditorService in
CmuxWorkspaces: spawn the editor with /usr/local/bin and
/opt/homebrew/bin appended to PATH when missing (inherited entries keep
precedence), and log launch failures and nonzero exits with only the
executable basename before falling back to the OS default handler.

Co-authored-by: benegessarit <benegessarit@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document v2.2 and I hereby sign the CLA


1 out of 2 committers have signed the CLA.
✅ teamleaderleo
❌ @benegessarit
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Main's history was rewritten after this branch was cut, so I merged main in and ported your PATH fix, logging and tests onto PreferredEditorService's new home in CmuxWorkspaces so this can land. Thank you for tracking this one down!!

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Thank you for this! It is reviewed, up to date with main and CI is running. The one thing left before we can merge is the CLA: just post a comment with exactly this line and it will go green :)

I have read the CLA Document v2.2 and I hereby sign the CLA

@teamleaderleo teamleaderleo added S3: minor Wrong behavior with a workaround area: settings Settings UI and the config file labels Sep 30, 2026

This branch was successfully deployed

1 active (outdated) deployment
Preview – cmux — 54ca99e2 Deployed Jun 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: settings Settings UI and the config file S3: minor Wrong behavior with a workaround

Projects

None yet

Development

Successfully merging this pull request may close these issues.

preferredEditorCommand with a bare command name (e.g. code) silently fails and falls back to the OS default

3 participants