From 493da5c7d83c73ba8bc4de555b219df2f3750d5e Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Thu, 26 Mar 2026 14:42:26 -0700 Subject: [PATCH 1/5] Fix tmux compat store decoding, layout cleanup, and cross-workspace fallback Three hardening fixes for the tmux compatibility layer: 1. Add custom init(from:) to TmuxCompatStore using decodeIfPresent so older store files missing mainVerticalLayouts/lastSplitSurface keys decode gracefully instead of silently resetting to an empty store. 2. Clear mainVerticalLayouts entry when a non-main-vertical layout is selected, preventing stale state from redirecting future splits. 3. Only enter caller-anchoring block in split-window when resolveWorkspaceId succeeds, avoiding cross-workspace splits when the fallback target.workspaceId differs from the caller's workspace. --- CLI/cmux.swift | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 3f4b67da7ae7..3d84199ca21a 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -10032,10 +10032,14 @@ struct CMUXCLI { // Claude's agent teams targets arbitrary panes (from list-panes), // not necessarily the leader pane from TMUX_PANE. Override the // target to anchor all teammate splits to the leader surface. + // Only apply caller anchoring when the caller's workspace resolves + // successfully. Falling back to target.workspaceId would pair + // the caller's surface with a different workspace, creating an + // invalid cross-workspace split. let store = loadTmuxCompatStore() if let callerSurface = tmuxCallerSurfaceHandle(), - let callerWorkspace = tmuxCallerWorkspaceHandle() { - let wsId = (try? resolveWorkspaceId(callerWorkspace, client: client)) ?? target.workspaceId + let callerWorkspace = tmuxCallerWorkspaceHandle(), + let wsId = try? resolveWorkspaceId(callerWorkspace, client: client) { if let mvState = store.mainVerticalLayouts[wsId], let lastColumn = mvState.lastColumnSurfaceId { // Main-vertical active: stack in right column. @@ -10369,6 +10373,14 @@ struct CMUXCLI { ) try saveTmuxCompatStore(store) } + } else if !layoutName.isEmpty { + // Non-main-vertical layout selected: clear stale state so + // future splits don't incorrectly redirect to the old column. + let workspaceId = try tmuxResolveWorkspaceTarget(parsed.value("-t"), client: client) + var store = loadTmuxCompatStore() + if store.mainVerticalLayouts.removeValue(forKey: workspaceId) != nil { + try saveTmuxCompatStore(store) + } } case "set-option", "set", "set-window-option", "setw", "source-file", "refresh-client", "attach-session", "detach-client": @@ -10400,6 +10412,19 @@ struct CMUXCLI { /// Used to seed lastColumnSurfaceId when select-layout main-vertical /// is called after the first split. var lastSplitSurface: [String: String] = [:] + + /// Custom decoder so older store files missing newer keys + /// (mainVerticalLayouts, lastSplitSurface) decode gracefully + /// instead of throwing and resetting the entire store. + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + buffers = try container.decodeIfPresent([String: String].self, forKey: .buffers) ?? [:] + hooks = try container.decodeIfPresent([String: String].self, forKey: .hooks) ?? [:] + mainVerticalLayouts = try container.decodeIfPresent([String: MainVerticalState].self, forKey: .mainVerticalLayouts) ?? [:] + lastSplitSurface = try container.decodeIfPresent([String: String].self, forKey: .lastSplitSurface) ?? [:] + } + + init() {} } private func tmuxCompatStoreURL() -> URL { From e73ee78f45c10bd302f1675672cfcff3eb228dbd Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Thu, 26 Mar 2026 16:02:13 -0700 Subject: [PATCH 2/5] Add auto-equalize after teammate splits After each teammate split-window, call workspace.equalize_splits with orientation: "vertical" to evenly distribute panes in the agent column without affecting the leader/column horizontal divider. Uses the equalize_splits socket method from the omo integration (PR #2087). The equalization works synchronously because bonsplit's setDividerPosition sets ratios on the internal split state directly, no layout flush needed. --- CLI/cmux.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 3d84199ca21a..145b9e73f653 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -10083,6 +10083,14 @@ struct CMUXCLI { try saveTmuxCompatStore(updatedStore) } + // Equalize vertical splits so teammate panes are evenly distributed. + // Use orientation: "vertical" to only equalize the agent column, + // preserving the leader/column horizontal divider position. + _ = try? client.sendV2(method: "workspace.equalize_splits", params: [ + "workspace_id": target.workspaceId, + "orientation": "vertical" + ]) + if let text = tmuxShellCommandText(commandTokens: parsed.positional, cwd: parsed.value("-c")) { _ = try client.sendV2(method: "surface.send_text", params: [ "workspace_id": target.workspaceId, From 2034079c39390c59632ea92d1617861bc23998a9 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Thu, 26 Mar 2026 17:15:55 -0700 Subject: [PATCH 3/5] Refactor: DRY up claude-teams and omo shared launcher code Extract shared functions from the nearly-identical claude-teams and omo integration code: - configureTmuxCompatEnvironment: parameterized env setup (tmux path prefix, bin env var, term override, extra vars) - createTmuxCompatShimDirectory: shared tmux shim creation with writeShimIfChanged - resolveExecutableInSearchPath: generic PATH search with optional skip predicate - Rename ClaudeTeamsFocusedContext -> TmuxCompatFocusedContext, claudeTeamsFocusedContext -> tmuxCompatFocusedContext, claudeTeamsResolvedSocketPath -> tmuxCompatResolvedSocketPath Both integrations are now thin wrappers over the shared functions. No behavior changes. --- CLI/cmux.swift | 178 ++++++++++++++++++++++++++----------------------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 145b9e73f653..f8c69dbc3f4a 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -9305,7 +9305,7 @@ struct CMUXCLI { return ordered.joined(separator: ":") } - private struct ClaudeTeamsFocusedContext { + private struct TmuxCompatFocusedContext { let socketPath: String let workspaceId: String let windowId: String? @@ -9314,7 +9314,7 @@ struct CMUXCLI { let surfaceId: String? } - private func claudeTeamsResolvedSocketPath(processEnvironment: [String: String]) -> String { + private func tmuxCompatResolvedSocketPath(processEnvironment: [String: String]) -> String { let envSocketPath: String? = { for key in ["CMUX_SOCKET_PATH", "CMUX_SOCKET"] { guard let raw = processEnvironment[key] else { continue } @@ -9341,11 +9341,11 @@ struct CMUXCLI { ) } - private func claudeTeamsFocusedContext( + private func tmuxCompatFocusedContext( processEnvironment: [String: String], explicitPassword: String? - ) -> ClaudeTeamsFocusedContext? { - let socketPath = claudeTeamsResolvedSocketPath(processEnvironment: processEnvironment) + ) -> TmuxCompatFocusedContext? { + let socketPath = tmuxCompatResolvedSocketPath(processEnvironment: processEnvironment) let client = SocketClient(path: socketPath) do { @@ -9379,7 +9379,7 @@ struct CMUXCLI { let surfaceId = (focused["surface_id"] as? String) ?? (focused["surface_ref"] as? String) - return ClaudeTeamsFocusedContext( + return TmuxCompatFocusedContext( socketPath: socketPath, workspaceId: workspaceId, windowId: windowId, @@ -9400,19 +9400,31 @@ struct CMUXCLI { return prefix.contains("cmux claude wrapper - injects hooks and session tracking") } - private func resolveClaudeExecutable(searchPath: String?) -> String? { + private func resolveExecutableInSearchPath( + _ name: String, + searchPath: String?, + skip: ((String) -> Bool)? = nil + ) -> String? { let entries = searchPath?.split(separator: ":").map(String.init) ?? [] for entry in entries where !entry.isEmpty { let candidate = URL(fileURLWithPath: entry, isDirectory: true) - .appendingPathComponent("claude", isDirectory: false) + .appendingPathComponent(name, isDirectory: false) .path guard FileManager.default.isExecutableFile(atPath: candidate) else { continue } - guard !isCmuxClaudeWrapper(at: candidate) else { continue } + if let skip, skip(candidate) { continue } return candidate } return nil } + private func resolveClaudeExecutable(searchPath: String?) -> String? { + resolveExecutableInSearchPath( + "claude", + searchPath: searchPath, + skip: { self.isCmuxClaudeWrapper(at: $0) } + ) + } + private func claudeTeamsHasExplicitTeammateMode(commandArgs: [String]) -> Bool { commandArgs.contains { arg in arg == "--teammate-mode" || arg.hasPrefix("--teammate-mode=") @@ -9426,13 +9438,17 @@ struct CMUXCLI { return ["--teammate-mode", "auto"] + commandArgs } - private func configureClaudeTeamsEnvironment( + private func configureTmuxCompatEnvironment( processEnvironment: [String: String], shimDirectory: URL, executablePath: String, socketPath: String, explicitPassword: String?, - focusedContext: ClaudeTeamsFocusedContext? + focusedContext: TmuxCompatFocusedContext?, + tmuxPathPrefix: String, + cmuxBinEnvVar: String, + termOverrideEnvVar: String, + extraEnvVars: [(key: String, value: String)] = [] ) { let updatedPath = prependPathEntries( [shimDirectory.path], @@ -9441,17 +9457,16 @@ struct CMUXCLI { let fakeTmuxValue: String = { if let focusedContext { let windowToken = focusedContext.windowId ?? focusedContext.workspaceId - return "/tmp/cmux-claude-teams/\(focusedContext.workspaceId),\(windowToken),\(focusedContext.paneHandle)" + return "/tmp/\(tmuxPathPrefix)/\(focusedContext.workspaceId),\(windowToken),\(focusedContext.paneHandle)" } - return processEnvironment["TMUX"] ?? "/tmp/cmux-claude-teams/default,0,0" + return processEnvironment["TMUX"] ?? "/tmp/\(tmuxPathPrefix)/default,0,0" }() let fakeTmuxPane = focusedContext.map { "%\($0.paneHandle)" } ?? processEnvironment["TMUX_PANE"] ?? "%1" - let fakeTerm = processEnvironment["CMUX_CLAUDE_TEAMS_TERM"] ?? "screen-256color" + let fakeTerm = processEnvironment[termOverrideEnvVar] ?? "screen-256color" - setenv("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS", "1", 1) - setenv("CMUX_CLAUDE_TEAMS_CMUX_BIN", executablePath, 1) + setenv(cmuxBinEnvVar, executablePath, 1) setenv("PATH", updatedPath, 1) setenv("TMUX", fakeTmuxValue, 1) setenv("TMUX_PANE", fakeTmuxPane, 1) @@ -9463,6 +9478,9 @@ struct CMUXCLI { setenv("CMUX_SOCKET_PASSWORD", explicitPassword, 1) } unsetenv("TERM_PROGRAM") + for envVar in extraEnvVars { + setenv(envVar.key, envVar.value, 1) + } if let focusedContext { setenv("CMUX_WORKSPACE_ID", focusedContext.workspaceId, 1) if let surfaceId = focusedContext.surfaceId, !surfaceId.isEmpty { @@ -9471,27 +9489,54 @@ struct CMUXCLI { } } - private func createClaudeTeamsShimDirectory() throws -> URL { + private func configureClaudeTeamsEnvironment( + processEnvironment: [String: String], + shimDirectory: URL, + executablePath: String, + socketPath: String, + explicitPassword: String?, + focusedContext: TmuxCompatFocusedContext? + ) { + configureTmuxCompatEnvironment( + processEnvironment: processEnvironment, + shimDirectory: shimDirectory, + executablePath: executablePath, + socketPath: socketPath, + explicitPassword: explicitPassword, + focusedContext: focusedContext, + tmuxPathPrefix: "cmux-claude-teams", + cmuxBinEnvVar: "CMUX_CLAUDE_TEAMS_CMUX_BIN", + termOverrideEnvVar: "CMUX_CLAUDE_TEAMS_TERM", + extraEnvVars: [ + (key: "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS", value: "1"), + ] + ) + } + + private func createTmuxCompatShimDirectory( + directoryName: String, + tmuxShimScript: String + ) throws -> URL { let homePath = ProcessInfo.processInfo.environment["HOME"] ?? NSHomeDirectory() - let rootPath = URL(fileURLWithPath: homePath, isDirectory: true) + let root = URL(fileURLWithPath: homePath, isDirectory: true) .appendingPathComponent(".cmuxterm", isDirectory: true) - .appendingPathComponent("claude-teams-bin", isDirectory: true) - .path - let root = URL(fileURLWithPath: rootPath, isDirectory: true) + .appendingPathComponent(directoryName, isDirectory: true) try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true, attributes: nil) let tmuxURL = root.appendingPathComponent("tmux", isDirectory: false) + try writeShimIfChanged(tmuxShimScript, to: tmuxURL) + return root + } + + private func createClaudeTeamsShimDirectory() throws -> URL { let script = """ #!/usr/bin/env bash set -euo pipefail exec "${CMUX_CLAUDE_TEAMS_CMUX_BIN:-cmux}" __tmux-compat "$@" """ - let normalizedScript = script.trimmingCharacters(in: .whitespacesAndNewlines) - let existingScript = try? String(contentsOf: tmuxURL, encoding: .utf8) - if existingScript?.trimmingCharacters(in: .whitespacesAndNewlines) != normalizedScript { - try script.write(to: tmuxURL, atomically: false, encoding: .utf8) - } - try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: tmuxURL.path) - return root + return try createTmuxCompatShimDirectory( + directoryName: "claude-teams-bin", + tmuxShimScript: script + ) } private func runClaudeTeams( @@ -9509,7 +9554,7 @@ struct CMUXCLI { } let shimDirectory = try createClaudeTeamsShimDirectory() let executablePath = resolvedExecutableURL()?.path ?? (args.first ?? "cmux") - let focusedContext = claudeTeamsFocusedContext( + let focusedContext = tmuxCompatFocusedContext( processEnvironment: launcherEnvironment, explicitPassword: explicitPassword ) @@ -9554,27 +9599,12 @@ struct CMUXCLI { // MARK: - cmux omo (OpenCode + oh-my-openagent) private func resolveOpenCodeExecutable(searchPath: String?) -> String? { - let entries = searchPath?.split(separator: ":").map(String.init) ?? [] - for entry in entries where !entry.isEmpty { - let candidate = URL(fileURLWithPath: entry, isDirectory: true) - .appendingPathComponent("opencode", isDirectory: false) - .path - guard FileManager.default.isExecutableFile(atPath: candidate) else { continue } - return candidate - } - return nil + resolveExecutableInSearchPath("opencode", searchPath: searchPath) } private func createOMOShimDirectory() throws -> URL { - let homePath = ProcessInfo.processInfo.environment["HOME"] ?? NSHomeDirectory() - let root = URL(fileURLWithPath: homePath, isDirectory: true) - .appendingPathComponent(".cmuxterm", isDirectory: true) - .appendingPathComponent("omo-bin", isDirectory: true) - try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true, attributes: nil) - // tmux shim: redirects tmux commands to cmux __tmux-compat // Handle -V locally (no socket needed) since __tmux-compat requires a connection. - let tmuxURL = root.appendingPathComponent("tmux", isDirectory: false) let tmuxScript = """ #!/usr/bin/env bash set -euo pipefail @@ -9585,7 +9615,10 @@ struct CMUXCLI { esac exec "${CMUX_OMO_CMUX_BIN:-cmux}" __tmux-compat "$@" """ - try writeShimIfChanged(tmuxScript, to: tmuxURL) + let root = try createTmuxCompatShimDirectory( + directoryName: "omo-bin", + tmuxShimScript: tmuxScript + ) // terminal-notifier shim: intercepts macOS notifications and routes to cmux notify let notifierURL = root.appendingPathComponent("terminal-notifier", isDirectory: false) @@ -9831,46 +9864,25 @@ struct CMUXCLI { executablePath: String, socketPath: String, explicitPassword: String?, - focusedContext: ClaudeTeamsFocusedContext? + focusedContext: TmuxCompatFocusedContext? ) { - let updatedPath = prependPathEntries( - [shimDirectory.path], - to: processEnvironment["PATH"] - ) - let fakeTmuxValue: String = { - if let focusedContext { - let windowToken = focusedContext.windowId ?? focusedContext.workspaceId - return "/tmp/cmux-omo/\(focusedContext.workspaceId),\(windowToken),\(focusedContext.paneHandle)" - } - return processEnvironment["TMUX"] ?? "/tmp/cmux-omo/default,0,0" - }() - let fakeTmuxPane = focusedContext.map { "%\($0.paneHandle)" } - ?? processEnvironment["TMUX_PANE"] - ?? "%1" - let fakeTerm = processEnvironment["CMUX_OMO_TERM"] ?? "screen-256color" - - setenv("CMUX_OMO_CMUX_BIN", executablePath, 1) - setenv("PATH", updatedPath, 1) - setenv("TMUX", fakeTmuxValue, 1) - setenv("TMUX_PANE", fakeTmuxPane, 1) - setenv("TERM", fakeTerm, 1) - setenv("CMUX_SOCKET_PATH", socketPath, 1) - setenv("CMUX_SOCKET", socketPath, 1) - if let explicitPassword, - !explicitPassword.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - setenv("CMUX_SOCKET_PASSWORD", explicitPassword, 1) - } - unsetenv("TERM_PROGRAM") // Tell oh-my-opencode the API server port so subagent attach works + var extraEnvVars: [(key: String, value: String)] = [] if processEnvironment["OPENCODE_PORT"] == nil { - setenv("OPENCODE_PORT", "4096", 1) - } - if let focusedContext { - setenv("CMUX_WORKSPACE_ID", focusedContext.workspaceId, 1) - if let surfaceId = focusedContext.surfaceId, !surfaceId.isEmpty { - setenv("CMUX_SURFACE_ID", surfaceId, 1) - } + extraEnvVars.append((key: "OPENCODE_PORT", value: "4096")) } + configureTmuxCompatEnvironment( + processEnvironment: processEnvironment, + shimDirectory: shimDirectory, + executablePath: executablePath, + socketPath: socketPath, + explicitPassword: explicitPassword, + focusedContext: focusedContext, + tmuxPathPrefix: "cmux-omo", + cmuxBinEnvVar: "CMUX_OMO_CMUX_BIN", + termOverrideEnvVar: "CMUX_OMO_TERM", + extraEnvVars: extraEnvVars + ) } private func runOMO( @@ -9891,7 +9903,7 @@ struct CMUXCLI { } let shimDirectory = try createOMOShimDirectory() let executablePath = resolvedExecutableURL()?.path ?? (args.first ?? "cmux") - let focusedContext = claudeTeamsFocusedContext( + let focusedContext = tmuxCompatFocusedContext( processEnvironment: launcherEnvironment, explicitPassword: explicitPassword ) From 1e11f5b72cb07614413270e3e74d4bef4d3c9d58 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Thu, 26 Mar 2026 18:30:03 -0700 Subject: [PATCH 4/5] Address PR review comments: store load, stale state, pane targets 1. Move loadTmuxCompatStore() inside the caller-anchoring if-let so it's only called when env vars are present (avoids unnecessary file I/O on non-agent splits). 2. Clear lastSplitSurface alongside mainVerticalLayouts when switching away from main-vertical layout, preventing stale seed values on re-entry. 3. Resolve select-layout -t via tmuxResolvePaneTarget first (tmux accepts pane targets like %1), falling back to workspace target. Removes duplicate workspace resolution in the else branch. --- CLI/cmux.swift | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index f8c69dbc3f4a..00106ac43db0 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -10048,10 +10048,10 @@ struct CMUXCLI { // successfully. Falling back to target.workspaceId would pair // the caller's surface with a different workspace, creating an // invalid cross-workspace split. - let store = loadTmuxCompatStore() if let callerSurface = tmuxCallerSurfaceHandle(), let callerWorkspace = tmuxCallerWorkspaceHandle(), let wsId = try? resolveWorkspaceId(callerWorkspace, client: client) { + let store = loadTmuxCompatStore() if let mvState = store.mainVerticalLayouts[wsId], let lastColumn = mvState.lastColumnSurfaceId { // Main-vertical active: stack in right column. @@ -10369,7 +10369,20 @@ struct CMUXCLI { case "select-layout": let parsed = try parseTmuxArguments(rawArgs, valueFlags: ["-t"], boolFlags: []) let layoutName = parsed.positional.first ?? "" - let workspaceId = try tmuxResolveWorkspaceTarget(parsed.value("-t"), client: client) + // select-layout -t accepts pane targets (e.g. %1) in real tmux. + // Resolve via pane first, fall back to workspace target. + let workspaceId: String = { + if let target = parsed.value("-t"), + let resolved = try? tmuxResolvePaneTarget(target, client: client) { + return resolved.workspaceId + } + return (try? tmuxResolveWorkspaceTarget(parsed.value("-t"), client: client)) + ?? (try? tmuxResolveWorkspaceTarget(nil, client: client)) + ?? "" + }() + guard !workspaceId.isEmpty else { + throw CLIError(message: "Could not resolve workspace for select-layout") + } if layoutName == "main-vertical" || layoutName == "main-horizontal" { // For main-* layouts, only equalize the agent column (vertical splits), // not the top-level horizontal split between main and agents. @@ -10396,9 +10409,10 @@ struct CMUXCLI { } else if !layoutName.isEmpty { // Non-main-vertical layout selected: clear stale state so // future splits don't incorrectly redirect to the old column. - let workspaceId = try tmuxResolveWorkspaceTarget(parsed.value("-t"), client: client) var store = loadTmuxCompatStore() - if store.mainVerticalLayouts.removeValue(forKey: workspaceId) != nil { + let removedLayout = store.mainVerticalLayouts.removeValue(forKey: workspaceId) != nil + let removedSplit = store.lastSplitSurface.removeValue(forKey: workspaceId) != nil + if removedLayout || removedSplit { try saveTmuxCompatStore(store) } } From 2a5e6e14c9d26d89e385a81a29a2da87dbc14890 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Thu, 26 Mar 2026 18:45:55 -0700 Subject: [PATCH 5/5] Fix select-layout -t fallback: don't apply to wrong workspace When an explicit -t target fails to resolve, error instead of silently falling back to the caller's current workspace. Only use the current workspace as default when no -t was provided. --- CLI/cmux.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 00106ac43db0..6f86efdf72d4 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -10370,15 +10370,18 @@ struct CMUXCLI { let parsed = try parseTmuxArguments(rawArgs, valueFlags: ["-t"], boolFlags: []) let layoutName = parsed.positional.first ?? "" // select-layout -t accepts pane targets (e.g. %1) in real tmux. - // Resolve via pane first, fall back to workspace target. + // Try pane target first, then workspace target. Only fall back to + // the caller's current workspace when no -t was provided; an + // explicit -t that fails to resolve should error, not silently + // apply to the wrong workspace. let workspaceId: String = { - if let target = parsed.value("-t"), - let resolved = try? tmuxResolvePaneTarget(target, client: client) { - return resolved.workspaceId + if let target = parsed.value("-t") { + if let resolved = try? tmuxResolvePaneTarget(target, client: client) { + return resolved.workspaceId + } + return (try? tmuxResolveWorkspaceTarget(target, client: client)) ?? "" } - return (try? tmuxResolveWorkspaceTarget(parsed.value("-t"), client: client)) - ?? (try? tmuxResolveWorkspaceTarget(nil, client: client)) - ?? "" + return (try? tmuxResolveWorkspaceTarget(nil, client: client)) ?? "" }() guard !workspaceId.isEmpty else { throw CLIError(message: "Could not resolve workspace for select-layout")