Skip to content

Add tmux shim for native agent teams support#1102

Open
adisinghstudent wants to merge 4 commits intomanaflow-ai:mainfrom
adisinghstudent:feat/tmux-shim-agent-teams
Open

Add tmux shim for native agent teams support#1102
adisinghstudent wants to merge 4 commits intomanaflow-ai:mainfrom
adisinghstudent:feat/tmux-shim-agent-teams

Conversation

@adisinghstudent
Copy link

@adisinghstudent adisinghstudent commented Mar 9, 2026

Summary

  • Adds a tmux shim at Resources/bin/tmux that translates tmux commands to cmux equivalents, enabling AI coding agent teams to natively spawn teammates in cmux splits
  • Exports TMUX env var in every cmux shell (GhosttyTerminalView.swift) so tools that detect tmux route through the shim

How it works

AI coding agents with team/multi-agent features detect tmux via the TMUX env var, then call:

  1. tmux split-window -h → shim runs cmux new-split right
  2. tmux send-keys -t %N "command" Enter → shim runs cmux send --surface surface:N "..."
  3. tmux capture-pane -t %N -p → shim runs cmux capture-pane --surface surface:N
  4. tmux kill-pane -t %N → shim runs cmux close-surface --surface surface:N

The shim maintains a %Nsurface:N pane ID mapping file so all commands target the correct cmux surfaces.

Since Resources/bin/ is prepended to PATH in every cmux shell, the shim intercepts tmux calls before the real tmux binary (if installed).

Supported tmux commands

tmux command cmux equivalent
split-window -h/-v new-split right/down
send-keys -t %N send --surface surface:N
capture-pane -t %N -p capture-pane --surface surface:N
kill-pane -t %N close-surface --surface surface:N
select-pane -t %N focus-panel --panel surface:N
resize-pane resize-pane (passthrough)
has-session always succeeds
display-message -p returns cmux session info
list-panes -F list-panes with format translation
new-window new-workspace
+ 12 more passthrough to cmux tmux-compat layer

Test plan

  • split-window → creates visible cmux split, returns clean %N pane ID
  • send-keys → sends text and Enter to correct surface, no stdout noise
  • capture-pane → reads terminal content from target surface
  • kill-pane → closes the correct surface cleanly
  • has-session / list-sessions / display-message → return expected values
  • Full agent teams spawn pattern: split → cd → send command → capture output → cleanup
  • Needs end-to-end testing with actual agent team spawning after rebuild

Resolves #1080


Summary by cubic

Adds a tmux shim and exports a per-surface TMUX env var so tmux-aware tools can split, send keys, and capture panes natively in cmux. Resolves #1080.

  • New Features

    • Resources/bin/tmux: translates core tmux commands to cmux (split-window, send-keys, capture-pane, kill-pane, select-pane, list-panes, display-message, resize-pane, new-window; others passthrough/no-op).
    • Maintains %Nsurface:* mapping and sets per-surface TMUX=<socket>,<pid>,<surface> in GhosttyTerminalView.swift so tools route through the shim.
  • Bug Fixes

    • list-panes -F: returns real %N IDs via reverse map (no synthetic indices); prevents format injection via env-passed format; uses grep -F for literal matching.
    • Reliability/correctness: atomic %N allocation with flock; honors send-keys -l; extracts surface_ref on new-window; display-message -p uses CMUX_SURFACE_ID.
    • Environment precedence: sets TMUX and prepends Resources/bin/ to PATH after env merge so caller vars can’t override the shim.

Written for commit 5f0863b. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Adds a tmux compatibility shim enabling standard tmux commands and tools to work with the multiplexing environment.
    • Supports pane/window/session management with tmux-style commands; prints pane/window IDs for created splits/windows and preserves mappings to underlying surfaces.
    • Handles unsupported commands gracefully to preserve existing workflows.
  • Environment

    • Sets TMUX metadata (including socket and surface identifier) and ensures the shim is prepended to PATH after merging caller-provided environment variables.

Claude Code agent teams detect tmux via the TMUX env var, then use
tmux split-window/send-keys to spawn teammates in split panes. This
adds two pieces to make that work natively in cmux:

1. Resources/bin/tmux — shim script that translates tmux commands to
   cmux equivalents (split-window → new-split, send-keys → send,
   capture-pane → capture-pane, kill-pane → close-surface, etc.)
   with a pane ID mapping layer (%N → surface:N).

2. GhosttyTerminalView.swift — exports TMUX env var in every cmux
   shell so Claude Code detects split-pane mode and routes through
   the shim (which is first on PATH via Resources/bin/).

Resolves manaflow-ai#1080

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 9, 2026

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

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds a Bash tmux-compatible shim at Resources/bin/tmux that maps tmux commands to cmux via a socket with persistent tmux-pane ↔ cmux-surface mapping and file-backed ID generation; updates GhosttyTerminalView environment to expose the cmux socket via TMUX and prepend the shim to PATH.

Changes

Cohort / File(s) Summary
Tmux shim script
Resources/bin/tmux
New Bash shim implementing tmux-compatible command dispatch mapped to cmux CLI: adds cmux_cmd, resolve_pane, store_pane, next_pane_id; persistent pane-id map, atomic file-backed counter, socket/config helpers, and handlers for split-window, send-keys, list/kill/select/resize/capture-pane, session/window ops.
Terminal environment
Sources/GhosttyTerminalView.swift
Compute CMUX_SOCKET_PATH once from SocketControlSettings.socketPath(), set TMUX to "<socketPath>,<pid>,<surface-id-short>", merge env before modifying PATH, and explicitly prepend Resources/bin to PATH so the shim is discoverable without overriding caller-supplied vars.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client / tmux-aware tool
    participant Shim as Tmux Shim\n(Resources/bin/tmux)
    participant Cmux as cmux CLI\n(socket)
    participant Surface as Cmux Surface
    Client->>Shim: issue tmux command
    Shim->>Shim: resolve target / seq-id / store mapping
    Shim->>Cmux: invoke cmux (via socket)
    Cmux->>Surface: operate on surface (create/send/inspect)
    Surface-->>Cmux: respond (ref / ack / data)
    Cmux-->>Shim: return result
    Shim-->>Client: emit tmux-compatible output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop a shim between panes and sky,
