Skip to content

feat: add tmux-bridge script for cross-pane agent communication - #1329

Merged
shunkakinoki merged 2 commits into
mainfrom
feat/add-tmux-bridge
Apr 5, 2026
Merged

feat: add tmux-bridge script for cross-pane agent communication#1329
shunkakinoki merged 2 commits into
mainfrom
feat/add-tmux-bridge

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add tmux-bridge CLI from ShawnPana/smux to local-scripts
  • Enables AI agents (Claude Code, Codex, etc.) to read, type, and send keys to other tmux panes
  • Deployed to ~/.local/scripts/tmux-bridge via home-manager

Test plan

  • Run home-manager switch and verify tmux-bridge is available in PATH
  • Run tmux-bridge list inside a tmux session
  • Run tmux-bridge doctor to verify connectivity

🤖 Generated with Claude Code


Summary by cubic

Add tmux-bridge CLI to ~/.local/scripts via home-manager for cross-pane communication in tmux. Tools can read panes, type text, send keys, and use label-based targeting with a read-before-act guard.

  • New Features

    • Installs tmux-bridge and exposes commands: list, read, type, keys, message, name, resolve, id, doctor.
    • Adds a read-before-act guard, pane labels (@name), sender-aware message headers, and auto tmux socket detection with a doctor diagnostic.
  • Migration

    • Run home-manager switch; confirm tmux-bridge is in PATH.
    • In tmux: tmux-bridge list, then tmux-bridge read <target> before type, keys, or message.
    • Set TMUX_BRIDGE_SOCKET if the default tmux socket cannot be detected.

Written for commit 2d13624. Summary will update on new commits.

From https://github.com/ShawnPana/smux - enables AI agents to read,
type, and send keys to other tmux panes via a bash CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 2, 2026 03:37
@mesa-dot-dev

mesa-dot-dev Bot commented Apr 2, 2026

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@mesa-dot-dev

mesa-dot-dev Bot commented Apr 2, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Added the tmux-bridge CLI to ~/.local/scripts via home-manager to enable cross-pane communication for AI agents within tmux.

What changed?

  • home-manager/modules/local-scripts/default.nix: Updated to deploy the new tmux-bridge.sh executable script to ~/.local/scripts/tmux-bridge.
  • home-manager/modules/local-scripts/tmux-bridge.sh: New bash script providing agent-agnostic cross-pane communication within tmux. It includes commands for listing, reading, typing, sending keys, sending messages with sender headers, labeling panes, and resolving labels. Features include a 'read-before-act' policy, tmux server socket detection, and a doctor diagnostic command.

Description generated by Mesa. Update settings

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 2 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="home-manager/modules/local-scripts/tmux-bridge.sh">

<violation number="1" location="home-manager/modules/local-scripts/tmux-bridge.sh:227">
P3: `cmd_type` only sends the second argument, so unquoted multi-word text is truncated (e.g., `hello world` becomes `hello`). Consider shifting the target and sending the remaining args as a single message.</violation>

<violation number="2" location="home-manager/modules/local-scripts/tmux-bridge.sh:251">
P3: `cmd_message` only includes the second argument, so unquoted multi-word messages are truncated. Consider shifting the target and appending all remaining args to the header.</violation>
</file>

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

session_win=$(tmx display-message -t "$sender_pane" -p '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null || true)

local header="[tmux-bridge from:${from} pane:${sender_pane} at:${session_win} — load the smux skill to reply]"
tmx send-keys -t "$target" -l -- "${header} $2"

@cubic-dev-ai cubic-dev-ai Bot Apr 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: cmd_message only includes the second argument, so unquoted multi-word messages are truncated. Consider shifting the target and appending all remaining args to the header.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/modules/local-scripts/tmux-bridge.sh, line 251:

<comment>`cmd_message` only includes the second argument, so unquoted multi-word messages are truncated. Consider shifting the target and appending all remaining args to the header.</comment>

<file context>
@@ -0,0 +1,404 @@
+  session_win=$(tmx display-message -t "$sender_pane" -p '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null || true)
+
+  local header="[tmux-bridge from:${from} pane:${sender_pane} at:${session_win} — load the smux skill to reply]"
+  tmx send-keys -t "$target" -l -- "${header} $2"
+  clear_read "$target"
+}
</file context>
Fix with Cubic

validate_target "$target"
require_read "$target"

tmx send-keys -t "$target" -l -- "$2"

@cubic-dev-ai cubic-dev-ai Bot Apr 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: cmd_type only sends the second argument, so unquoted multi-word text is truncated (e.g., hello world becomes hello). Consider shifting the target and sending the remaining args as a single message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/modules/local-scripts/tmux-bridge.sh, line 227:

<comment>`cmd_type` only sends the second argument, so unquoted multi-word text is truncated (e.g., `hello world` becomes `hello`). Consider shifting the target and sending the remaining args as a single message.</comment>

<file context>
@@ -0,0 +1,404 @@
+  validate_target "$target"
+  require_read "$target"
+
+  tmx send-keys -t "$target" -l -- "$2"
+  clear_read "$target"
+}
</file context>
Fix with Cubic

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces tmux-bridge, a bash-based CLI tool for cross-pane communication within tmux, and integrates it into the home-manager configuration. The script includes features for socket detection, pane labeling, and a 'read-before-act' guard mechanism. Feedback was provided regarding a security vulnerability in the read_guard_path function, where predictable filenames in the world-writable /tmp directory could lead to symlink attacks or denial of service; a more secure approach using user-specific directories was suggested.

Comment on lines +16 to +20
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
echo "/tmp/tmux-bridge-read-${pane_id//%/_}"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Using predictable filenames in the world-writable /tmp directory can lead to security vulnerabilities (e.g., symlink attacks or denial of service). Another process could create these files before the script does, potentially bypassing the read guard.

It's more secure to use a user-specific directory. A good practice is to use $XDG_RUNTIME_DIR if it's set, falling back to a user-specific directory inside /tmp.

