Skip to content

Add configurable cmux.json workspace and tab bar actions - #3084

Merged
lawrencecchen merged 29 commits into
mainfrom
feat-custom-new-command
Apr 23, 2026
Merged

lawrencecchen merged 29 commits into
mainfrom
feat-custom-new-command

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add a cmux.json action registry and ui placements for the new-workspace action and surface tab-bar buttons.
  • Support built-in buttons, arbitrary bash commands, and agent actions for Codex and Claude Code.
  • Support SF Symbols, emoji, and relative SVG/PDF/PNG/JPEG/GIF/TIFF/BMP/HEIC/HEIF/WebP/AVIF/ICO/ICNS icon paths for action buttons.
  • Keep legacy newWorkspaceCommand and surfaceTabBarButtons compatibility, including [] to disable default tab-bar buttons.
  • Document the nightly-only action button schema and icon formats in custom commands docs.

Related

Testing

  • swift test in vendor/bonsplit, verified split button configuration, icon handling, duplicate action handling, and tab bar behavior.
  • ./scripts/test-unit.sh -derivedDataPath /tmp/cmux-btncfg-unit test -only-testing:cmuxTests/CmuxConfigDecodingTests, verified cmux config decoding and command/button resolution.
  • ./scripts/test-unit.sh -derivedDataPath /tmp/cmux-feat-custom-new-command-unit test -only-testing:cmuxTests/CmuxConfigDecodingTests, re-ran the same config tests after review fixes.
  • bunx tsc --noEmit in web, verified the docs page still typechecks.
  • git diff --check and git -C vendor/bonsplit diff --check, verified whitespace cleanliness.
  • ./scripts/reload.sh --tag feat-custom-new-command, verified the tagged macOS dogfood build compiles after the latest app/runtime changes.

Demo Video

  • Not recorded. Manual verification is through the tagged macOS dogfood build and the Vercel docs preview above.

Review Trigger

  • @coderabbitai review requested after the latest push.
  • Greptile and Codex review feedback has been addressed inline.

Checklist

  • Tests pass locally for the touched config path.
  • Docs mention this is a nightly feature.
  • Tab-bar examples create a new terminal in the current pane before running agent commands.
  • Bonsplit submodule PR is linked.

Summary by CodeRabbit

  • New Features

    • Centralized “New Workspace” routing with configurable new-workspace actions and surface tab bar buttons that run workspace or terminal actions.
    • Command palette and shortcuts now surface configured actions and palette entries.
  • Tests

    • Expanded decoding and integration tests for actions, buttons, and workspace-command resolution.
  • Documentation

    • Updated docs/examples for actions, UI buttons, and new-workspace configuration.
  • Chores

    • New action-trust model replacing trusted-directories handling; updated localization and vendored dependency.

@vercel

vercel Bot commented Apr 22, 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 Apr 23, 2026 10:19am

@coderabbitai

coderabbitai Bot commented Apr 22, 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

Centralizes new-workspace (Cmd+N) routing in AppDelegate, threads an optional per-window CmuxConfigStore through window registration, adds an actions/ui model and surface tab-bar button configuration, applies buttons to workspaces, and implements execution/trust flows and shortcut handling changes across UI, executor, tests, and docs.

Changes