I map each id and nudge each key with care,
Sockets hum softly where window-doors lie,
Split and focus — agents find fresh air,
Hop on, little meadow of cmux, I'll share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add tmux shim for native agent teams support' clearly summarizes the main change: introducing a tmux shim to enable AI agent teams.
Description check ✅ Passed The PR description covers the Summary section explaining what changed and why, provides how-it-works details with command mappings, and includes a comprehensive test plan with checklist items marked complete.
Linked Issues check ✅ Passed The PR directly addresses issue #1080 by implementing a tmux shim that enables tmux-detecting agent teams to spawn sub-agents in separate cmux splits/panes, matching the expected behavior of opening each sub-agent in its own window.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the tmux shim implementation and TMUX environment variable setup required to enable agent teams support, with no extraneous modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@greptile-apps
Copy link

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR adds a tmux shim at Resources/bin/tmux that translates a subset of tmux commands into cmux CLI equivalents, and sets the TMUX environment variable in every TerminalSurface so that AI coding agents detect "tmux" and route split/send/capture calls through the shim. The approach is well-scoped and the core split→send→capture→kill flow works as described.

Several issues need to be addressed before merging:

  • Python code injection (list-panes, line 181): The -F format string is interpolated verbatim into a Python triple-quoted string literal, allowing an agent-supplied format containing ''' to break out and execute arbitrary Python code in the shim's process.
  • Broken new-window pane mapping (lines 328–341): new-window allocates a pane ID and echoes it, but never extracts surface_ref from the cmux JSON response and never calls store_pane. Any subsequent command targeting that pane ID will fall back to passing the raw %N string to cmux, silently failing.
  • Race condition in next_pane_id (lines 49–59): The counter file is read, incremented, and written without locking, so concurrent shim invocations can produce duplicate pane IDs.
  • Identical TMUX value for all surfaces (GhosttyTerminalView.swift, line 2399): All surfaces receive the same TMUX string (same app PID, hardcoded window index 0), so agents cannot distinguish which pane they are running in via display-message -p '#{pane_id}' (always returns %0).
  • Unused variable (PANE_MAP_DIR, line 24): Declared but never referenced.

Confidence Score: 2/5

  • Not safe to merge — contains a code injection vulnerability and a functional regression in new-window pane mapping.
  • The Python code injection in list-panes is a real security issue that can be triggered by agent-controlled input. The new-window missing store_pane is a clear functional bug that breaks the pane targeting contract for an entire command variant. The race condition and identical TMUX value are correctness issues that will surface under concurrent or multi-pane agent use.
  • Resources/bin/tmux requires the most attention — specifically the list-panes Python injection, the new-window store_pane omission, and the counter race condition.

Important Files Changed

Filename Overview
Resources/bin/tmux New bash shim translating tmux commands to cmux equivalents; contains a Python code injection vulnerability in list-panes, a functional bug in new-window (missing store_pane), a race condition in the pane ID counter, and an unused variable.
Sources/GhosttyTerminalView.swift Adds TMUX env var export to every TerminalSurface; the value is identical for all surfaces (same PID, hardcoded window index 0), which can cause agent pane identity confusion.

Sequence Diagram

sequenceDiagram
    participant Agent as AI Agent
    participant Shim as Resources/bin/tmux (shim)
    participant Map as PANE_MAP file
    participant Cmux as cmux CLI

    Note over Agent: Detects TMUX env var set by GhosttyTerminalView.swift

    Agent->>Shim: tmux split-window -h -c /path
    Shim->>Cmux: cmux --json new-split right
    Cmux-->>Shim: {"surface_ref": "surface:42"}
    Shim->>Map: store %1 → surface:42
    Shim-->>Agent: %1

    Agent->>Shim: tmux send-keys -t %1 "ls" Enter
    Shim->>Map: resolve_pane(%1)
    Map-->>Shim: surface:42
    Shim->>Cmux: cmux send --surface surface:42 "ls\n"

    Agent->>Shim: tmux capture-pane -t %1 -p
    Shim->>Map: resolve_pane(%1)
    Map-->>Shim: surface:42
    Shim->>Cmux: cmux capture-pane --surface surface:42
    Cmux-->>Agent: pane content

    Agent->>Shim: tmux kill-pane -t %1
    Shim->>Map: resolve_pane(%1)
    Map-->>Shim: surface:42
    Shim->>Cmux: cmux close-surface --surface surface:42
Loading

Last reviewed commit: df2bc8c

Comment on lines +181 to +201
echo "$panes" | python3 -c "
import sys, json
fmt = '''$format'''
try:
data = json.load(sys.stdin)
except:
sys.exit(0)
# Output basic pane info
panes = data if isinstance(data, list) else []
for i, p in enumerate(panes):
line = fmt
pane_id = '%' + str(i)
line = line.replace('#{pane_id}', pane_id)
line = line.replace('#{pane_index}', str(i))
line = line.replace('#{pane_width}', str(p.get('width', 80)))
line = line.replace('#{pane_height}', str(p.get('height', 24)))
line = line.replace('#{pane_active}', '1' if p.get('focused', False) else '0')
line = line.replace('#{pane_current_command}', p.get('command', 'bash'))
line = line.replace('#{pane_pid}', str(p.get('pid', 0)))
print(line)
" 2>/dev/null
Copy link

Choose a reason for hiding this comment

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

Python code injection via unescaped $format

The $format variable is interpolated verbatim into a Python source string delimited by triple-quotes ('''...'). An agent calling tmux list-panes -F with a format string containing ''' will terminate the Python string early, and everything after that point will be parsed as executable Python code.

For example:

tmux list-panes -F "'''; import os; os.system('malicious'); '''"

would break out of the fmt = '''...''' assignment and execute arbitrary Python in the context of the shim's process.

Use sys.argv or pass the format string as a JSON-encoded environment variable instead of string-interpolating it into Python source:

echo "$panes" | TMUX_FORMAT="$format" python3 -c "
import sys, json, os
fmt = os.environ.get('TMUX_FORMAT', '')
try:
    data = json.load(sys.stdin)
except:
    sys.exit(0)
panes = data if isinstance(data, list) else []
for i, p in enumerate(panes):
    line = fmt
    pane_id = '%' + str(i)
    line = line.replace('#{pane_id}', pane_id)
    line = line.replace('#{pane_index}', str(i))
    line = line.replace('#{pane_width}', str(p.get('width', 80)))
    line = line.replace('#{pane_height}', str(p.get('height', 24)))
    line = line.replace('#{pane_active}', '1' if p.get('focused', False) else '0')
    line = line.replace('#{pane_current_command}', p.get('command', 'bash'))
    line = line.replace('#{pane_pid}', str(p.get('pid', 0)))
    print(line)
" 2>/dev/null

Comment on lines +328 to +341
result=$(cmux_cmd --json new-workspace 2>/dev/null)
# After creating workspace, get the new surface
sleep 0.3

if [[ -n "$cwd" ]]; then
cmux_cmd send "cd $(printf '%q' "$cwd")"$'\n' 2>/dev/null
sleep 0.2
fi
if [[ -n "$shell_cmd" ]]; then
cmux_cmd send "${shell_cmd}"$'\n' 2>/dev/null
fi

tmux_id=$(next_pane_id)
echo "$tmux_id"
Copy link

Choose a reason for hiding this comment

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

new-window never stores the pane mapping

result captures the JSON output from cmux --json new-workspace, but surface_ref is never extracted from it, and store_pane is never called. The returned $tmux_id is allocated but not registered in PANE_MAP, so any subsequent send-keys -t, capture-pane -t, kill-pane -t, etc. targeting that ID will fail to find a mapping in resolve_pane and will pass the raw %N string as a surface reference to cmux (which will error silently).

Compare to split-window, which correctly does:

surface_ref=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('surface_ref',''))" 2>/dev/null || echo "")
#
store_pane "$tmux_id" "$surface_ref"

The same extraction and store_pane call is needed here. Also, the send commands in new-window (lines 333/337) are missing --surface "$surface_ref", so they may target the wrong pane even once the mapping is fixed.

Comment on lines +49 to +59
# Allocate a tmux-style %N pane ID.
next_pane_id() {
local counter_file="${PANE_MAP}.counter"
local n=0
if [[ -f "$counter_file" ]]; then
n=$(cat "$counter_file" 2>/dev/null || echo 0)
fi
n=$((n + 1))
echo "$n" > "$counter_file"
echo "%${n}"
}
Copy link

Choose a reason for hiding this comment

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

Race condition in next_pane_id counter

The read → increment → write sequence on the counter file is not atomic. Two concurrent shim invocations (e.g. an agent spawning several splits rapidly) can both read the same n, both increment to the same value, and both write it back — producing duplicate %N IDs. A duplicate ID causes resolve_pane to tail -1 the last entry for that ID, silently redirecting one pane's commands to another surface.

A simple mitigation is to use flock for mutual exclusion:

next_pane_id() {
    local counter_file="${PANE_MAP}.counter"
    local n
    n=$(flock -x "$counter_file" bash -c '
        n=$(cat "$1" 2>/dev/null) || n=0
        n=$((n + 1))
        echo "$n" > "$1"
        echo "$n"
    ' _ "$counter_file")
    echo "%${n}"
}

SOCKET="${CMUX_SOCKET_PATH:-/tmp/cmux.sock}"

# Pane target mapping file — maps tmux %N pane IDs to cmux surface refs.
PANE_MAP_DIR="${TMPDIR:-/tmp}/cmux-tmux-shim-$$"
Copy link

Choose a reason for hiding this comment

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

PANE_MAP_DIR defined but never used

PANE_MAP_DIR is assigned on this line but referenced nowhere else in the script. It appears to be a leftover from an earlier design (the $$-suffixed per-process directory approach). Consider removing it to avoid confusion.

Suggested change
PANE_MAP_DIR="${TMPDIR:-/tmp}/cmux-tmux-shim-$$"

(Remove the line entirely.)

// Expose TMUX env var so tools that detect tmux (e.g. Claude Code agent
// teams split-pane mode) route through the cmux tmux shim at Resources/bin/tmux.
// Format mirrors real tmux: <socket-path>,<pid>,<window-index>
env["TMUX"] = "\(socketPath),\(ProcessInfo.processInfo.processIdentifier),0"
Copy link

Choose a reason for hiding this comment

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

All surfaces share the same TMUX value

Every TerminalSurface receives the exact same TMUX string (socketPath + "," + appPID + ",0"), because ProcessInfo.processInfo.processIdentifier is the cmux app's PID (constant) and the window index is hardcoded to 0.

Real tmux sets TMUX to a per-client value that encodes which session the pane belongs to. When an agent calls tmux display-message -p '#{pane_id}' to discover its own pane, the shim returns %0 unconditionally (line 255). If two agent panes try to coordinate using their respective TMUX values to discover each other, they will both see the same TMUX string and the same pane_id, leading to one agent's commands silently targeting the wrong split.

Consider incorporating the surface UUID into the TMUX value (e.g. as the window index field, or an extra field) so that each surface has a distinct identifier agents can use to determine which pane they are running in.

Copy link

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

Choose a reason for hiding this comment

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

No issues found across 2 files


Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
Resources/bin/tmux (4)

33-40: Potential regex metacharacter injection in resolve_pane.

If $target contains regex metacharacters (e.g., ., *, +), the grep pattern ^${target}= may match unintended lines. Since pane IDs are %N format, this is low risk in practice, but defensive quoting would be safer.

🛡️ Proposed fix using grep -F for literal matching
 resolve_pane() {
     local target="$1"
-    if [[ -f "$PANE_MAP" ]] && grep -q "^${target}=" "$PANE_MAP" 2>/dev/null; then
-        grep "^${target}=" "$PANE_MAP" | tail -1 | cut -d= -f2
+    if [[ -f "$PANE_MAP" ]] && grep -Fq "${target}=" "$PANE_MAP" 2>/dev/null; then
+        grep -F "${target}=" "$PANE_MAP" | grep "^${target}=" | tail -1 | cut -d= -f2
     else
         echo "$target"
     fi
 }

Alternatively, use awk for exact matching:

resolve_pane() {
    local target="$1"
    if [[ -f "$PANE_MAP" ]]; then
        awk -F= -v t="$target" '$1 == t { v=$2 } END { print (v ? v : t) }' "$PANE_MAP"
    else
        echo "$target"
    fi
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` around lines 33 - 40, The resolve_pane function uses grep
with an unquoted regex pattern (^${target}=) which can misbehave if target
contains regex metacharacters; update resolve_pane to perform a literal key
match instead (e.g., use grep -F -x pattern or switch to awk with -F= and
compare $1 == t) so that the lookup uses exact matching against PANE_MAP and
falls back to echoing the original target when no match is found; reference the
resolve_pane function and the pattern "^${target}=" in your change.

291-291: Unused variable scrollback.

The scrollback variable is defined but never used. Consider removing it or implementing scrollback support if cmux supports it.

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

In `@Resources/bin/tmux` at line 291, The config sets an unused variable
"scrollback" (seen alongside "target" and "print_mode") which should be removed
or wired up to the terminal handling code; either delete the "scrollback=false"
token from the tmux invocation/config to remove the unused variable, or
implement scrollback support by passing its value into the cmux/terminal setup
logic (where target/print_mode are handled) and using it to enable/disable
scrollback behavior.

24-24: Unused variable PANE_MAP_DIR.

This variable is defined but never referenced anywhere in the script. Consider removing it or using it for its apparent intended purpose.

🧹 Proposed fix
-PANE_MAP_DIR="${TMPDIR:-/tmp}/cmux-tmux-shim-$$"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` at line 24, The variable PANE_MAP_DIR is declared but
never used; either remove the declaration or implement its intended use:
initialize and export PANE_MAP_DIR (currently set to
"${TMPDIR:-/tmp}/cmux-tmux-shim-$$"), create the directory (mkdir -p), use it
where pane mapping files are written/read, and add cleanup (trap) logic to
remove it on exit; if you choose to remove it, simply delete the
PANE_MAP_DIR="${TMPDIR:-/tmp}/cmux-tmux-shim-$$" line and any related dead
references.

106-114: Sleep-based synchronization is fragile.

The hardcoded sleep 0.3 and sleep 0.2 delays assume the split is ready within that time. On slow systems or under load, the surface may not be ready, causing cd or command execution to fail silently.

Consider whether cmux provides a synchronous mode or a ready-signal that could replace these sleeps, or document this as a known limitation.

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

In `@Resources/bin/tmux` around lines 106 - 114, The hardcoded sleeps before
sending to the new surface (cmux_cmd send --surface "$surface_ref" ...) are
fragile; replace them with a readiness check loop that polls the surface state
(e.g., call a cmux readiness/status command or probe with a harmless no-op send)
until ready or a short timeout, then send the cd and shell_cmd payloads; if cmux
has no readiness API, implement a retry-with-backoff loop around cmux_cmd send
for the cd and the shell_cmd with a clear timeout and error logging, and if
neither approach is possible, add a comment documenting this limitation. Ensure
you update the code paths that reference surface_ref, the cd send, and the
shell_cmd send to use the new polling/retry logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Resources/bin/tmux`:
- Around line 181-201: The embedded Python snippet interpolates the shell
variable $format directly into the triple-quoted string (assigned to fmt),
allowing a malicious format containing ''' to break out and inject code; change
the invocation so the shell passes the format safely (e.g., export FORMAT or
pass it on stdin) and have the Python snippet read it from os.environ['FORMAT']
or from a separate JSON/stdin field instead of embedding '''$format''', update
references to fmt in the Python block to use the environment/input value, and
ensure you keep existing variables panes/data handling and exit behavior intact.
- Around line 50-59: The next_pane_id function has a race on reading/updating
"${PANE_MAP}.counter"; replace the naive read-increment-write with an exclusive
file lock around the whole sequence so concurrent shim invocations cannot
allocate the same ID. Use flock (or bash's exec + flock FD) to obtain an
exclusive lock on the counter file (create it if missing), then read the value,
increment, write it back, and release the lock; keep the function name
next_pane_id and the counter filename "${PANE_MAP}.counter" so callers remain
unchanged.
- Around line 124-130: The script parses -l into the variable literal but never
uses it; update the key translation/processing block (the code that currently
translates named keys like Enter/Space into tmux key codes) to check the literal
flag and skip translation when literal=true — i.e., if literal is true,
append/emit the provided key strings verbatim (without mapping) into the keys
array/argument construction, otherwise run the existing translation logic;
reference the existing variable literal and the key translation loop/logic to
locate where to add the conditional.
- Around line 328-338: The sends after creating a workspace are missing the
--surface flag so they may target the wrong surface; capture the new surface
reference from result (the output of cmux_cmd --json new-workspace stored in
result), extract the surface id/ref the same way split-window uses surface_ref,
and pass that ref to subsequent cmux_cmd send calls for both the cwd and
shell_cmd branches (i.e., use --surface "$surface_ref" when calling cmux_cmd
send after new-workspace) so commands go to the newly created workspace.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 2393-2399: The TMUX shim environment keys are being set before
merging in additionalEnvironment so callers can override/disable the shim; move
the assignments that set env["CMUX_SOCKET_PATH"], env["TMUX"] (built from
SocketControlSettings.socketPath() and
ProcessInfo.processInfo.processIdentifier) and the PATH prepend so they occur
after merging additionalEnvironment, or ensure the merge preserves these
internal values (e.g., merge with a policy that keeps existing env values);
apply the same change for the other occurrences around the code that references
these keys (the blocks around lines handling PATH and the TMUX/CMUX_SOCKET_PATH
assignments).

---

Nitpick comments:
In `@Resources/bin/tmux`:
- Around line 33-40: The resolve_pane function uses grep with an unquoted regex
pattern (^${target}=) which can misbehave if target contains regex
metacharacters; update resolve_pane to perform a literal key match instead
(e.g., use grep -F -x pattern or switch to awk with -F= and compare $1 == t) so
that the lookup uses exact matching against PANE_MAP and falls back to echoing
the original target when no match is found; reference the resolve_pane function
and the pattern "^${target}=" in your change.
- Line 291: The config sets an unused variable "scrollback" (seen alongside
"target" and "print_mode") which should be removed or wired up to the terminal
handling code; either delete the "scrollback=false" token from the tmux
invocation/config to remove the unused variable, or implement scrollback support
by passing its value into the cmux/terminal setup logic (where target/print_mode
are handled) and using it to enable/disable scrollback behavior.
- Line 24: The variable PANE_MAP_DIR is declared but never used; either remove
the declaration or implement its intended use: initialize and export
PANE_MAP_DIR (currently set to "${TMPDIR:-/tmp}/cmux-tmux-shim-$$"), create the
directory (mkdir -p), use it where pane mapping files are written/read, and add
cleanup (trap) logic to remove it on exit; if you choose to remove it, simply
delete the PANE_MAP_DIR="${TMPDIR:-/tmp}/cmux-tmux-shim-$$" line and any related
dead references.
- Around line 106-114: The hardcoded sleeps before sending to the new surface
(cmux_cmd send --surface "$surface_ref" ...) are fragile; replace them with a
readiness check loop that polls the surface state (e.g., call a cmux
readiness/status command or probe with a harmless no-op send) until ready or a
short timeout, then send the cd and shell_cmd payloads; if cmux has no readiness
API, implement a retry-with-backoff loop around cmux_cmd send for the cd and the
shell_cmd with a clear timeout and error logging, and if neither approach is
possible, add a comment documenting this limitation. Ensure you update the code
paths that reference surface_ref, the cd send, and the shell_cmd send to use the
new polling/retry logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90042a16-003b-41e5-8b95-bb62eaee0d69

📥 Commits

Reviewing files that changed from the base of the PR and between a636104 and df2bc8c.

📒 Files selected for processing (2)
  • Resources/bin/tmux
  • Sources/GhosttyTerminalView.swift

adisinghstudent and others added 2 commits March 9, 2026 16:52
- Fix Python code injection: pass format string via env var instead of
  triple-quote interpolation
- Fix new-window: extract surface_ref and store pane mapping
- Fix race condition: use flock for atomic pane ID allocation
- Fix send-keys: honor -l literal flag with early continue
- Fix resolve_pane: use grep -F for literal matching (no regex metachar issues)
- Fix display-message: use CMUX_SURFACE_ID for pane_id query
- Remove unused PANE_MAP_DIR variable
- Replace bare except with specific exception types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use surface UUID prefix in TMUX value so each surface gets a unique
  identifier (e.g. /tmp/cmux.sock,1234,A1B2C3D4 instead of ...,0)
- Move TMUX and PATH assignments after additionalEnvironment merge so
  they cannot be accidentally overridden by caller-supplied env vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
Resources/bin/tmux (3)

74-82: Unused variable: pane_format is parsed but never used.

The -F flag is accepted and stored but the format string is never applied to the output. The shim always prints just the pane ID regardless of the format requested.

♻️ Suggested cleanup

Either remove the variable if format support isn't planned:

     direction="right"
     cwd=""
     shell_cmd=""
-    pane_format=""
 
     while [[ $# -gt 0 ]]; do
         case "$1" in
             -h) direction="right"; shift ;;
             -v) direction="down"; shift ;;
             -c) cwd="$2"; shift 2 ;;
             -P) shift ;;  # print pane info (we always do)
-            -F) pane_format="$2"; shift 2 ;;  # format string
+            -F) shift 2 ;;  # format string (ignored, always print pane ID)

Or implement basic format support in a follow-up.

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

In `@Resources/bin/tmux` around lines 74 - 82, The script parses a -F flag into
the variable pane_format but never uses it; update the code handling the -F flag
(the while [[ $# -gt 0 ]] loop and subsequent output path that prints the pane
ID) to either remove the -F parsing if format support is not intended, or
implement basic format support by applying pane_format when printing pane info
(use the existing pane id variable and substitute it into the user-supplied
format string before output). Ensure you reference and update the pane_format
variable and the output/print logic so the -F option actually affects the
script's output.

293-302: Unused variable: scrollback is assigned but never read.

The variable is set to false but never checked. The -S and -E flags (scrollback range) are already handled by shifting past them.

♻️ Suggested cleanup
 capture-pane)
-    target="" print_mode=false scrollback=false
+    target="" print_mode=false
     while [[ $# -gt 0 ]]; do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` around lines 293 - 302, Remove the unused variable
assignment by deleting the declaration "scrollback=false" and any related unused
references to "scrollback" in this script (the while/case that handles flags
including "-S" and "-E" already shifts past those args), so only actually-used
flags like "target" and "print_mode" remain; ensure no other code later expects
a "scrollback" variable before committing.

316-321: Unused variable: pane_format is parsed but never used.

Same issue as split-window - the -F format flag is captured but the output always just prints the pane ID.

♻️ Suggested cleanup
 new-window)
-    cwd="" shell_cmd="" pane_format=""
+    cwd="" shell_cmd=""
     while [[ $# -gt 0 ]]; do
         case "$1" in
             -c) cwd="$2"; shift 2 ;;
             -P) shift ;;
-            -F) pane_format="$2"; shift 2 ;;
+            -F) shift 2 ;;  # format string (ignored)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` around lines 316 - 321, The loop currently parses the -F
flag into the pane_format variable but never uses it, so either remove the -F
case from the argument parsing or apply pane_format when producing output;
update the while/case that sets pane_format to either drop handling of -F (and
remove the pane_format variable) or pass pane_format into the code path that
prints the result (the same place that currently prints the pane ID) so the
final output respects the requested format; reference the pane_format variable
and the argument-parsing while/case in the tmux script when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Resources/bin/tmux`:
- Around line 193-203: The loop is fabricating pane IDs ("%0", "%1", ...) that
don't match the real IDs stored in PANE_MAP (and assigned by next_pane_id() when
split-window is called), causing resolve_pane to fail; update the replacement
for '#{pane_id}' inside the loop to use the actual pane identifier (e.g.,
p.get('id') or a reverse lookup into PANE_MAP to map the pane's stored internal
id/index to the real tmux id assigned at creation) instead of '%' + str(i), and
keep other replacements the same so printed pane lines use the real IDs that
resolve_pane and split-window return.

---

Nitpick comments:
In `@Resources/bin/tmux`:
- Around line 74-82: The script parses a -F flag into the variable pane_format
but never uses it; update the code handling the -F flag (the while [[ $# -gt 0
]] loop and subsequent output path that prints the pane ID) to either remove the
-F parsing if format support is not intended, or implement basic format support
by applying pane_format when printing pane info (use the existing pane id
variable and substitute it into the user-supplied format string before output).
Ensure you reference and update the pane_format variable and the output/print
logic so the -F option actually affects the script's output.
- Around line 293-302: Remove the unused variable assignment by deleting the
declaration "scrollback=false" and any related unused references to "scrollback"
in this script (the while/case that handles flags including "-S" and "-E"
already shifts past those args), so only actually-used flags like "target" and
"print_mode" remain; ensure no other code later expects a "scrollback" variable
before committing.
- Around line 316-321: The loop currently parses the -F flag into the
pane_format variable but never uses it, so either remove the -F case from the
argument parsing or apply pane_format when producing output; update the
while/case that sets pane_format to either drop handling of -F (and remove the
pane_format variable) or pass pane_format into the code path that prints the
result (the same place that currently prints the pane ID) so the final output
respects the requested format; reference the pane_format variable and the
argument-parsing while/case in the tmux script when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d907b066-7a30-4ded-a8b9-71773ae78247

📥 Commits

Reviewing files that changed from the base of the PR and between df2bc8c and bd8fdf6.

📒 Files selected for processing (1)
  • Resources/bin/tmux

list-panes was generating synthetic %0,%1,%2 IDs that didn't match
the counter-based IDs returned by split-window. Now does a reverse
lookup from the pane map (cmux surface ref → tmux %N ID) so agents
can use IDs from list-panes with send-keys/kill-pane.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Resources/bin/tmux`:
- Around line 268-275: The current print_mode branch short-circuits token
matches in the case on "$message" and returns single canned values, which breaks
combined formats and returns the raw %${CMUX_SURFACE_ID:-0} instead of the
mapped pane id; change the logic in the print_mode handling to perform format
substitution on "$message" (e.g. replace occurrences of "#{pane_id}",
"#{session_name}", "#{window_index}", "#{pane_index}" rather than matching the
whole string) by doing ordered string replacements: lookup PANE_MAP for the
actual shim pane id and substitute it for "#{pane_id}" (falling back to
%${CMUX_SURFACE_ID:-0} only if no mapping), substitute "cmux" for
"#{session_name}", "0" for "#{window_index}" and "#{pane_index}", and then echo
the fully substituted string so combined formats like
"#{session_name}:#{window_index}.#{pane_index}" are preserved.
- Around line 283-301: The current option parsing for resize-pane ignores -x/-y
but leaves direction and amount set (defaulting to -R and 5), causing unintended
relative resizes; update the parsing logic that handles -x and -y to mark an
"absolute_size" error (e.g., set a flag like absolute_size=true) instead of
shifting and continuing, and then before calling resolve_pane/ cmux_cmd
resize-pane check that flag and bail out with a clear error message and non-zero
exit (or return) indicating absolute sizing is unsupported; ensure you reference
the same symbols in the change (the case branches for -x and -y, the variables
target/direction/amount, resolve_pane and the cmux_cmd resize-pane invocation)
so the code never falls through to perform a relative resize when -x/-y were
provided.
- Around line 185-217: The environment variables TMUX_SHIM_FORMAT and
TMUX_SHIM_PANE_MAP are currently being set for echo instead of the python3
process, so fmt and pane_map_path are empty; fix this by ensuring those vars are
exported/placed in the environment of the python3 invocation (so python3 sees
TMUX_SHIM_FORMAT and TMUX_SHIM_PANE_MAP and can populate fmt and pane_map_path).
Locate the shell line that runs python3 (the invocation that pipes "$panes" into
python3 -c "...") and move the assignments so they apply to the python3 process
(or use env/export before calling python3), ensuring the python code using fmt
and pane_map_path receives the correct values.
- Around line 94-95: The shim currently calls python3 to parse JSON (e.g., the
surface_ref extraction using: surface_ref=$(echo "$result" | python3 -c "import
sys,json; print(json.load(sys.stdin).get('surface_ref',''))" ...)), creating a
hard runtime dependency that fails silently; add an explicit runtime check for a
JSON parser at startup (e.g., test -x "$(command -v python3)" or "$(command -v
jq)"), and if none is found print a clear error message about the missing
dependency and exit, or refactor the three parsing sites (split-window,
list-panes format handling, new-window) to use jq when available (fall back to
python3 only if present) so the code no longer assumes python3 is on PATH and
users get actionable errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12a5b1f8-5e05-4d1e-82b9-86bbacc7856f

📥 Commits

Reviewing files that changed from the base of the PR and between 79e3e25 and 5f0863b.

📒 Files selected for processing (1)
  • Resources/bin/tmux

Comment on lines +94 to +95
surface_ref=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('surface_ref',''))" 2>/dev/null || echo "")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "python3 => $(command -v python3 || echo MISSING)"
sw_vers

# Run this on the same clean macOS image / DMG install target as the PR.
# If python3 is missing, split-window/new-window/list-panes will fail here.

Repository: manaflow-ai/cmux

Length of output: 152


🏁 Script executed:

# First, let's look at the actual file to understand the python3 usage
fd -t f "tmux" Resources/bin/ 2>/dev/null || find Resources/bin -name "tmux" -type f

Repository: manaflow-ai/cmux

Length of output: 78


🏁 Script executed:

# Check project structure and distribution method
ls -la | head -20
cat README.md 2>/dev/null | head -50
cat setup.py 2>/dev/null || cat pyproject.toml 2>/dev/null || cat package.json 2>/dev/null

Repository: manaflow-ai/cmux

Length of output: 3550


🏁 Script executed:

# Look for distribution/packaging configuration
fd -t f "(dmg|app|setup|pyproject|package)" . --max-depth 2 2>/dev/null | grep -v node_modules

Repository: manaflow-ai/cmux

Length of output: 680


🏁 Script executed:

# Read the entire tmux file to understand the python3 usage
cat -n Resources/bin/tmux | head -150

Repository: manaflow-ai/cmux

Length of output: 5471


🏁 Script executed:

# Look at the specific lines mentioned: 94-95, 185-217, 344-345
cat -n Resources/bin/tmux | sed -n '90,100p'
cat -n Resources/bin/tmux | sed -n '180,220p'
cat -n Resources/bin/tmux | sed -n '340,350p'

Repository: manaflow-ai/cmux

Length of output: 2876


🏁 Script executed:

# Check how this shim is invoked - look for wrapper/setup scripts
rg -l "Resources/bin/tmux" . --type sh
cat Resources/bin/tmux | wc -l

Repository: manaflow-ai/cmux

Length of output: 63


🏁 Script executed:

# Check if there are error handling paths or fallbacks for the python3 invocations
cat -n Resources/bin/tmux | sed -n '90,120p'
cat -n Resources/bin/tmux | sed -n '340,365p'

# Look for how the JSON responses are actually structured
rg "new-split|new-workspace|list-panes" Resources/bin/tmux -A 3 -B 1

Repository: manaflow-ai/cmux

Length of output: 3465


🏁 Script executed:

# Check macOS specifics - look for bundled python or requirement declarations
fd -t f "requirements\|setup\|build\|script" . --max-depth 3 2>/dev/null | head -20
find . -name "*.sh" -path "*/scripts/*" -exec grep -l "python\|interpreter" {} \;

Repository: manaflow-ai/cmux

Length of output: 176


🏁 Script executed:

# Verify actual error behavior - trace what happens when python3 parsing fails
cat -n Resources/bin/tmux | sed -n '93,99p'

Repository: manaflow-ai/cmux

Length of output: 414


🏁 Script executed:

# Check Python3 availability claim - look at macOS-specific docs or setup scripts
cat scripts/smoke-test-ci.sh | head -50
cat scripts/run-tests-v1.sh | head -30

# Check if there's any python bundling or interpreter management
rg "python" Resources/ scripts/ --type sh -A 2 -B 2 | head -60

Repository: manaflow-ai/cmux

Length of output: 4756


🏁 Script executed:

# Verify what the cmux --json outputs actually look like
# Check if there are integration tests showing expected JSON format
find . -name "*.py" -type f -exec grep -l "surface_ref\|new-split\|new-workspace" {} \;

Repository: manaflow-ai/cmux

Length of output: 559


🏁 Script executed:

# Check if macOS ships with python3 by default - search for any docs/discussions
rg -i "python|system.python|brew" README.md CONTRIBUTING.md docs/ 2>/dev/null || echo "No references found"

# Look at actual cmux setup/requirements to see if python3 is expected
cat package.json | jq .
cat Package.swift 2>/dev/null | head -30 || echo "No Package.swift"

Repository: manaflow-ai/cmux

Length of output: 1538


🏁 Script executed:

# Verify the actual behavior: does the shim gracefully handle missing python3?
# Look at what happens when python3 fails in line 94
cat -n Resources/bin/tmux | sed -n '92,100p'
# The pattern: surface_ref=$(... python3 ... 2>/dev/null || echo "")
# If python3 is missing: 'command not found' on stderr, then || echo "" runs
# Result: surface_ref=""
# Then: if [[ -z "$surface_ref" ]] → exit 1

# Check: is there any way the shim guarantees python3 exists?
rg "python|PATH|interpreter" Resources/bin/tmux

Repository: manaflow-ai/cmux

Length of output: 945


🏁 Script executed:

# Verify the error messaging when python3 fails
cat -n Resources/bin/tmux | sed -n '93,99p'
cat -n Resources/bin/tmux | sed -n '343,352p'

Repository: manaflow-ai/cmux

Length of output: 1023


Avoid a hard runtime dependency on python3 in this shim.

The shim requires python3 to parse JSON responses in three critical agent-spawning paths: split-window (line 94), list-panes with format strings (lines 185–217), and new-window (line 344). While error handling exists (returning "Error: failed to create split" when parsing fails), it masks the real failure—missing python3 in PATH. On a clean macOS install where python3 is not guaranteed, agent team spawning will fail with a generic error message instead of clearly indicating the missing dependency.

Consider either bundling a Python interpreter with the DMG or removing the JSON parsing dependency by using alternative tools like jq or native macOS utilities.

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

In `@Resources/bin/tmux` around lines 94 - 95, The shim currently calls python3 to
parse JSON (e.g., the surface_ref extraction using: surface_ref=$(echo "$result"
| python3 -c "import sys,json;
print(json.load(sys.stdin).get('surface_ref',''))" ...)), creating a hard
runtime dependency that fails silently; add an explicit runtime check for a JSON
parser at startup (e.g., test -x "$(command -v python3)" or "$(command -v jq)"),
and if none is found print a clear error message about the missing dependency
and exit, or refactor the three parsing sites (split-window, list-panes format
handling, new-window) to use jq when available (fall back to python3 only if
present) so the code no longer assumes python3 is on PATH and users get
actionable errors.

Comment on lines +185 to +217
TMUX_SHIM_FORMAT="$format" TMUX_SHIM_PANE_MAP="$PANE_MAP" echo "$panes" | python3 -c "
import sys, json, os
fmt = os.environ.get('TMUX_SHIM_FORMAT', '')
pane_map_path = os.environ.get('TMUX_SHIM_PANE_MAP', '')
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
sys.exit(0)
# Build reverse map: cmux surface ref -> tmux pane ID
rev = {}
if pane_map_path:
try:
for line in open(pane_map_path):
line = line.strip()
if '=' in line:
tid, sref = line.split('=', 1)
rev[sref] = tid
except FileNotFoundError:
pass
panes = data if isinstance(data, list) else []
for i, p in enumerate(panes):
line = fmt
sref = p.get('surface_ref', p.get('id', ''))
pane_id = rev.get(sref, '%' + str(i))
line = line.replace('#{pane_id}', pane_id)
line = line.replace('#{pane_index}', str(i))
line = line.replace('#{pane_width}', str(p.get('width', 80)))
line = line.replace('#{pane_height}', str(p.get('height', 24)))
line = line.replace('#{pane_active}', '1' if p.get('focused', False) else '0')
line = line.replace('#{pane_current_command}', p.get('command', 'bash'))
line = line.replace('#{pane_pid}', str(p.get('pid', 0)))
print(line)
" 2>/dev/null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

TMUX_SHIM_FORMAT="pane" echo '[]' | python3 -c 'import os; print(repr(os.environ.get("TMUX_SHIM_FORMAT")))'

# Expected: None / empty output, because the variable is scoped to `echo`, not `python3`.

Repository: manaflow-ai/cmux

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Check if the file exists and verify the code around lines 185-217
if [ -f "Resources/bin/tmux" ]; then
    echo "=== File found, showing lines 180-220 ==="
    sed -n '180,220p' "Resources/bin/tmux" | cat -n
else
    echo "File not found at Resources/bin/tmux"
    find . -name "tmux" -type f 2>/dev/null | head -20
fi

Repository: manaflow-ai/cmux

Length of output: 1956


Pass the format env vars to python3, not to echo.

TMUX_SHIM_FORMAT=... TMUX_SHIM_PANE_MAP=... echo "$panes" | python3 ... only sets those variables for echo. python3 sees empty values (None), causing fmt and pane_map_path to be empty strings. This breaks formatting entirely and loses the reverse lookup functionality, resulting in blank output.

Suggested fix
-        TMUX_SHIM_FORMAT="$format" TMUX_SHIM_PANE_MAP="$PANE_MAP" echo "$panes" | python3 -c "
+        TMUX_SHIM_FORMAT="$format" TMUX_SHIM_PANE_MAP="$PANE_MAP" python3 -c "
 import sys, json, os
 fmt = os.environ.get('TMUX_SHIM_FORMAT', '')
 pane_map_path = os.environ.get('TMUX_SHIM_PANE_MAP', '')
 try:
     data = json.load(sys.stdin)
@@
-\" 2>/dev/null
+\" 2>/dev/null <<<\"$panes\"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` around lines 185 - 217, The environment variables
TMUX_SHIM_FORMAT and TMUX_SHIM_PANE_MAP are currently being set for echo instead
of the python3 process, so fmt and pane_map_path are empty; fix this by ensuring
those vars are exported/placed in the environment of the python3 invocation (so
python3 sees TMUX_SHIM_FORMAT and TMUX_SHIM_PANE_MAP and can populate fmt and
pane_map_path). Locate the shell line that runs python3 (the invocation that
pipes "$panes" into python3 -c "...") and move the assignments so they apply to
the python3 process (or use env/export before calling python3), ensuring the
python code using fmt and pane_map_path receives the correct values.

Comment on lines +268 to +275
if [[ "$print_mode" == true ]]; then
case "$message" in
*pane_id*) echo "%${CMUX_SURFACE_ID:-0}" ;;
*session_name*) echo "cmux" ;;
*window_index*) echo "0" ;;
*pane_index*) echo "0" ;;
*) echo "$message" ;;
esac
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make display-message -p do format substitution, not token short-circuiting.

The current case returns a single canned value based on the first matching placeholder. Combined formats like #{session_name}:#{window_index}.#{pane_index} lose surrounding text, and #{pane_id} returns %${CMUX_SURFACE_ID:-0} instead of the shim pane id stored in PANE_MAP. Callers that fetch the current pane id and feed it back into send-keys/kill-pane can miss the mapping.

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

In `@Resources/bin/tmux` around lines 268 - 275, The current print_mode branch
short-circuits token matches in the case on "$message" and returns single canned
values, which breaks combined formats and returns the raw %${CMUX_SURFACE_ID:-0}
instead of the mapped pane id; change the logic in the print_mode handling to
perform format substitution on "$message" (e.g. replace occurrences of
"#{pane_id}", "#{session_name}", "#{window_index}", "#{pane_index}" rather than
matching the whole string) by doing ordered string replacements: lookup PANE_MAP
for the actual shim pane id and substitute it for "#{pane_id}" (falling back to
%${CMUX_SURFACE_ID:-0} only if no mapping), substitute "cmux" for
"#{session_name}", "0" for "#{window_index}" and "#{pane_index}", and then echo
the fully substituted string so combined formats like
"#{session_name}:#{window_index}.#{pane_index}" are preserved.

Comment on lines +283 to +301
target="" direction="-R" amount="5"
while [[ $# -gt 0 ]]; do
case "$1" in
-t) target="$2"; shift 2 ;;
-x) shift 2 ;; # absolute width (not directly supported)
-y) shift 2 ;; # absolute height (not directly supported)
-L) direction="-L"; shift ;;
-R) direction="-R"; shift ;;
-U) direction="-U"; shift ;;
-D) direction="-D"; shift ;;
[0-9]*) amount="$1"; shift ;;
*) shift ;;
esac
done

if [[ -n "$target" ]]; then
surface=$(resolve_pane "$target")
cmux_cmd resize-pane --pane "$surface" "$direction" --amount "$amount" 2>/dev/null
fi
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

resize-pane -x/-y currently performs the wrong resize.

This branch ignores -x/-y, but direction still defaults to -R and amount to 5. So tmux resize-pane -t %1 -x 120 becomes a relative “grow right by 5”, which is worse than a no-op. If absolute sizing is unsupported, please bail out explicitly instead of mutating pane size incorrectly.

Minimal guard
-resize-pane)
-    target="" direction="-R" amount="5"
+resize-pane)
+    target="" direction="" amount="5" absolute_resize=false
@@
-            -x) shift 2 ;;  # absolute width (not directly supported)
-            -y) shift 2 ;;  # absolute height (not directly supported)
+            -x|-y) absolute_resize=true; shift 2 ;;
@@
-    if [[ -n "$target" ]]; then
+    if [[ "$absolute_resize" == true ]]; then
+        exit 0
+    fi
+    if [[ -n "$target" && -n "$direction" ]]; then
         surface=$(resolve_pane "$target")
         cmux_cmd resize-pane --pane "$surface" "$direction" --amount "$amount" 2>/dev/null
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
target="" direction="-R" amount="5"
while [[ $# -gt 0 ]]; do
case "$1" in
-t) target="$2"; shift 2 ;;
-x) shift 2 ;; # absolute width (not directly supported)
-y) shift 2 ;; # absolute height (not directly supported)
-L) direction="-L"; shift ;;
-R) direction="-R"; shift ;;
-U) direction="-U"; shift ;;
-D) direction="-D"; shift ;;
[0-9]*) amount="$1"; shift ;;
*) shift ;;
esac
done
if [[ -n "$target" ]]; then
surface=$(resolve_pane "$target")
cmux_cmd resize-pane --pane "$surface" "$direction" --amount "$amount" 2>/dev/null
fi
target="" direction="" amount="5" absolute_resize=false
while [[ $# -gt 0 ]]; do
case "$1" in
-t) target="$2"; shift 2 ;;
-x|-y) absolute_resize=true; shift 2 ;;
-L) direction="-L"; shift ;;
-R) direction="-R"; shift ;;
-U) direction="-U"; shift ;;
-D) direction="-D"; shift ;;
[0-9]*) amount="$1"; shift ;;
*) shift ;;
esac
done
if [[ "$absolute_resize" == true ]]; then
exit 0
fi
if [[ -n "$target" && -n "$direction" ]]; then
surface=$(resolve_pane "$target")
cmux_cmd resize-pane --pane "$surface" "$direction" --amount "$amount" 2>/dev/null
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/tmux` around lines 283 - 301, The current option parsing for
resize-pane ignores -x/-y but leaves direction and amount set (defaulting to -R
and 5), causing unintended relative resizes; update the parsing logic that
handles -x and -y to mark an "absolute_size" error (e.g., set a flag like
absolute_size=true) instead of shifting and continuing, and then before calling
resolve_pane/ cmux_cmd resize-pane check that flag and bail out with a clear
error message and non-zero exit (or return) indicating absolute sizing is
unsupported; ensure you reference the same symbols in the change (the case
branches for -x and -y, the variables target/direction/amount, resolve_pane and
the cmux_cmd resize-pane invocation) so the code never falls through to perform
a relative resize when -x/-y were provided.

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.

No claude agent teams windows

1 participant