Suggested change
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
echo "/tmp/tmux-bridge-read-${pane_id//%/_}"
}
read_guard_path() {
local pane_id="$1"
local tmpdir="${XDG_RUNTIME_DIR:-/tmp/tmux-bridge-$(id -u)}"
mkdir -p "$tmpdir"
# Sanitize: %66 → _66
echo "${tmpdir}/read-${pane_id//%/_}"
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new tmux-bridge Bash CLI to the Home Manager “local-scripts” module to enable cross-pane communication (list/read/type/keys/label/doctor) within tmux sessions, intended for AI-agent workflows.

Changes:

  • Introduce home-manager/modules/local-scripts/tmux-bridge.sh implementing the tmux-bridge CLI (socket detection, pane targeting, read-before-act guard, and diagnostic tooling).
  • Deploy the script to ~/.local/scripts/tmux-bridge via home-manager/modules/local-scripts/default.nix.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
home-manager/modules/local-scripts/tmux-bridge.sh New tmux-bridge CLI implementation for tmux cross-pane communication.
home-manager/modules/local-scripts/default.nix Installs tmux-bridge into ~/.local/scripts and marks it executable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +223 to +227
target=$(resolve_target "$1")
validate_target "$target"
require_read "$target"

tmx send-keys -t "$target" -l -- "$2"

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

type only sends $2, so tmux-bridge type <target> hello world will drop everything after the first word unless the caller remembers to quote. Consider consuming the rest of the arguments as the text payload (e.g., join/"$*" after shifting the target) so multi-word input works reliably for agents and humans.

Copilot uses AI. Check for mistakes.
session_win=$(tmx display-message -t "$sender_pane" -p '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null || true)

local header="[tmux-bridge from:${from} pane:${sender_pane} at:${session_win} — load the smux skill to reply]"
tmx send-keys -t "$target" -l -- "${header} $2"

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

message only appends $2 after the header, which truncates unquoted multi-word messages. Consider treating all remaining args after <target> as the message body so callers don't have to rely on correct quoting.

Suggested change
tmx send-keys -t "$target" -l -- "${header} $2"
local message_body="${*:2}"
tmx send-keys -t "$target" -l -- "${header} ${message_body}"

Copilot uses AI. Check for mistakes.
Comment on lines +171 to +172
result=$(tmx list-panes -a -F '#{pane_id} #{@name}' 2>/dev/null \
| awk -v lbl="$label" '$2 == lbl { print $1; exit }')

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Label resolution splits on whitespace (-F '#{pane_id} #{@name}' then awk compares $2), so labels containing spaces (or other whitespace) cannot be resolved correctly. Either enforce/validate that @name contains no whitespace in cmd_name, or change the format to use a delimiter (e.g., |) and parse robustly.

Suggested change
result=$(tmx list-panes -a -F '#{pane_id} #{@name}' 2>/dev/null \
| awk -v lbl="$label" '$2 == lbl { print $1; exit }')
result=$(tmx list-panes -a -F '#{pane_id}|#{@name}' 2>/dev/null \
| awk -F '|' -v lbl="$label" '$2 == lbl { print $1; exit }')

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +19
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
echo "/tmp/tmux-bridge-read-${pane_id//%/_}"

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The read-guard marker is created in /tmp with a predictable name. On multi-user systems another local user could pre-create that file to bypass the read-before-act guard (sticky bit allows creating new files in /tmp). Consider placing guard files under a per-user 0700 directory (e.g., ${XDG_RUNTIME_DIR:-/tmp}/tmux-bridge-$UID/) to prevent spoofing.

Suggested change
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
echo "/tmp/tmux-bridge-read-${pane_id//%/_}"
read_guard_base_dir() {
# Use per-user runtime dir to avoid predictable files in shared /tmp.
local runtime_dir="${XDG_RUNTIME_DIR:-/tmp}"
local uid
uid=$(id -u)
echo "${runtime_dir}/tmux-bridge-${uid}"
}
ensure_read_guard_dir() {
local dir
dir=$(read_guard_base_dir)
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir" || die "failed to create read guard directory: $dir"
chmod 700 "$dir" || die "failed to set permissions on read guard directory: $dir"
fi
}
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
ensure_read_guard_dir
local dir
dir=$(read_guard_base_dir)
echo "${dir}/tmux-bridge-read-${pane_id//%/_}"

Copilot uses AI. Check for mistakes.
Comment on lines +200 to +203
# Get the actual running process (deepest child of pane pid)
local proc child_pid
proc=$(ps -o comm= -p "$pid" 2>/dev/null || echo "?")
# Find child process (e.g. claude/node running inside zsh) — works on macOS + Linux

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The comment claims this finds the "deepest child" process, but pgrep -P "$pid" | head -1 only returns an arbitrary direct child. Either adjust the comment to match the behavior, or implement a real descendant walk if you need the innermost process.

Suggested change
# Get the actual running process (deepest child of pane pid)
local proc child_pid
proc=$(ps -o comm= -p "$pid" 2>/dev/null || echo "?")
# Find child process (e.g. claude/node running inside zsh) — works on macOS + Linux
# Get the displayed process (pane pid, or one direct child if present)
local proc child_pid
proc=$(ps -o comm= -p "$pid" 2>/dev/null || echo "?")
# Prefer a direct child process (e.g. claude/node running inside zsh) — works on macOS + Linux

Copilot uses AI. Check for mistakes.
@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A new tmux bridge script is added as an executable to ~/.local/scripts/tmux-bridge, enabling agent-agnostic cross-pane communication. The script implements socket discovery, target validation, and a read-before-act guard protocol for secure inter-pane messaging with multiple command types including list, type, message, read, keys, name, resolve, and diagnostic utilities.

Changes

Cohort / File(s) Summary
Home Manager Module Configuration
home-manager/modules/local-scripts/default.nix
Added file mapping to install tmux-bridge.sh as an executable script at ~/.local/scripts/tmux-bridge with force overwrite enabled.
Tmux Bridge Script Implementation
home-manager/modules/local-scripts/tmux-bridge.sh
New executable Bash script (404 lines) implementing cross-pane communication via socket discovery, target validation, guard file protocol (read-before-act), and 8 core commands (list, type, message, read, keys, name, resolve, id, doctor) plus version and help.

Sequence Diagram

sequenceDiagram
    actor User
    participant Script as tmux-bridge.sh
    participant TmuxServer as Tmux Server
    participant GuardFiles as /tmp Guard Files
    participant TargetPane as Target Pane

    User->>Script: Execute command (e.g., "message")
    Script->>Script: Discover socket (TMUX_BRIDGE_SOCKET/<br/>TMUX env/<br/>scan /tmp dirs)
    Script->>TmuxServer: Validate socket connectivity
    Script->>Script: Resolve target (pane id/<br/>@label/selector)
    Script->>TmuxServer: Validate target with display-message
    alt Command requires read guard
        Script->>GuardFiles: Check if read guard exists
        alt Guard missing
            Script->>Script: Abort (die)
        end
    end
    Script->>GuardFiles: Write guard file (type/message/keys)
    Script->>TmuxServer: Send command to target pane
    TargetPane->>TargetPane: Receive input/keys
    Script->>GuardFiles: Clear guard file
    Script->>User: Return status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A bridge of scripts now spans the tmux panes,
Where rabbits send messages down digital lanes,
With guard files standing sentry at /tmp's door,
Cross-pane chatter like never before! 🌉✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a tmux-bridge script for cross-pane agent communication, which matches the changeset perfectly.
Description check ✅ Passed The pull request description clearly relates to the changeset, describing the addition of tmux-bridge CLI to local-scripts with deployment details and testing instructions.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-tmux-bridge

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.

@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: 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 `@home-manager/modules/local-scripts/tmux-bridge.sh`:
- Around line 219-229: The cmd_type (and the similar message function) currently
only sends "$2", dropping all remaining argv words; update cmd_type to shift the
first arg (target) and send the rest as one string using the positional slice
expansion (e.g., use "${*:2}" or equivalent) so tmx send-keys receives the full
payload, and apply the same change to the message function (functions: cmd_type
and message) to preserve multi-word input.
- Around line 144-176: resolve_label and resolve_target currently split tmux
output on whitespace and treat the first match as authoritative while cmd_name
allows arbitrary/duplicate labels, so labels with spaces, dots or duplicates can
misroute; fix by enforcing a safe label policy at write time in cmd_name (reject
or normalize labels to a restricted charset and ensure uniqueness) OR make
lookup delimiter-safe and unambiguous in resolve_label/resolve_target by using a
null-delimited/listing mode (or tmux format fields with an exact-name
comparator) and explicitly detect and die on multiple matches; update
resolve_label to perform exact equality checks (not whitespace splitting),
return an error on ambiguous matches, and ensure resolve_target treats reserved
tmux syntaxes consistently with the chosen label policy.
- Around line 305-364: The doctor routine (cmd_doctor) is running after global
init_socket and still uses tmux in places, so stale TMUX/missing-server cases
abort early and pane counts query the wrong server; move the doctor dispatch to
run before init_socket is called and inside cmd_doctor use detect_socket to set
a local TMUX_SOCKET (or export TMUX_SOCKET locally) from detect_socket's result,
then use tmx everywhere in cmd_doctor (replace tmux calls like in the pane/count
logic) so it queries the detected socket and will not trigger set -e aborts;
update any global dispatcher that calls init_socket to call cmd_doctor before
init_socket so doctor can diagnose stale envs.
- Around line 16-35: The current read-guard functions (read_guard_path,
mark_read, require_read, clear_read) key the guard only by pane id ("%N"), which
is not unique across tmux servers or restarts; update read_guard_path to
incorporate the tmux server socket name and a stable pane identifier (e.g.,
pane_pid) into the filename so the guard ties to the specific server+pane
instance; change mark_read to capture and persist the pane's stable identity
when creating the guard file, make require_read validate both the socket and the
persisted pane identity inside the guard, and ensure clear_read removes the
correct server-scoped guard; use tmux queries (socket name and pane_pid via tmux
display/list-panes) to obtain the identifiers in the functions referenced above.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a436d49-b53b-452a-b520-a88d0b253bc7

📥 Commits

Reviewing files that changed from the base of the PR and between 4f3612a and ecc5d4c.

📒 Files selected for processing (2)
  • home-manager/modules/local-scripts/default.nix
  • home-manager/modules/local-scripts/tmux-bridge.sh

Comment on lines +16 to +35
read_guard_path() {
local pane_id="$1"
# Sanitize: %66 → _66
echo "/tmp/tmux-bridge-read-${pane_id//%/_}"
}

mark_read() {
touch "$(read_guard_path "$1")"
}

require_read() {
local guard
guard=$(read_guard_path "$1")
if [[ ! -f "$guard" ]]; then
die "must read the pane before interacting. Run: tmux-bridge read $1"
fi
}

clear_read() {
rm -f "$(read_guard_path "$1")"

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

Key the read guard by tmux server and pane instance, not just %N.

/tmp/tmux-bridge-read-_1 is reusable across tmux servers and after server restarts, so a previous read %1 can authorize type %1 against a different pane. That breaks the core read-before-act guarantee. Include the detected socket in the guard key and persist a stable pane identity (for example pane_pid) when marking/validating the read.

Proposed fix
 read_guard_path() {
   local pane_id="$1"
-  # Sanitize: %66 → _66
-  echo "/tmp/tmux-bridge-read-${pane_id//%/_}"
+  local runtime_dir="${XDG_RUNTIME_DIR:-/tmp}"
+  local socket_key="${TMUX_SOCKET:-__default__}"
+  socket_key="${socket_key//[^[:alnum:]]/_}"
+  echo "${runtime_dir}/tmux-bridge-read-${socket_key}-${pane_id//%/_}"
 }
 
 mark_read() {
-  touch "$(read_guard_path "$1")"
+  tmx display-message -t "$1" -p '#{pane_pid}' >"$(read_guard_path "$1")"
 }
 
 require_read() {
   local guard
   guard=$(read_guard_path "$1")
   if [[ ! -f "$guard" ]]; then
     die "must read the pane before interacting. Run: tmux-bridge read $1"
   fi
+  local current_pid
+  current_pid=$(tmx display-message -t "$1" -p '#{pane_pid}')
+  [[ "$(cat "$guard")" == "$current_pid" ]] \
+    || die "pane changed since last read. Run: tmux-bridge read $1"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/modules/local-scripts/tmux-bridge.sh` around lines 16 - 35, The
current read-guard functions (read_guard_path, mark_read, require_read,
clear_read) key the guard only by pane id ("%N"), which is not unique across
tmux servers or restarts; update read_guard_path to incorporate the tmux server
socket name and a stable pane identifier (e.g., pane_pid) into the filename so
the guard ties to the specific server+pane instance; change mark_read to capture
and persist the pane's stable identity when creating the guard file, make
require_read validate both the socket and the persisted pane identity inside the
guard, and ensure clear_read removes the correct server-scoped guard; use tmux
queries (socket name and pane_pid via tmux display/list-panes) to obtain the
identifiers in the functions referenced above.

Comment on lines +144 to +176
# Resolve a target: if it looks like a tmux target (%N, session:win.pane, pure digits),
# use it directly; otherwise treat it as a @name label and resolve.
resolve_target() {
local target="$1"

# tmux pane ID like %0, %12
if [[ "$target" =~ ^%[0-9]+$ ]]; then
echo "$target"; return
fi

# Looks like a tmux target with colon or dot (session:win.pane)
if [[ "$target" == *:* ]] || [[ "$target" == *.* ]]; then
echo "$target"; return
fi

# Pure numeric — treat as window index
if [[ "$target" =~ ^[0-9]+$ ]]; then
echo "$target"; return
fi

# Otherwise resolve as a @name label
resolve_label "$target"
}

resolve_label() {
local label="$1"
local result
result=$(tmx list-panes -a -F '#{pane_id} #{@name}' 2>/dev/null \
| awk -v lbl="$label" '$2 == lbl { print $1; exit }')
if [[ -z "$result" ]]; then
die "no pane found with label '$label'"
fi
echo "$result"

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

Label-based routing is lossy and can misroute commands.

cmd_name allows arbitrary/duplicate labels, resolve_label splits on whitespace and returns the first match only, and resolve_target reserves dotted/numeric names for tmux syntax. Labels like my bot, api.v1, or duplicate codex values will either fail to resolve or resolve arbitrarily. Please either validate labels on write time (unique + restricted charset) or make lookup delimiter-safe and fail on ambiguity.

Also applies to: 282-289

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

In `@home-manager/modules/local-scripts/tmux-bridge.sh` around lines 144 - 176,
resolve_label and resolve_target currently split tmux output on whitespace and
treat the first match as authoritative while cmd_name allows arbitrary/duplicate
labels, so labels with spaces, dots or duplicates can misroute; fix by enforcing
a safe label policy at write time in cmd_name (reject or normalize labels to a
restricted charset and ensure uniqueness) OR make lookup delimiter-safe and
unambiguous in resolve_label/resolve_target by using a null-delimited/listing
mode (or tmux format fields with an exact-name comparator) and explicitly detect
and die on multiple matches; update resolve_label to perform exact equality
checks (not whitespace splitting), return an error on ambiguous matches, and
ensure resolve_target treats reserved tmux syntaxes consistently with the chosen
label policy.

Comment on lines +219 to +229
cmd_type() {
require_args 2 $# "type"
require_tmux
local target
target=$(resolve_target "$1")
validate_target "$target"
require_read "$target"

tmx send-keys -t "$target" -l -- "$2"
clear_read "$target"
}

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 | 🟡 Minor

type and message silently drop everything after the first word.

Both commands only send $2. Any caller that passes the text as multiple argv entries will lose the rest of the payload, which is easy to hit for agent prompts/instructions. Shift the target off first and send the remaining arguments as one string.

Proposed fix
 cmd_type() {
   require_args 2 $# "type"
   require_tmux
   local target
   target=$(resolve_target "$1")
   validate_target "$target"
   require_read "$target"
+  shift
 
-  tmx send-keys -t "$target" -l -- "$2"
+  tmx send-keys -t "$target" -l -- "$*"
   clear_read "$target"
 }
 
 cmd_message() {
   require_args 2 $# "message"
@@
   local target
   target=$(resolve_target "$1")
   validate_target "$target"
   require_read "$target"
+  shift
 
   # Detect sender identity and location
   local sender_pane="${TMUX_PANE:-}"
@@
 
   local header="[tmux-bridge from:${from} pane:${sender_pane} at:${session_win} — load the smux skill to reply]"
-  tmx send-keys -t "$target" -l -- "${header} $2"
+  tmx send-keys -t "$target" -l -- "${header} $*"
   clear_read "$target"
 }

Also applies to: 231-253

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

In `@home-manager/modules/local-scripts/tmux-bridge.sh` around lines 219 - 229,
The cmd_type (and the similar message function) currently only sends "$2",
dropping all remaining argv words; update cmd_type to shift the first arg
(target) and send the rest as one string using the positional slice expansion
(e.g., use "${*:2}" or equivalent) so tmx send-keys receives the full payload,
and apply the same change to the message function (functions: cmd_type and
message) to preserve multi-word input.

Comment on lines +305 to +364
cmd_doctor() {
require_tmux
local ok=true

echo "tmux-bridge doctor v${VERSION}"
echo "---"

# Environment
echo "TMUX_PANE: ${TMUX_PANE:-<unset>}"
echo "TMUX: ${TMUX:-<unset>}"
echo "TMUX_BRIDGE_SOCKET: ${TMUX_BRIDGE_SOCKET:-<unset>}"

# Socket from $TMUX
if [[ -n "${TMUX:-}" ]]; then
local env_socket="${TMUX%%,*}"
if [[ -S "$env_socket" ]]; then
if tmux -S "$env_socket" list-sessions &>/dev/null; then
echo "\$TMUX socket: $env_socket (reachable)"
else
echo "\$TMUX socket: $env_socket (exists but not responding)"
ok=false
fi
else
echo "\$TMUX socket: $env_socket (MISSING — stale env)"
ok=false
fi
fi

# Detected socket
echo "---"
local detected
detected=$(detect_socket 2>/dev/null || echo "__failed__")
if [[ "$detected" == "__failed__" ]]; then
echo "Detected socket: FAILED — no reachable tmux server found"
ok=false
elif [[ "$detected" == "__default__" ]]; then
echo "Detected socket: (default tmux server)"
else
echo "Detected socket: $detected"
fi

# Pane visibility
echo "---"
if [[ -n "${TMUX_PANE:-}" && "$detected" != "__failed__" ]]; then
if tmx display-message -t "$TMUX_PANE" -p '#{pane_id}' &>/dev/null; then
echo "This pane ($TMUX_PANE): visible to server"
else
echo "This pane ($TMUX_PANE): NOT visible to server"
ok=false
fi
fi

# Pane count
if [[ "$detected" != "__failed__" ]]; then
local count
count=$(tmx list-panes -a -F '#{pane_id}' 2>/dev/null | wc -l | tr -d ' ')
echo "Total panes: $count"
local labeled
labeled=$(tmx list-panes -a -F '#{@name}' 2>/dev/null | grep -cv '^$' || echo 0)
echo "Labeled panes: $labeled"

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

doctor bails out before the failure mode it is supposed to diagnose.

The main dispatcher still runs init_socket before cmd_doctor, so stale TMUX/missing-server cases exit before any diagnostics print. Inside cmd_doctor, the pane counts also use raw tmux instead of tmx, so a non-default socket is reported against the wrong server and can abort under set -e. Let doctor run before global socket init and bind TMUX_SOCKET from the locally detected value inside the command.

Proposed fix
 case "$1" in
   id)      shift; cmd_id "$@"; exit ;;
+  doctor)  shift; cmd_doctor "$@"; exit ;;
   -h|--help|help) usage ;;
   version) echo "tmux-bridge $VERSION"; exit ;;
 esac
@@
   detected=$(detect_socket 2>/dev/null || echo "__failed__")
   if [[ "$detected" == "__failed__" ]]; then
     echo "Detected socket:    FAILED — no reachable tmux server found"
     ok=false
   elif [[ "$detected" == "__default__" ]]; then
     echo "Detected socket:    (default tmux server)"
+    TMUX_SOCKET="__default__"
   else
     echo "Detected socket:    $detected"
+    TMUX_SOCKET="$detected"
   fi
@@
   if [[ "$detected" != "__failed__" ]]; then
     local count
-    count=$(tmux list-panes -a -F '#{pane_id}' 2>/dev/null | wc -l | tr -d ' ')
+    count=$(tmx list-panes -a -F '#{pane_id}' 2>/dev/null | wc -l | tr -d ' ')
     echo "Total panes:        $count"
     local labeled
-    labeled=$(tmux list-panes -a -F '#{`@name`}' 2>/dev/null | grep -cv '^$' || echo 0)
+    labeled=$(tmx list-panes -a -F '#{`@name`}' 2>/dev/null | grep -cv '^$' || echo 0)
     echo "Labeled panes:      $labeled"
   fi

Also applies to: 383-402

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

In `@home-manager/modules/local-scripts/tmux-bridge.sh` around lines 305 - 364,
The doctor routine (cmd_doctor) is running after global init_socket and still
uses tmux in places, so stale TMUX/missing-server cases abort early and pane
counts query the wrong server; move the doctor dispatch to run before
init_socket is called and inside cmd_doctor use detect_socket to set a local
TMUX_SOCKET (or export TMUX_SOCKET locally) from detect_socket's result, then
use tmx everywhere in cmd_doctor (replace tmux calls like in the pane/count
logic) so it queries the detected socket and will not trigger set -e aborts;
update any global dispatcher that calls init_socket to call cmd_doctor before
init_socket so doctor can diagnose stale envs.

@shunkakinoki
shunkakinoki merged commit 7fcd73d into main Apr 5, 2026
29 of 36 checks passed
@shunkakinoki
shunkakinoki deleted the feat/add-tmux-bridge branch April 5, 2026 09:52
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.

2 participants