Cohort / File(s) Summary
App routing & window context
Sources/AppDelegate.swift
Added performNewWorkspaceAction(...), extended registerMainWindow(..., cmuxConfigStore:), updated main-window context plumbing and Cmd+N/new-workspace routing, and adjusted shortcut handling to consult configured Cmux actions.
Config model & store
Sources/CmuxConfig.swift
Added actions, ui, newWorkspaceCommand, surfaceTabBarButtons; custom decoding/validation; new types (actions/buttons/icons/agents/resolved types); CmuxConfigStore now publishes loaded actions, resolved workspace command/action, surface buttons, and configuration issues; load/resolution logic expanded.
Execution & trust
Sources/CmuxConfigExecutor.swift, Sources/CmuxActionTrust.swift
execute(...) overloads added/updated to return Bool and accept action/context; added preparedShellInput(...), workspace-trust descriptor/confirm flow, and isTrustedSurfaceButton; new CmuxActionTrust persistent fingerprint trust model.
Workspace & TabManager surface behavior
Sources/Workspace.swift, Sources/TabManager.swift
Added APIs to apply surface tab-bar button configs across workspaces, new-surface/initialInput support, Bonsplit delegate hook for custom actions, and execution/send-input behavior for terminal vs workspace commands.
UI wiring & command palette
Sources/ContentView.swift, Sources/cmuxApp.swift, Sources/Update/UpdateTitlebarAccessory.swift
Replaced direct tab/workspace creation with performNewWorkspaceAction(...); command-palette now sources configured actions from CmuxConfigStore and routes certain built-ins through configured-action execution.
Keyboard shortcuts & settings
Sources/KeyboardShortcutSettings.swift, Sources/KeyboardShortcutSettingsFileStore.swift
Added Hashable for shortcut types, parsing/serialization from/to config strings, chord support in parsing; file-store removed trusted-directories handling and updated parsing/serialization usage.
Tests & docs
cmuxTests/CmuxConfigTests.swift, web/app/.../docs/custom-commands/page.tsx, web/messages/*
Expanded Cmsux config decoding/resolution tests (actions, newWorkspaceCommand, surface buttons, icons, SVG trust); updated docs and localized strings to document actions/ui and new config paths/schema.
Project & resources
GhosttyTabs.xcodeproj/project.pbxproj, Resources/Localizable.xcstrings
Swapped source file in Xcode project to CmuxActionTrust.swift; updated localization keys/strings for the confirmation dialog and new button titles.
Removed: directory trust
Sources/CmuxDirectoryTrust.swift, web/data/cmux-settings.schema.json
Deleted CmuxDirectoryTrust and removed customCommands.trustedDirectories from schema; corresponding settings/managed handling removed from settings store.
Vendor
vendor/bonsplit
Updated vendored bonsplit submodule commit reference.
Minor UI closures
Sources/ContentView.swift, Sources/Update/UpdateTitlebarAccessory.swift
Titlebar/accessory new-workspace handlers now call performNewWorkspaceAction(...) with distinct debugSource values.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as UI (Menu/Titlebar/Shortcut)
    participant AD as AppDelegate
    participant Store as CmuxConfigStore
    participant TabMgr as TabManager
    participant Exec as CmuxConfigExecutor

    User->>UI: trigger New Workspace (Cmd+N / menu / titlebar)
    UI->>AD: performNewWorkspaceAction(tabManager?, event?, debugSource)
    AD->>Store: resolvedNewWorkspaceCommand()
    alt Store resolves workspace-targeting action
        AD->>Exec: execute(action, commands, commandSourcePaths,...)
        Exec->>Exec: confirm/trust -> run workspace action (open/replace/close as defined)
    else Store resolves terminal command or no action
        AD->>TabMgr: create workspace or call preferred tab manager
        TabMgr->>Workspace: new workspace / new surface (initialInput if needed)
        Workspace->>Exec: execute terminal command or send prepared shell input
    end
    alt No available preferred tab manager
        AD->>AD: open new main window (register with cmuxConfigStore if available)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nudged a key and opened space,
Buttons lined the tab bar, found their place.
Actions bloom from files and spring,
Terminals hum and workspaces sing.
Hop—new windows, one more bring.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add configurable cmux.json workspace and tab bar actions' clearly and directly describes the main change: adding configuration support for new workspace actions and tab bar actions via cmux.json. It is concise, specific, and matches the primary objective of the PR.
Description check ✅ Passed The description follows the template structure with Summary, Testing, Demo Video, Review Trigger, and Checklist sections all present. Summary explains what changed and why; Testing details how changes were verified; and the checklist confirms required items completed (tests pass, docs updated, bot reviews requested).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-custom-new-command

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 Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a root-level newWorkspaceCommand field in cmux.json that lets users designate a named command as the action for all "New Workspace" entry points (Cmd+N, File menu, command palette, titlebar plus buttons). All call sites are unified through a new performNewWorkspaceAction helper on AppDelegate, which resolves the appropriate window context and falls back gracefully to the default workspace-creation path when no command is configured.

Confidence Score: 5/5

Safe to merge; all findings are P2 style/test suggestions with no blocking correctness issues.

The routing logic is well-structured with correct fallback chains, the config validation is sound (blank-value guard at decode time), and all call sites have been updated consistently. The only gaps are a missing whitespace-trim test and no test coverage for the resolvedNewWorkspaceCommand() guard paths — neither affects runtime correctness.

No files require special attention, though Sources/CmuxConfig.swift would benefit from surfacing misconfiguration to the user rather than silently falling back.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Centralizes all new-workspace entry points through performNewWorkspaceAction; adds cmuxConfigStore to MainWindowContext and a private helper that executes the configured command. Logic is sound; fallback chain covers edge cases.
Sources/CmuxConfig.swift Adds newWorkspaceCommand parsing with blank-value guard and resolvedNewWorkspaceCommand() lookup; silent fallback when command name is unmatched or references a non-workspace command may confuse users.
Sources/ContentView.swift Routes fullscreen titlebar plus-button and command-palette "New Workspace" through performNewWorkspaceAction; passes tabManager so the correct window context is selected.
Sources/Update/UpdateTitlebarAccessory.swift Two titlebar plus-button callbacks updated to use performNewWorkspaceAction; no context issues since they don't pass a tabManager and will resolve the preferred window.
Sources/cmuxApp.swift File > New Workspace menu item now routes through performNewWorkspaceAction; fallback else branch (AppDelegate nil) changed from addTab() to addWorkspace() which is the correct semantic.
cmuxTests/CmuxConfigTests.swift Happy-path decode test and blank-value throw test added; whitespace-trimming and resolvedNewWorkspaceCommand() guard paths are not covered.
web/app/[locale]/docs/custom-commands/page.tsx Adds newWorkspaceCommand to both the schema snippet and the full example in the docs; no logic concerns.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Cmd+N / File Menu / Command Palette / Titlebar +] --> B[performNewWorkspaceAction]
    B --> C{mainWindowContexts empty\nAND preferredContext nil?}
    C -- Yes --> D[openNewMainWindow]
    C -- No --> E[Resolve context:\npreferredContext ?? preferredMainWindowContext]
    E --> F{executeConfiguredNewWorkspace\nCommandIfAvailable?}
    F -- Yes: configured command found --> G[CmuxConfigExecutor.execute\nconfigured command]
    F -- No --> H{preferredTabManager\nAND preferredContext non-nil?}
    H -- Yes --> I[preferredTabManager.addWorkspace]
    H -- No --> J[addWorkspaceInPreferredMainWindow]
    J --> K{returns nil?}
    K -- Yes --> D
    K -- No --> L[Done]
Loading

Reviews (1): Last reviewed commit: "Add configurable new workspace command" | Re-trigger Greptile

Comment thread Sources/CmuxConfig.swift
Comment thread cmuxTests/CmuxConfigTests.swift
Comment thread cmuxTests/CmuxConfigTests.swift

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccf8aed0b9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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

🧹 Nitpick comments (2)
Sources/CmuxConfig.swift (2)

19-36: Hard-throwing on blank newWorkspaceCommand drops the entire config.

Because parseConfig catches decoding errors and returns nil (only NSLog), a user who writes "newWorkspaceCommand": "" (or whitespace) will silently lose every command from that file, not just the misconfigured field. Other blank validations in this file (e.g., blank command) already behave this way, so this is consistent — but given this is a brand-new, top-level, optional field, it may be friendlier to treat blank as nil (equivalent to omission) rather than invalidate the whole config. Up to you; current behavior is defensible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 19 - 36, The initializer init(from
decoder:) currently throws DecodingError.dataCorrupted when
CodingKeys.newWorkspaceCommand is present but blank; change this so that if the
decoded rawNewWorkspaceCommand trims to an empty string you set
newWorkspaceCommand = nil (treat blank as omission) instead of throwing, while
preserving the existing behavior of assigning trimmed to newWorkspaceCommand
when non-empty and still decoding commands via commands = try
container.decode([CmuxCommandDefinition].self, forKey: .commands).

431-442: Resolution and workspace-scope validation look good.

Returning nil on mismatch/misconfiguration lets the caller fall through to the default new-workspace path (per executeConfiguredNewWorkspaceCommandIfAvailable in AppDelegate.swift), which is the right behavior. NSLog matches the existing parse-error logging style in this file.

One minor thought: the mismatch log fires every time the user triggers Cmd+N / File › New Workspace while the config is misconfigured, which can flood Console. Consider logging once per configRevision change if that becomes noisy in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 431 - 442, The current
resolvedNewWorkspaceCommand() logs a mismatch every time Cmd+N is pressed when
the config is invalid, which can flood Console; add a stored property on
CmuxConfig (e.g., lastLoggedInvalidNewWorkspaceConfigRevision) and in
resolvedNewWorkspaceCommand() check the current configRevision against that
property before calling NSLog, only logging and updating
lastLoggedInvalidNewWorkspaceConfigRevision when the revision changed; keep the
existing nil return behavior and ensure the property is updated for both "no
matching command" and "not a workspace command" cases (add thread-safety if
CmuxConfig is accessed from multiple threads).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 6599-6624: The code currently skips honoring an explicitly passed
preferredTabManager when mainWindowContext(for:) returns nil; change the logic
so that after resolving context and attempting
executeConfiguredNewWorkspaceCommandIfAvailable(in:debugSource:), if
preferredTabManager is non-nil you call preferredTabManager.addWorkspace() and
return true regardless of preferredContext being nil (i.e., remove the
preferredContext != nil check), ensuring preferredTabManager is used as the
fallback target; refer to preferredTabManager, mainWindowContext(for:),
preferredMainWindowContextForWorkspaceCreation(event:debugSource:),
preferredContext, and addWorkspace() when making this change.
- Around line 6646-6670: The method
executeConfiguredNewWorkspaceCommandIfAvailable currently treats any call to
CmuxConfigExecutor.execute(...) as a successful execution; change this so we
only return true when the command actually ran: update
CmuxConfigExecutor.execute to return a Bool (or optional workspace ID)
indicating success, then in executeConfiguredNewWorkspaceCommandIfAvailable call
the new return value and only return true when it indicates success;
alternatively, if you prefer not to change the executor signature, pre-validate
here that the configured command can run (e.g., check for a focused terminal or
that the command is a workspace-creating command) before calling execute and
only return true when that validation passes. Ensure you update references to
resolvedNewWorkspaceCommand(), tabManager (e.g.,
selectedWorkspace/currentDirectory or focused terminal check) and the call site
to use the new Bool/ID result.
- Around line 4840-4853: The re-registration path unconditionally overwrites a
previously registered cmuxConfigStore because the parameter defaults to nil;
update the branches that find an existing MainWindowContext (the first if-let
existing = mainWindowContexts[key] and the else-if using
mainWindowContexts.values.first where $0.windowId == windowId) to only replace
existing.cmuxConfigStore when the incoming cmuxConfigStore is non-nil (i.e.,
keep the current existing.cmuxConfigStore if the parameter is nil), leaving
other assignments (existing.window, reindexMainWindowContextIfNeeded) as-is.

---

Nitpick comments:
In `@Sources/CmuxConfig.swift`:
- Around line 19-36: The initializer init(from decoder:) currently throws
DecodingError.dataCorrupted when CodingKeys.newWorkspaceCommand is present but
blank; change this so that if the decoded rawNewWorkspaceCommand trims to an
empty string you set newWorkspaceCommand = nil (treat blank as omission) instead
of throwing, while preserving the existing behavior of assigning trimmed to
newWorkspaceCommand when non-empty and still decoding commands via commands =
try container.decode([CmuxCommandDefinition].self, forKey: .commands).
- Around line 431-442: The current resolvedNewWorkspaceCommand() logs a mismatch
every time Cmd+N is pressed when the config is invalid, which can flood Console;
add a stored property on CmuxConfig (e.g.,
lastLoggedInvalidNewWorkspaceConfigRevision) and in
resolvedNewWorkspaceCommand() check the current configRevision against that
property before calling NSLog, only logging and updating
lastLoggedInvalidNewWorkspaceConfigRevision when the revision changed; keep the
existing nil return behavior and ensure the property is updated for both "no
matching command" and "not a workspace command" cases (add thread-safety if
CmuxConfig is accessed from multiple threads).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82f4b321-551c-4798-9e8b-cae490670c74

📥 Commits

Reviewing files that changed from the base of the PR and between e39f5e0 and ccf8aed.

📒 Files selected for processing (7)
  • Sources/AppDelegate.swift
  • Sources/CmuxConfig.swift
  • Sources/ContentView.swift
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Sources/cmuxApp.swift
  • cmuxTests/CmuxConfigTests.swift
  • web/app/[locale]/docs/custom-commands/page.tsx

Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift Outdated

@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.

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/AppDelegate.swift">

<violation number="1" location="Sources/AppDelegate.swift:6622">
P1: The `preferredContext != nil` guard causes the explicitly provided `preferredTabManager` to be silently ignored when `mainWindowContext(for:)` returns `nil`. This can happen during close/reparent races where the context briefly has `window == nil`, causing the workspace to be created in a different window (or a new window) instead of the one the caller intended. Remove the `preferredContext != nil` check so the explicit tab manager is always honored as a fallback.</violation>

<violation number="2" location="Sources/AppDelegate.swift:6663">
P2: `executeConfiguredNewWorkspaceCommandIfAvailable` reports success even when the configured shell command cannot run (no focused terminal), which swallows New Workspace without creating anything.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2e18e044c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/AppDelegate.swift

@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: 2

🧹 Nitpick comments (1)
Sources/CmuxConfig.swift (1)

403-408: $tabs sink will re-apply buttons on every tabs mutation (and once on subscribe).

@Published.projectedValue emits the current value to new subscribers, so this sink fires immediately after wireDirectoryTracking — duplicating the applySurfaceTabBarButtonsToCurrentManager() call that already happens at the end of loadAll(). It then fires again on every add/remove/reorder, re-applying the same button configuration to every workspace each time. Functionally correct, but noisy.

Consider dropping the initial emission and/or de-duplicating, e.g.:

         tabManager.$tabs
+            .dropFirst()
+            .map { tabs in tabs.map(\.id) }
+            .removeDuplicates()
             .receive(on: DispatchQueue.main)
-            .sink { [weak self] _ in
+            .sink { [weak self] _ in
                 self?.applySurfaceTabBarButtonsToCurrentManager()
             }
             .store(in: &cancellables)

If TabManager.applySurfaceTabBarButtons(_:) only needs to run when the tab set changes (not when an existing workspace mutates), keying on tabs.map(\.id) is cheaper than hashing the whole workspace array.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 403 - 408, The sink on
tabManager.$tabs currently receives the initial value and every mutation,
causing duplicate/noisy re-applications of
applySurfaceTabBarButtonsToCurrentManager(); change the publisher chain to
ignore the initial emission and de-duplicate only when the tab identities change
(e.g. use tabManager.$tabs.map { $0.map(\.id) }.removeDuplicates() or at minimum
.dropFirst()) so applySurfaceTabBarButtonsToCurrentManager() is invoked only
when the set/order of tabs changes; update the existing sink (the closure that
calls applySurfaceTabBarButtonsToCurrentManager()) to subscribe to that
transformed publisher instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/CmuxConfig.swift`:
- Around line 505-516: resolvedNewWorkspaceCommand() currently calls NSLog every
time an invalid newWorkspaceCommandName is hit; add a small cache on the
CmuxConfig instance (e.g., a property lastLoggedInvalidName: (String, UInt64)?)
keyed by the invalid name and the current configRevision to dedupe logs so the
same warning is emitted only once per reload. In practice: before each NSLog
check lastLoggedInvalidName against (newWorkspaceCommandName, configRevision)
and skip logging if it matches; if you do log, set lastLoggedInvalidName =
(newWorkspaceCommandName, configRevision). Update the logic in
resolvedNewWorkspaceCommand() (referencing newWorkspaceCommandName,
loadedCommands, commandSourcePaths, and configRevision) to consult and update
this cache so repeated Cmd+N invocations don’t spam the log.
- Around line 26-60: The current CmuxConfig.init(from decoder:) throws
DecodingError for a blank newWorkspaceCommand and for duplicate
surfaceTabBarButtons, which causes JSONDecoder().decode to fail and the whole
file to be discarded; change the decoder to be lenient: in init(from decoder:)
for newWorkspaceCommand, decode and trim but if empty set newWorkspaceCommand =
nil and NSLog a clear warning instead of throwing; for surfaceTabBarButtons,
decode the array, remove duplicates (preserve first occurrence) into
surfaceTabBarButtons and NSLog which duplicates were dropped instead of
throwing; keep commands decoding unchanged. Update the logic in
CmuxConfig.init(from:) (referencing newWorkspaceCommand and
surfaceTabBarButtons) so bad top-level values are normalized and logged rather
than causing a thrown DecodingError that drops the whole config.

---

Nitpick comments:
In `@Sources/CmuxConfig.swift`:
- Around line 403-408: The sink on tabManager.$tabs currently receives the
initial value and every mutation, causing duplicate/noisy re-applications of
applySurfaceTabBarButtonsToCurrentManager(); change the publisher chain to
ignore the initial emission and de-duplicate only when the tab identities change
(e.g. use tabManager.$tabs.map { $0.map(\.id) }.removeDuplicates() or at minimum
.dropFirst()) so applySurfaceTabBarButtonsToCurrentManager() is invoked only
when the set/order of tabs changes; update the existing sink (the closure that
calls applySurfaceTabBarButtonsToCurrentManager()) to subscribe to that
transformed publisher instead.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b85f1ea-242a-46bf-ad1e-152a069e2165

📥 Commits

Reviewing files that changed from the base of the PR and between cc254a8 and 02bcff1.

📒 Files selected for processing (6)
  • Sources/CmuxConfig.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • cmuxTests/CmuxConfigTests.swift
  • vendor/bonsplit
  • web/app/[locale]/docs/custom-commands/page.tsx
✅ Files skipped from review due to trivial changes (2)
  • web/app/[locale]/docs/custom-commands/page.tsx
  • vendor/bonsplit
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmuxTests/CmuxConfigTests.swift
  • Sources/Workspace.swift

Comment thread Sources/CmuxConfig.swift
Comment thread Sources/CmuxConfig.swift

@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.

♻️ Duplicate comments (2)
Sources/CmuxConfig.swift (2)

505-515: ⚠️ Potential issue | 🟡 Minor

Deduplicate invalid newWorkspaceCommand warnings per config revision.

A persistent bad setting still logs on every Cmd+N/menu/palette/titlebar invocation. Cache the last invalid (name, reason, configRevision) so the warning emits once per reload.

🧹 Proposed log dedupe
 final class CmuxConfigStore: ObservableObject {
     `@Published` private(set) var loadedCommands: [CmuxCommandDefinition] = []
     `@Published` private(set) var newWorkspaceCommandName: String?
     `@Published` private(set) var surfaceTabBarButtons: [CmuxSurfaceTabBarButton] = CmuxSurfaceTabBarButton.defaults
     `@Published` private(set) var configRevision: UInt64 = 0
 
     /// Which config file each command came from, keyed by command id.
     private(set) var commandSourcePaths: [String: String] = [:]
+    private var lastLoggedInvalidNewWorkspaceCommand: (name: String, revision: UInt64, reason: String)?
 
     private(set) var localConfigPath: String?
     private weak var tabManager: TabManager?
     func resolvedNewWorkspaceCommand() -> CmuxResolvedCommand? {
         guard let commandName = newWorkspaceCommandName else { return nil }
         guard let command = loadedCommands.first(where: { $0.name == commandName }) else {
-            NSLog("[CmuxConfig] newWorkspaceCommand '%@' does not match any loaded command", commandName)
+            logInvalidNewWorkspaceCommand(commandName, reason: "does not match any loaded command")
             return nil
         }
         guard command.workspace != nil else {
-            NSLog("[CmuxConfig] newWorkspaceCommand '%@' must reference a workspace command", commandName)
+            logInvalidNewWorkspaceCommand(commandName, reason: "must reference a workspace command")
             return nil
         }
         return CmuxResolvedCommand(command: command, sourcePath: commandSourcePaths[command.id])
     }
+
+    private func logInvalidNewWorkspaceCommand(_ commandName: String, reason: String) {
+        let last = lastLoggedInvalidNewWorkspaceCommand
+        guard last?.name != commandName ||
+              last?.revision != configRevision ||
+              last?.reason != reason else {
+            return
+        }
+        lastLoggedInvalidNewWorkspaceCommand = (commandName, configRevision, reason)
+        NSLog("[CmuxConfig] newWorkspaceCommand '%@' %@", commandName, reason)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 505 - 515, The
resolvedNewWorkspaceCommand() currently logs the same invalid
newWorkspaceCommand every time; add a small dedupe cache (e.g., a stored
property like lastInvalidNewWorkspaceCommand tuple containing name, reason, and
configRevision) and use it inside resolvedNewWorkspaceCommand() to only call
NSLog when the (newWorkspaceCommandName, reason, configRevision) differs from
the cached tuple; update the cache with the new tuple whenever you return nil
for a different reason or revision. Reference resolvedNewWorkspaceCommand(),
newWorkspaceCommandName, loadedCommands, commandSourcePaths, and the
configRevision value so the logic detects three distinct reasons (not found, not
a workspace command, or nil) and suppresses repeated logs until the
configRevision or name/reason changes.

26-58: ⚠️ Potential issue | 🟠 Major

Keep optional top-level config validation non-fatal.

This still lets a blank newWorkspaceCommand or duplicate surfaceTabBarButtons throw during top-level decode; Line 527 then fails the whole file, dropping otherwise valid commands. Prefer normalizing/logging just the bad top-level value and preserving the rest of the config.

♻️ Proposed normalization
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         if let rawNewWorkspaceCommand = try container.decodeIfPresent(String.self, forKey: .newWorkspaceCommand) {
             let trimmed = rawNewWorkspaceCommand.trimmingCharacters(in: .whitespacesAndNewlines)
             if trimmed.isEmpty {
-                throw DecodingError.dataCorrupted(
-                    DecodingError.Context(
-                        codingPath: decoder.codingPath + [CodingKeys.newWorkspaceCommand],
-                        debugDescription: "newWorkspaceCommand must not be blank"
-                    )
-                )
+                NSLog("[CmuxConfig] ignoring blank newWorkspaceCommand")
+                newWorkspaceCommand = nil
+            } else {
+                newWorkspaceCommand = trimmed
             }
-            newWorkspaceCommand = trimmed
         } else {
             newWorkspaceCommand = nil
         }
         if let buttons = try container.decodeIfPresent([CmuxSurfaceTabBarButton].self, forKey: .surfaceTabBarButtons) {
             var seen = Set<CmuxSurfaceTabBarButton>()
+            var normalizedButtons: [CmuxSurfaceTabBarButton] = []
+            var duplicateButtons: [String] = []
             for button in buttons {
                 if !seen.insert(button).inserted {
-                    throw DecodingError.dataCorrupted(
-                        DecodingError.Context(
-                            codingPath: decoder.codingPath + [CodingKeys.surfaceTabBarButtons],
-                            debugDescription: "surfaceTabBarButtons must not contain duplicate values"
-                        )
-                    )
+                    duplicateButtons.append(button.rawValue)
+                } else {
+                    normalizedButtons.append(button)
                 }
             }
-            surfaceTabBarButtons = buttons
+            if !duplicateButtons.isEmpty {
+                NSLog(
+                    "[CmuxConfig] ignoring duplicate surfaceTabBarButtons values: %@",
+                    duplicateButtons.joined(separator: ", ")
+                )
+            }
+            surfaceTabBarButtons = normalizedButtons
         } else {
             surfaceTabBarButtons = nil
         }
         commands = try container.decodeIfPresent([CmuxCommandDefinition].self, forKey: .commands) ?? []
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 26 - 58, The init(from decoder:
Decoder) currently throws DecodingError when a top-level optional value is
invalid; change it to normalize and preserve the rest of the config by replacing
throwing behavior in init(from decoder: Decoder): for newWorkspaceCommand, trim
and if empty set newWorkspaceCommand = nil and emit a non-fatal log/warning (do
not throw); for surfaceTabBarButtons, deduplicate the decoded
[CmuxSurfaceTabBarButton] (e.g., build a filtered array keeping first
occurrences) and assign the deduped array (or nil if empty) while logging a
warning instead of throwing; keep commands = try
container.decodeIfPresent([...]) ?? [] unchanged so invalid top-level values
don’t discard commands.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/CmuxConfig.swift`:
- Around line 505-515: The resolvedNewWorkspaceCommand() currently logs the same
invalid newWorkspaceCommand every time; add a small dedupe cache (e.g., a stored
property like lastInvalidNewWorkspaceCommand tuple containing name, reason, and
configRevision) and use it inside resolvedNewWorkspaceCommand() to only call
NSLog when the (newWorkspaceCommandName, reason, configRevision) differs from
the cached tuple; update the cache with the new tuple whenever you return nil
for a different reason or revision. Reference resolvedNewWorkspaceCommand(),
newWorkspaceCommandName, loadedCommands, commandSourcePaths, and the
configRevision value so the logic detects three distinct reasons (not found, not
a workspace command, or nil) and suppresses repeated logs until the
configRevision or name/reason changes.
- Around line 26-58: The init(from decoder: Decoder) currently throws
DecodingError when a top-level optional value is invalid; change it to normalize
and preserve the rest of the config by replacing throwing behavior in init(from
decoder: Decoder): for newWorkspaceCommand, trim and if empty set
newWorkspaceCommand = nil and emit a non-fatal log/warning (do not throw); for
surfaceTabBarButtons, deduplicate the decoded [CmuxSurfaceTabBarButton] (e.g.,
build a filtered array keeping first occurrences) and assign the deduped array
(or nil if empty) while logging a warning instead of throwing; keep commands =
try container.decodeIfPresent([...]) ?? [] unchanged so invalid top-level values
don’t discard commands.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6988ecc4-493b-4d5d-b5a8-a6a7790a9775

📥 Commits

Reviewing files that changed from the base of the PR and between 02bcff1 and fad4350.

📒 Files selected for processing (6)
  • Sources/CmuxConfig.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • cmuxTests/CmuxConfigTests.swift
  • vendor/bonsplit
  • web/app/[locale]/docs/custom-commands/page.tsx
✅ Files skipped from review due to trivial changes (1)
  • web/app/[locale]/docs/custom-commands/page.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • vendor/bonsplit
  • cmuxTests/CmuxConfigTests.swift

@lawrencecchen
lawrencecchen force-pushed the feat-custom-new-command branch from 4475e43 to ed45d1b Compare April 22, 2026 04:51
@lawrencecchen lawrencecchen changed the title Add configurable new workspace command Add configurable cmux.json workspace and tab bar actions Apr 22, 2026
@lawrencecchen
lawrencecchen force-pushed the feat-custom-new-command branch from ed45d1b to 171bf7c Compare April 22, 2026 05:41

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 171bf7ca51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Workspace.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: 10

Caution

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

⚠️ Outside diff range comments (2)
Resources/Localizable.xcstrings (1)

16860-16890: ⚠️ Potential issue | 🟡 Minor

Reusing existing keys with changed semantics leaves non-en/ja translations stale.

dialog.cmuxConfig.confirmCommand.run and dialog.cmuxConfig.confirmCommand.title changed from generic "Run"/"Run Command" wording to "Run Once"/"Run Project Action?" but 16 locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) retain their old translations. This creates a semantic mismatch where the trust dialog shows translated "Run Command" instead of "Run Once".

Consider introducing new semantic keys, or updating the translations on these two keys across all locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/Localizable.xcstrings` around lines 16860 - 16890, The localization
keys dialog.cmuxConfig.confirmCommand.run and
dialog.cmuxConfig.confirmCommand.title were repurposed from "Run"/"Run Command"
to "Run Once"/"Run Project Action?", causing non-en/ja translations to be
semantically stale; either add new keys (e.g.,
dialog.cmuxConfig.confirmCommand.runOnce and
dialog.cmuxConfig.confirmCommand.titleProjectAction) and update all locale files
with the new strings, or update the existing non-English entries for those two
keys across the affected locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR,
ru, th, tr, uk, zh-Hans, zh-Hant) to match the new English/ja semantics so the
trust dialog displays correct translations.
Sources/CmuxConfig.swift (1)

1874-1899: ⚠️ Potential issue | 🟠 Major

Don’t cancel the action-trust observer when wiring directory tracking.

wireDirectoryTracking removes all cancellables, which cancels the CmuxActionTrust.didChangeNotification subscription installed in init. After this, “Trust and Run” changes won’t reapply tab-bar button trust/lock state.

Proposed fix
+    private var trustCancellables = Set<AnyCancellable>()
     private var cancellables = Set<AnyCancellable>()
@@
         NotificationCenter.default.publisher(for: CmuxActionTrust.didChangeNotification)
             .receive(on: DispatchQueue.main)
             .sink { [weak self] _ in
@@
                 self.configRevision &+= 1
             }
-            .store(in: &cancellables)
+            .store(in: &trustCancellables)
@@
     func wireDirectoryTracking(tabManager: TabManager) {
         cancellables.removeAll()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfig.swift` around lines 1874 - 1899, The NotificationCenter
subscription to CmuxActionTrust.didChangeNotification was stored in cancellables
in init and gets cancelled by wireDirectoryTracking's cancellables.removeAll(),
so create a dedicated AnyCancellable property (e.g., actionTrustObserver:
AnyCancellable?) and store the NotificationCenter publisher's sink there instead
of into cancellables; then leave cancellables.removeAll() in
wireDirectoryTracking to clear only tab-related subscriptions, and cancel
actionTrustObserver in deinit (or when no longer needed). This preserves the
CmuxActionTrust.didChangeNotification observer while still allowing
tabManager-related cancellables to be cleared.
♻️ Duplicate comments (3)
Sources/AppDelegate.swift (1)

4964-4984: ⚠️ Potential issue | 🟠 Major

Honor preferredTabManager before creating a fallback window.

Line 4964 takes the empty-registry fallback before the later preferred-manager fallback. If a caller passes a live preferredTabManager before its window context is registered, this creates a second main window instead of adding the workspace to the explicit manager.

Suggested fix
-        if mainWindowContexts.isEmpty && livePreferredContext == nil {
+        if mainWindowContexts.isEmpty && livePreferredContext == nil {
+            if let preferredTabManager, preferredContext == nil {
+                preferredTabManager.addWorkspace()
+                return true
+            }
 `#if` DEBUG
             logWorkspaceCreationRouting(
                 phase: "fallback_new_window",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 4964 - 4984, The fallback branch
currently creates a new main window when mainWindowContexts.isEmpty and
livePreferredContext == nil; reorder or extend this logic to honor the
caller-provided preferredTabManager before creating a fallback window by
checking preferredTabManager (and resolving livePreferredContext from it)
earlier: if a livePreferredContext can be obtained for the preferredTabManager,
use that context's tabManager/selectedWorkspace and call
executeConfiguredNewWorkspaceCommandIfAvailable(in:context, debugSource:...,
replacingInitialWorkspace:...) instead of calling createMainWindow(); only call
createMainWindow() when no preferredTabManager/livePreferredContext exists and
mainWindowContexts is truly empty. Ensure you reference mainWindowContexts,
livePreferredContext, preferredTabManager, createMainWindow(),
executeConfiguredNewWorkspaceCommandIfAvailable(in:), and
tabManager.selectedWorkspace when making the change.
Sources/CmuxConfigExecutor.swift (2)

78-96: ⚠️ Potential issue | 🟠 Major

Check .currentTerminal availability before preparing trusted input.

For action-based terminal commands, preparedShellInput(...) can show confirmation and persist trust before Line 95 discovers there is no focused terminal. Guard the focused terminal first when target == .currentTerminal.

Proposed fix
         guard let command = action.terminalCommand else { return false }
         let target = action.terminalCommandTarget ?? .newTabInCurrentPane
+        let currentTerminal = tabManager.selectedWorkspace?.focusedTerminalPanel
+        if target == .currentTerminal, currentTerminal == nil {
+            return false
+        }
         guard let shellInput = preparedShellInput(
             command,
             confirm: action.confirm ?? false,
@@
         switch target {
         case .currentTerminal:
-            guard let terminal = tabManager.selectedWorkspace?.focusedTerminalPanel else { return false }
+            guard let terminal = currentTerminal else { return false }
             terminal.sendInput(shellInput)
             return true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfigExecutor.swift` around lines 78 - 96, When target ==
.currentTerminal you must ensure a focused terminal exists before calling
preparedShellInput to avoid persisting trust or showing confirmation for a
command that cannot be delivered; modify the flow in CmuxConfigExecutor so that
after setting let target = action.terminalCommandTarget you first check if
target == .currentTerminal and guard let terminal =
tabManager.selectedWorkspace?.focusedTerminalPanel else { return false }, and
only then call preparedShellInput(...); keep references to
preparedShellInput(...), action.terminalCommandTarget/.currentTerminal,
tabManager.selectedWorkspace?.focusedTerminalPanel and terminal.sendInput in the
updated order.

146-154: ⚠️ Potential issue | 🟠 Major

Honor confirm: true even when the source path is unavailable.

Line 146 treats a nil configSourcePath as trusted, so an explicitly confirmed command can run without any prompt. Use globalConfigPath as the fallback display/trust path or add a no-path confirmation path.

Proposed fix
-        guard let sourcePath = configSourcePath,
-              sourcePath != globalConfigPath else {
+        let sourcePath = configSourcePath ?? globalConfigPath
+        guard sourcePath != globalConfigPath || confirm else {
             return true
         }

If global-config commands are intended to stay trusted by default, keep that fast path only when confirm == false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxConfigExecutor.swift` around lines 146 - 154, The early-return
currently treats a nil configSourcePath as trusted; change the logic so we only
bypass confirmation when confirm is false and the path equals globalConfigPath,
and otherwise use a fallback display/trust path (let displayPath =
configSourcePath ?? globalConfigPath) for both the
CmuxActionTrust.shared.isTrusted(descriptor) check and the
showConfirmDialog(command: displayCommand, descriptor: descriptor, configPath:
displayPath) call; update the guard/if branches accordingly so explicit confirm
== true always routes to showConfirmDialog with the fallback path.
🧹 Nitpick comments (1)
web/app/[locale]/docs/custom-commands/page.tsx (1)

56-107: Icon schema documented inconsistently across examples.

The documentation shows icon in two different forms:

  • Lines 56–107: object form { "type": "image", "path": "./icons/codex.svg" }
  • Lines 260–280: string shorthand "./icons/codex.svg" and bare symbol "play.circle"

The prose (lines 122–123) mentions only "SF Symbols, emoji, or image paths" without explaining the object form or string variants.

The accepted encodings are:

  • { "type": "symbol" / "sfSymbol" / "systemImage", "name": "..." }
  • { "type": "emoji", "value": "..." }
  • { "type": "image" / "file", "path": "..." }
  • "emoji:..." (shorthand)
  • "file:..." (shorthand)
  • bare SF Symbol name (e.g., "play.circle")
  • relative image path (auto-detected by extension)

Consider consolidating the examples to use one canonical form or adding a subsection that enumerates all accepted icon encodings so readers can map examples to the schema unambiguously.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/`[locale]/docs/custom-commands/page.tsx around lines 56 - 107, The
examples for icon encoding are inconsistent and the prose doesn't enumerate
accepted encodings; update the docs around the CodeBlock example that defines
"cmux.newTerminal" / "claude" and the later examples (the sections showing bare
`"play.circle"` and `"./icons/codex.svg"`) to either use one canonical icon form
or add a concise "Icon encodings" subsection that lists the accepted encodings
and their canonical keys: object forms `{
"type":"symbol"|"sfSymbol"|"systemImage","name":"..." }`, `{
"type":"emoji","value":"..." }`, `{ "type":"image"|"file","path":"..." }`,
string shorthands `"emoji:..."`, `"file:..."`, bare SF Symbol names like
`"play.circle"`, and relative image paths; reference the `icon` fields in the
CodeBlock and any other examples so all snippets match the documented schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/CmuxActionTrust.swift`:
- Around line 15-20: The current fingerprint computed in var fingerprint: String
masks JSON encoding failures by hashing empty Data; change the API to surface
encoding errors (make fingerprint either throwing or optional: e.g., func
fingerprint() throws -> String or var fingerprint: String?) so encoding failure
returns nil/throws instead of producing a deterministic empty-data hash; update
Self.sha256Hex usage to only be called when encoding succeeds and ensure callers
of fingerprint/trust-check (the other similar property at the second occurrence
around lines 54-61) treat a nil/throw as "deny trust" (fail-closed) rather than
accepting a default fingerprint.

In `@Sources/CmuxConfig.swift`:
- Around line 2189-2240: The resolver currently drops source path info for
direct buttons and explicit icon overrides; update resolvedSurfaceTabBarButtons
to accept a button-list source path parameter and propagate it into
resolvedSurfaceTabBarButton so that when a button is direct (command/agent) the
returned ResolvedSurfaceTabBarButtonEntry.actionSourcePath is set to the
button-list source path (not nil), and when an icon is explicitly overridden set
the CmuxSurfaceTabBarButton.iconSourcePath to the button-list source path;
ensure terminalCommandSourcePaths and existing actionSourcePath logic (from
entry.actionSourcePath) remain unchanged and only fill missing source fields
with the provided button-list source path.

In `@Sources/ContentView.swift`:
- Around line 6056-6060: The palette item with commandId "palette.newWorkspace"
isn't being mapped to the configured "new-workspace" action, so cmux.json
overrides don't apply; update the lookup that uses
commandPaletteConfigActionID(for:) (or the code that computes
configuredPaletteAction via commandPaletteConfigActionID(...).flatMap {
cmuxConfigStore.resolvedAction(id: $0) }) to translate "palette.newWorkspace" to
the configured action id "new-workspace" before resolving (e.g., return
"new-workspace" from commandPaletteConfigActionID(for:) for that commandId or
substitute the id prior to calling cmuxConfigStore.resolvedAction), and make the
same change for the other occurrences referenced (the blocks around the other
ranges) so palette rows for new-workspace honor
palette.hide/title/keywords/shortcut overrides.
- Around line 7127-7134: Handlers currently call executeConfiguredAction(id:)
and fall back to built-ins when it returns false, but false conflates "no
configured action" and "configured action declined" — change the handlers (the
registry.register blocks that reference
CmuxSurfaceTabBarBuiltInAction.newTerminal, newBrowser and the split handlers)
to first check for the presence of a configured action and only call the
built-in fallback when no configured action exists; i.e., replace the pattern
`if !executeConfiguredAction(id: ...) { <builtin>() }` with logic like `if
hasConfiguredAction(id: ...) { _ = executeConfiguredAction(id: ...) ; return }
else { <builtin>() }` (implement a small helper hasConfiguredAction(id:) that
queries the same configuration store used by executeConfiguredAction if one does
not exist) and apply this change to the browser, terminal and split handlers
mentioned.
- Around line 7468-7480: In executeConfiguredAction, derive baseCwd from the
currently focused surface’s tracked directory instead of always using
tabManager.selectedWorkspace?.currentDirectory: first attempt to read
focusedSurface?.trackedDirectory (or focusedPanel?.surface?.trackedDirectory)
and use it if non-empty, otherwise fall back to
tabManager.selectedWorkspace?.currentDirectory and finally
FileManager.default.homeDirectoryForCurrentUser.path; then pass that baseCwd
into CmuxConfigExecutor.execute (preserving the existing parameter list) so
configured actions run in the focused pane’s directory.

In `@Sources/KeyboardShortcutSettings.swift`:
- Around line 1986-2001: The helper configKeyString(preserveDigit:) currently
returns key for both branches; implement the intended behavior so that when
preserveDigit is true it returns the original key, and when false it
canonicalizes digit keys to a generic placeholder (e.g., replace
single-character numeric keys "0".."9" with a stable token like "digit" or "#"
or whatever the codebase expects) while leaving non-digit keys unchanged; update
the configKeyString(_:), referenced by configString(preserveDigit:) and callers
such as KeyboardShortcutSettingsFileStore, to perform this transformation and
ensure any case/normalization used elsewhere (e.g., lowercasing) is preserved.
- Around line 2003-2046: parseConfigKeyToken currently rejects multi-character
tokens (so configString values like "f5" or "media.playPause" can't be
round-tripped); update parseConfigKeyToken to accept and return function-key
tokens ("f1"…"f20") and media-key tokens ("media.*") by recognizing these
patterns in the lowered input (e.g., regex for ^f([1-9]|1[0-9]|20)$ and
^media\.[a-z0-9]+$) and returning the lowered token unchanged; keep all existing
single-char and named-key handling intact and implement this logic inside
parseConfigKeyToken so hand-edited "cmd+f5" and "cmd+media.playPause" parse
successfully.

In `@Sources/TabManager.swift`:
- Around line 5290-5306: The call in TabManager.applySurfaceTabBarButtons(...)
only updates existing tabs and needs to persist the resolved config so future
workspaces get the same buttons; update TabManager to store the resolved tuple
(buttons, sourcePath, globalConfigPath, terminalCommandSourcePaths,
workspaceCommands) as a property (e.g., currentSurfaceTabBarConfig), modify
applySurfaceTabBarButtons to save that config before iterating, and add a helper
applyCurrentSurfaceTabBarButtons(to workspace:) that applies the cached config
to a single workspace by calling workspace.applySurfaceTabBarButtons(...);
finally, invoke applyCurrentSurfaceTabBarButtons(to:) from workspace
creation/attachment/restore code paths (e.g., inside addWorkspace(...),
attachWorkspace(...), and the session restore handler) so newly added or
restored workspaces receive the persisted tab-bar buttons.

In `@Sources/Workspace.swift`:
- Around line 12448-12450: The delegate method
splitTabBar(_:didRequestCustomAction:inPane:) currently forwards the action to
executeSurfaceTabBarCommandButton without logging; add a debug-only dlog() call
before dispatching so custom tab-bar actions are recorded. Specifically, inside
func splitTabBar(_ controller: BonsplitController, didRequestCustomAction
identifier: String, inPane pane: PaneID) insert a dlog(...) invocation wrapped
in `#if` DEBUG / `#endif` that logs the identifier and pane (or pane.description)
and then call executeSurfaceTabBarCommandButton(identifier: identifier, inPane:
pane) as before.

In `@web/app/`[locale]/docs/custom-commands/page.tsx:
- Around line 29-47: The new hard-coded English strings on this page should be
moved into the translations and referenced with t(...): add keys under the
docs.customCommands namespace (e.g. fallbackLocal, nightlyCallout, trustCallout,
schemaIntro, nightlyActionRegistry*, etc.) in web/messages/*.json and replace
the inline English JSX text with t("docs.customCommands.<key>") calls; update
the fallback-local list item (the <li> that currently says "./cmux.json"), both
Callout components (the two <Callout type="info"> blocks), the schema intro
paragraph, and the Nightly action registry section to use t(...) just like the
other strings already using t, ensuring key names match the suggested
identifiers and mirror existing usage of the t function and Callout component in
this file.

---

Outside diff comments:
In `@Resources/Localizable.xcstrings`:
- Around line 16860-16890: The localization keys
dialog.cmuxConfig.confirmCommand.run and dialog.cmuxConfig.confirmCommand.title
were repurposed from "Run"/"Run Command" to "Run Once"/"Run Project Action?",
causing non-en/ja translations to be semantically stale; either add new keys
(e.g., dialog.cmuxConfig.confirmCommand.runOnce and
dialog.cmuxConfig.confirmCommand.titleProjectAction) and update all locale files
with the new strings, or update the existing non-English entries for those two
keys across the affected locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR,
ru, th, tr, uk, zh-Hans, zh-Hant) to match the new English/ja semantics so the
trust dialog displays correct translations.

In `@Sources/CmuxConfig.swift`:
- Around line 1874-1899: The NotificationCenter subscription to
CmuxActionTrust.didChangeNotification was stored in cancellables in init and
gets cancelled by wireDirectoryTracking's cancellables.removeAll(), so create a
dedicated AnyCancellable property (e.g., actionTrustObserver: AnyCancellable?)
and store the NotificationCenter publisher's sink there instead of into
cancellables; then leave cancellables.removeAll() in wireDirectoryTracking to
clear only tab-related subscriptions, and cancel actionTrustObserver in deinit
(or when no longer needed). This preserves the
CmuxActionTrust.didChangeNotification observer while still allowing
tabManager-related cancellables to be cleared.

---

Duplicate comments:
In `@Sources/AppDelegate.swift`:
- Around line 4964-4984: The fallback branch currently creates a new main window
when mainWindowContexts.isEmpty and livePreferredContext == nil; reorder or
extend this logic to honor the caller-provided preferredTabManager before
creating a fallback window by checking preferredTabManager (and resolving
livePreferredContext from it) earlier: if a livePreferredContext can be obtained
for the preferredTabManager, use that context's tabManager/selectedWorkspace and
call executeConfiguredNewWorkspaceCommandIfAvailable(in:context,
debugSource:..., replacingInitialWorkspace:...) instead of calling
createMainWindow(); only call createMainWindow() when no
preferredTabManager/livePreferredContext exists and mainWindowContexts is truly
empty. Ensure you reference mainWindowContexts, livePreferredContext,
preferredTabManager, createMainWindow(),
executeConfiguredNewWorkspaceCommandIfAvailable(in:), and
tabManager.selectedWorkspace when making the change.

In `@Sources/CmuxConfigExecutor.swift`:
- Around line 78-96: When target == .currentTerminal you must ensure a focused
terminal exists before calling preparedShellInput to avoid persisting trust or
showing confirmation for a command that cannot be delivered; modify the flow in
CmuxConfigExecutor so that after setting let target =
action.terminalCommandTarget you first check if target == .currentTerminal and
guard let terminal = tabManager.selectedWorkspace?.focusedTerminalPanel else {
return false }, and only then call preparedShellInput(...); keep references to
preparedShellInput(...), action.terminalCommandTarget/.currentTerminal,
tabManager.selectedWorkspace?.focusedTerminalPanel and terminal.sendInput in the
updated order.
- Around line 146-154: The early-return currently treats a nil configSourcePath
as trusted; change the logic so we only bypass confirmation when confirm is
false and the path equals globalConfigPath, and otherwise use a fallback
display/trust path (let displayPath = configSourcePath ?? globalConfigPath) for
both the CmuxActionTrust.shared.isTrusted(descriptor) check and the
showConfirmDialog(command: displayCommand, descriptor: descriptor, configPath:
displayPath) call; update the guard/if branches accordingly so explicit confirm
== true always routes to showConfirmDialog with the fallback path.

---

Nitpick comments:
In `@web/app/`[locale]/docs/custom-commands/page.tsx:
- Around line 56-107: The examples for icon encoding are inconsistent and the
prose doesn't enumerate accepted encodings; update the docs around the CodeBlock
example that defines "cmux.newTerminal" / "claude" and the later examples (the
sections showing bare `"play.circle"` and `"./icons/codex.svg"`) to either use
one canonical icon form or add a concise "Icon encodings" subsection that lists
the accepted encodings and their canonical keys: object forms `{
"type":"symbol"|"sfSymbol"|"systemImage","name":"..." }`, `{
"type":"emoji","value":"..." }`, `{ "type":"image"|"file","path":"..." }`,
string shorthands `"emoji:..."`, `"file:..."`, bare SF Symbol names like
`"play.circle"`, and relative image paths; reference the `icon` fields in the
CodeBlock and any other examples so all snippets match the documented schema.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1489f37c-78da-43d5-ae40-960711714f0d

📥 Commits

Reviewing files that changed from the base of the PR and between 17ab8b4 and b392d7e.

📒 Files selected for processing (18)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/CmuxActionTrust.swift
  • Sources/CmuxConfig.swift
  • Sources/CmuxConfigExecutor.swift
  • Sources/CmuxDirectoryTrust.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/CmuxConfigTests.swift
  • web/app/[locale]/docs/custom-commands/page.tsx
  • web/data/cmux-settings.schema.json
  • web/messages/en.json
  • web/messages/ja.json
💤 Files with no reviewable changes (2)
  • web/data/cmux-settings.schema.json
  • Sources/CmuxDirectoryTrust.swift
✅ Files skipped from review due to trivial changes (2)
  • web/messages/en.json
  • web/messages/ja.json

Comment on lines +15 to +20
var fingerprint: String {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let data = (try? encoder.encode(self)) ?? Data()
return Self.sha256Hex(data)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail closed when trust descriptor encoding fails.

Line 18 hashes empty data on any encoding failure, causing all failed descriptors to share the same trusted fingerprint. Make fingerprinting optional/throwing and deny trust when encoding fails.

🛡️ Proposed fail-closed fix
-    var fingerprint: String {
+    var fingerprint: String? {
         let encoder = JSONEncoder()
         encoder.outputFormatting = [.sortedKeys]
-        let data = (try? encoder.encode(self)) ?? Data()
+        guard let data = try? encoder.encode(self) else { return nil }
         return Self.sha256Hex(data)
     }
@@
     func isTrusted(_ descriptor: CmuxActionTrustDescriptor) -> Bool {
-        trustedFingerprints.contains(descriptor.fingerprint)
+        guard let fingerprint = descriptor.fingerprint else { return false }
+        return trustedFingerprints.contains(fingerprint)
     }
 
     func trust(_ descriptor: CmuxActionTrustDescriptor) {
-        trustedFingerprints.insert(descriptor.fingerprint)
+        guard let fingerprint = descriptor.fingerprint else { return }
+        guard trustedFingerprints.insert(fingerprint).inserted else { return }
         save()
     }

Also applies to: 54-61

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/CmuxActionTrust.swift` around lines 15 - 20, The current fingerprint
computed in var fingerprint: String masks JSON encoding failures by hashing
empty Data; change the API to surface encoding errors (make fingerprint either
throwing or optional: e.g., func fingerprint() throws -> String or var
fingerprint: String?) so encoding failure returns nil/throws instead of
producing a deterministic empty-data hash; update Self.sha256Hex usage to only
be called when encoding succeeds and ensure callers of fingerprint/trust-check
(the other similar property at the second occurrence around lines 54-61) treat a
nil/throw as "deny trust" (fail-closed) rather than accepting a default
fingerprint.

Comment thread Sources/CmuxConfig.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/KeyboardShortcutSettings.swift
Comment thread Sources/KeyboardShortcutSettings.swift
Comment thread Sources/TabManager.swift
Comment thread Sources/Workspace.swift
Comment thread web/app/[locale]/docs/custom-commands/page.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03c0eb649b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/CmuxConfig.swift Outdated
Comment on lines +1904 to +1905
?? (((directory as NSString).appendingPathComponent(".cmux") as NSString)
.appendingPathComponent("cmux.json"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Watch parent dir for first local config creation

When no project config is found, updateLocalConfigPath now defaults to .../.cmux/cmux.json. The file watcher then tries to watch that .cmux directory, but if .cmux does not exist yet, startLocalDirectoryWatcher() exits early and no watcher remains active. In that state, creating either .cmux/cmux.json or legacy cmux.json later in the project is not detected until the workspace directory changes or the app restarts, so live config loading silently regresses for new projects.

Useful? React with 👍 / 👎.

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 watching the project directory while waiting for the first local config, including the empty .cmux directory plus legacy cmux.json case.

— Claude Code

Comment on lines +75 to +77
if let commandName = action.workspaceCommandName,
let command = commands.first(where: { $0.name == commandName }) {
return execute(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce workspace-only execution for workspaceCommand actions

This branch executes any command matched by name without verifying that it is actually a workspace command. If an action is declared as type: "workspaceCommand" but points to a simple command entry, it will run terminal input instead of being rejected, which contradicts the workspace-only validation already applied in other paths (for example new-workspace and tab-bar workspace resolution). This makes misconfigured actions behave inconsistently and can run unintended shell commands.

Useful? React with 👍 / 👎.

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 rejecting workspaceCommand actions unless the referenced command has a workspace definition.

— Claude Code

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b643c55cc3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +93 to +94
let targetTerminal = (target == .currentTerminal) ? tabManager.selectedWorkspace?.focusedTerminalPanel : nil
let targetWorkspace = (target == .newTabInCurrentPane) ? tabManager.selectedWorkspace : nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return false when current-terminal target has no terminal

execute(action:...) computes targetTerminal as optional for .currentTerminal actions, but it still proceeds through authorization and returns true even when no terminal is focused. In that state (for example, browser-focused workspaces), the action is marked handled while no input is sent, so callers can skip their fallback behavior and users get a silent no-op. Add an early guard for .currentTerminal so this path returns false when there is no terminal target.

Useful? React with 👍 / 👎.

Comment thread Sources/CmuxConfig.swift Outdated
Comment on lines +1469 to +1472
case .codex:
return "Codex"
case .claudeCode:
return "Claude Code"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Localize default agent action titles

defaultTitle(for:) introduces hardcoded English labels ("Codex", "Claude Code") for user-visible action titles when config omits title, so these remain untranslated in non-English locales. /workspace/cmux/AGENTS.md explicitly requires all user-facing strings to be localized; these defaults should use localized keys (and corresponding Localizable.xcstrings entries) instead of string literals.

Useful? React with 👍 / 👎.

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 localizing the default Codex and Claude Code action titles.

— Claude Code

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8072c47443

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Workspace.swift
Comment on lines +12465 to +12466
case .currentTerminal:
self.selectedTerminalPanel(inPane: pane)?.sendInput(shellInput)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject current-terminal tab-bar action without terminal target

When a surface-tab-bar button is configured with target: "currentTerminal", this path always reports success after authorization but sends input via selectedTerminalPanel(inPane:)?.sendInput(...). If the selected tab in that pane is a browser (no terminal panel), the optional send becomes a silent no-op: the click is consumed (and may even show/trust the confirmation dialog) while no command runs. This makes current-terminal actions appear broken in mixed browser/terminal panes and prevents any fallback behavior.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c0e9344d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +5153 to +5157
if didExecute,
let initialWorkspace,
context.tabManager.tabs.count > 1,
context.tabManager.tabs.contains(where: { $0 === initialWorkspace }),
context.tabManager.selectedWorkspace !== initialWorkspace {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Delay initial-workspace cleanup until command confirmation completes

This cleanup check assumes didExecute means the configured workspace command has already run, but for untrusted project actions CmuxConfigExecutor.authorizeProjectActionIfNeeded returns true immediately after starting a sheet and runs the command later in the callback. In the no-window fallback path (performNewWorkspaceAction), that means the initial default workspace is checked/removed too early and remains open after the user clicks Run Once/Trust and Run, so one New Workspace action can still leave two workspaces. Fresh evidence: the executor returns before user response in CmuxConfigExecutor (presentConfirmDialog branch).

Useful? React with 👍 / 👎.

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 moving initial-workspace cleanup into an executor callback that runs only after the configured command is authorized and executed.

— Claude Code

Comment thread Sources/CmuxConfig.swift Outdated
Comment on lines +2066 to +2067
if let action = globalActions[newWorkspaceActionID] {
configuredNewWorkspaceAction = action.definition

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve global new-workspace action through merged overrides

When only the global config provides ui.newWorkspace.action, this branch resolves the action ID from globalActions only, so a local config cannot override that action definition by reusing the same ID. This breaks the documented local-precedence merge behavior used elsewhere (for commands and tab-bar action resolution) and forces projects to duplicate the ui.newWorkspace.action placement just to customize the action body.

Useful? React with 👍 / 👎.

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 resolving a global ui.newWorkspace.action through the merged action map so local action definitions can override the global action body.

— Claude Code

@socket-security

socket-security Bot commented Apr 23, 2026 •

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e20e59caef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +372 to +374
let configSourcePath = terminalCommandSourcePath
?? workspaceCommand?.sourcePath
?? button.actionSourcePath

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use tab-bar config source when computing trust descriptor

surfaceButtonTrustDescriptor only derives configSourcePath from command/workspace sources (terminalCommandSourcePath, workspaceCommand?.sourcePath, button.actionSourcePath) and ignores the tab-bar config file source. For inline local buttons (or local icon overrides on global actions), this leaves configPath nil/global, so isTrustedSurfaceButton treats them as trusted and Workspace.applySurfaceTabBarButtons renders project-local image icons instead of the lock placeholder before trust is granted. This weakens the trust boundary for untrusted project configs.

Useful? React with 👍 / 👎.

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 including the surface-tab-bar config source in the button trust descriptor, including inline local icon overrides.

— Claude Code

@lawrencecchen
lawrencecchen merged commit 444197f into main Apr 23, 2026
17 checks passed
@lawrencecchen
lawrencecchen deleted the feat-custom-new-command branch April 23, 2026 10:36
@coderabbitai coderabbitai Bot mentioned this pull request May 13, 2026
6 tasks done

This branch was successfully deployed

1 active deployment
Preview — e7e21344 Deployed Apr 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant