Skip to content

__tmux-compat: short tmux format codes (#S, #I, #W, ...) are not substituted, breaking cmux omc - #3228

Closed
lawrencecchen wants to merge 2 commits into
mainfrom
loader/issue-3190-tmux-compat-short-tmux-format-codes-s-i-w-are-not-substi
Closed

lawrencecchen wants to merge 2 commits into
mainfrom
loader/issue-3190-tmux-compat-short-tmux-format-codes-s-i-w-are-not-substi

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

Addresses #3190

Generated by cmux-loader.

Codex final message:

Done.

Summary

  1. Implemented minimal __tmux-compat format substitution support for short tmux aliases in tmuxRenderFormat by mapping short codes to existing long-form context keys before fallback cleanup:

    • #S -> session_name
    • #I -> window_index
    • #W -> window_name
    • #P -> pane_index
    • #T -> pane_title
    • #D -> pane_id
    • File: CLI/cmux.swift
  2. Added a behavior-level regression test that validates short-form rendering:

Verification performed

  • Required tagged reload attempted: ./scripts/reload --tag loader
  • Result: failed immediately in this environment with:
    • Error: zig is not installed. Install via: brew install zig
  • No runtime tests were executed in this session due the environment limitation and the local policy of not running test suites unless explicitly requested.

PR URL

No PR was opened from this environment because GitHub CLI access requires authentication (GH_TOKEN) and it is not set here.


Summary by cubic

Enable short tmux format aliases in __tmux-compat so #S, #I, #W, and others render correctly, fixing broken cmux omc output. Fixes #3190 and refreshes the Swift file length budget.

  • Bug Fixes
    • Map short aliases in tmuxRenderFormat: #S→session_name, #I→window_index, #W→window_name, #P→pane_index, #T→pane_title, #D→pane_id.
    • Add regression test comparing #S:#I:#W to long-form and keeping %<paneid> literal.

Written for commit 7c793c9. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for shorthand format string placeholders for enhanced tmux compatibility.
  • Tests

    • Added comprehensive tests for format string rendering and placeholder handling.

Generated by cmux-loader.
@vercel

vercel Bot commented Apr 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 28, 2026 11:11pm
cmux-staging Building Building Preview, Comment Apr 28, 2026 11:11pm

@coderabbitai

coderabbitai Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The tmux-format rendering in the CLI now recognizes and expands shorthand placeholders (#S, #I, #W, #P, #T, #D) to their long-form equivalents during format string processing. A corresponding test validates that both short and long form codes produce equivalent output.

Changes

Cohort / File(s) Summary
Shorthand Format Code Expansion
CLI/cmux.swift
Adds mapping logic to recognize six tmux shorthand format codes and expand them to their long-form context-backed values (session_name, window_index, window_name, pane_index, pane_title, pane_id) during rendering.
Geometry Compatibility Tests
tests_v2/test_tmux_compat_geometry.py
Introduces test coverage verifying that short-form aliases (#S, #I, #W) produce equivalent output to long-form tokens, and validates handling of literal placeholders like %<paneid>.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A shorthand here, expansion there,
Format codes now matched with care—
#S and #I find their way,
Where session_name comes to play!
Short and long now dance as one,
The OMC crew can finally run.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.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 clearly describes the bug: short tmux format codes (#S, #I, #W, etc.) are not substituted, breaking the cmux omc command.
Description check ✅ Passed The description includes a summary of changes, details about test coverage, and verification steps. However, testing verification is incomplete due to environment limitations.
Linked Issues check ✅ Passed The PR successfully addresses issue #3190 by implementing short-form tmux format code substitution (#S, #I, #W, #P, #T, #D) and adding regression test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing short tmux format code substitution and adding corresponding test coverage as specified in issue #3190.

✏️ 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 loader/issue-3190-tmux-compat-short-tmux-format-codes-s-i-w-are-not-substi

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8418b2e0d0

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread CLI/cmux.swift
"D": "pane_id",
]
for (shortAlias, key) in shortAliases where context.keys.contains(key) {
rendered = rendered.replacingOccurrences(of: "#\(shortAlias)", with: context[key] ?? "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent recursive expansion of inserted short-format values

The new short-alias loop does global replacingOccurrences on the already-rendered string, so substitutions can run again inside values that were just inserted. For example, if a window title is literally #P, formatting #W first inserts #P and then the later #P pass rewrites it to the pane index, which is incorrect (tmux should return the title text as-is). This causes wrong output for user-controlled names/titles containing short alias patterns.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes cmux omc by adding short tmux format code substitution (#S, #I, #W, #P, #T, #D) inside tmuxRenderFormat, mapping each alias to its existing long-form context key. A regression test verifies the happy-path #S:#I:#W expansion and that %<paneid> tokens pass through untouched.

  • P1 – literal token leakage: the where context.keys.contains(key) guard skips substitution when a key is absent, so short codes like #P, #T, or #D stay in the rendered string as literal #X instead of being stripped — inconsistent with how #{…} long-form codes are cleaned up by the regex that follows.

Confidence Score: 3/5

Not safe to merge as-is: the guard condition causes unresolved short codes to remain as literal tokens in output.

One P1 logic bug (unresolved short codes leak instead of being stripped) and one P2 policy violation (single-commit regression test). The P1 affects real users: any format string using #P, #T, or #D when no pane is active will produce corrupted output with literal #P/#T/#D artifacts.

CLI/cmux.swift — the where context.keys.contains(key) guard on line 11571 needs to be removed so unresolved short codes are always replaced (with empty string, matching long-form cleanup behaviour).

Important Files Changed

Filename Overview
CLI/cmux.swift Adds short-form tmux alias substitution to tmuxRenderFormat; the where context.keys.contains(key) guard causes unresolved short codes to leak as literal tokens instead of being stripped, inconsistent with long-form cleanup behaviour.
tests_v2/test_tmux_compat_geometry.py Adds test_display_short_format_aliases covering the #S:#I:#W happy path and %<paneid> passthrough; test is correct but violates the two-commit regression-test policy from CLAUDE.md.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["tmuxRenderFormat(format, context, fallback)"] --> B["Replace long-form #{key} tokens from context"]
    B --> C["For each shortAlias in table #S/#I/#W/#P/#T/#D"]
    C --> D{"context contains mapped key?"}
    D -- "yes" --> E["Replace #X with context value"]
    D -- "no" --> F["⚠️ Leave literal #X in string (not cleaned up)"]
    E --> G["Regex cleanup: remove unmatched #{...} tokens"]
    F --> G
    G --> H["Trim whitespace"]
    H --> I{"trimmed empty?"}
    I -- "yes" --> J["Return fallback"]
    I -- "no" --> K["Return rendered string"]
Loading

Reviews (1): Last reviewed commit: "Address https://github.com/manaflow-ai/c..." | Re-trigger Greptile

Comment thread CLI/cmux.swift
Comment on lines +11571 to +11573
for (shortAlias, key) in shortAliases where context.keys.contains(key) {
rendered = rendered.replacingOccurrences(of: "#\(shortAlias)", with: context[key] ?? "")
}

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.

P1 Unresolved short codes leak as literal #X instead of being stripped

When a context key is absent (e.g. no active pane, so pane_index/pane_title/pane_id are never set), the where context.keys.contains(key) guard skips the substitution and leaves the raw token — e.g. #P, #T, #D — in the rendered string. Long-form codes like #{pane_index} are cleaned up by the #\{[^}]+\} regex pass that follows, but there is no equivalent cleanup for short codes. The result is that callers who pass a format string that includes short codes for keys not present in the current context get back a string with literal #P / #T / #D artifacts, while the parallel long-form codes are silently removed.

A minimal fix is to always replace — using an empty string (matching long-form cleanup behaviour) when the key is absent:

for (shortAlias, key) in shortAliases {
    rendered = rendered.replacingOccurrences(of: "#\(shortAlias)", with: context[key] ?? "")
}

Comment on lines 158 to 183
print("PASS")


def test_display_short_format_aliases(cli: str) -> None:
"""Short tmux format aliases match long-form output."""
print(" test_display_short_format_aliases ... ", end="", flush=True)
long_form = _run_tmux_compat(cli, ["display", "-p", "#{session_name}:#{window_index}:#{window_name}"])
_must(long_form.returncode == 0, f"display with long-form failed: {long_form.stderr}")
long_output = long_form.stdout.strip()
_must(long_output, f"Expected long-form output, got empty: {long_form.stdout!r}")

short_form = _run_tmux_compat(cli, ["display", "-p", "#S:#I:#W"])
_must(short_form.returncode == 0, f"display with short-form failed: {short_form.stderr}")
short_output = short_form.stdout.strip()
_must(short_output == long_output, f"Short-form output mismatch: short={short_output!r} long={long_output!r}")

paneid_form = _run_tmux_compat(cli, ["display", "-p", "#S:#I %<paneid>"])
_must(paneid_form.returncode == 0, f"display with paneid token failed: {paneid_form.stderr}")
paneid_output = paneid_form.stdout.strip()
_must(long_output.split(":")[0] in paneid_output and long_output.split(":")[1] in paneid_output, f"paneid form should include expanded short fields: {paneid_output!r}")
_must("%<paneid>" in paneid_output, f"Expected literal %<paneid> to remain: {paneid_output!r}")
print("PASS")


def test_multi_pane_geometry(cli: str, c: cmux) -> None:
"""After splitting, two panes have different pane_left values and halved widths."""

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.

P2 Regression test policy: fix and test landed in a single commit

CLAUDE.md requires a two-commit structure for every regression-test/bug-fix pair so CI can prove the test actually catches the bug before the fix is applied:

  1. Commit 1 – Add the failing test only (no fix). CI should go red.
  2. Commit 2 – Add the fix. CI should go green.

This PR squashes both the new test_display_short_format_aliases test and the shortAliases implementation into one commit, so there is no CI evidence that the test would have failed against the pre-fix code.

Context Used: CLAUDE.md (source)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@CLI/cmux.swift`:
- Around line 11563-11576: The shorthand expansion currently mutates rendered
after long-form replacements, allowing substituted values to be reprocessed and
leaving unresolved short tokens; instead, scan the original template string (not
rendered) for short placeholders using the shortAliases map and apply
replacements into rendered only where those short placeholders actually existed
in the original template; use the same presence logic as the long-form fallback
(the "#\\{[^}]+\\}" cleanup) to clear any unresolved short tokens so no raw "#X"
remains; refer to shortAliases, rendered, context and the existing long-form
replacement call when making the change.

In `@tests_v2/test_tmux_compat_geometry.py`:
- Around line 161-179: Update test_display_short_format_aliases to also exercise
the newly added short aliases (`#P`, `#T`, `#D`) instead of only `#S/`#I/#W: build a
mapping of short tokens to their long-form tokens, request tmux display once
using the long-form tokens joined by a delimiter unlikely to appear in fields
(e.g. ASCII unit separator) and once using the corresponding short tokens joined
by the same delimiter, then assert the two outputs are equal; replace the
brittle long_output.split(":") checks with this delimiter-based equality check
and apply the same approach to the paneid_form check (use the delimiter and
verify the expected literal %<paneid> behavior) so the test covers `#P/`#T/#D and
avoids colon-splitting fragility while keeping checks around long_form,
short_form, and paneid_form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c517833-5cd6-40a4-9fc3-2b58ee401e1b

📥 Commits

Reviewing files that changed from the base of the PR and between 302382d and 7c793c9.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • CLI/cmux.swift
  • tests_v2/test_tmux_compat_geometry.py

Comment thread CLI/cmux.swift
Comment on lines +11563 to 11576
let shortAliases: [String: String] = [
"S": "session_name",
"I": "window_index",
"W": "window_name",
"P": "pane_index",
"T": "pane_title",
"D": "pane_id",
]
for (shortAlias, key) in shortAliases where context.keys.contains(key) {
rendered = rendered.replacingOccurrences(of: "#\(shortAlias)", with: context[key] ?? "")
}
rendered = rendered.replacingOccurrences(
of: "#\\{[^}]+\\}",
with: "",

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

Keep shorthand expansion from reprocessing substituted values.

This pass runs on rendered after the long-form replacements, so any literal #S/#I sequences inside a session name, window name, or pane title can be expanded a second time. The where context.keys.contains(key) guard also means an unresolved shorthand token can survive as a raw #X instead of being cleared like the long-form fallback.

Please expand shorthand tokens while scanning the original template, or otherwise confine replacement to actual format placeholders only.

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

In `@CLI/cmux.swift` around lines 11563 - 11576, The shorthand expansion currently
mutates rendered after long-form replacements, allowing substituted values to be
reprocessed and leaving unresolved short tokens; instead, scan the original
template string (not rendered) for short placeholders using the shortAliases map
and apply replacements into rendered only where those short placeholders
actually existed in the original template; use the same presence logic as the
long-form fallback (the "#\\{[^}]+\\}" cleanup) to clear any unresolved short
tokens so no raw "#X" remains; refer to shortAliases, rendered, context and the
existing long-form replacement call when making the change.

Comment on lines +161 to +179
def test_display_short_format_aliases(cli: str) -> None:
"""Short tmux format aliases match long-form output."""
print(" test_display_short_format_aliases ... ", end="", flush=True)
long_form = _run_tmux_compat(cli, ["display", "-p", "#{session_name}:#{window_index}:#{window_name}"])
_must(long_form.returncode == 0, f"display with long-form failed: {long_form.stderr}")
long_output = long_form.stdout.strip()
_must(long_output, f"Expected long-form output, got empty: {long_form.stdout!r}")

short_form = _run_tmux_compat(cli, ["display", "-p", "#S:#I:#W"])
_must(short_form.returncode == 0, f"display with short-form failed: {short_form.stderr}")
short_output = short_form.stdout.strip()
_must(short_output == long_output, f"Short-form output mismatch: short={short_output!r} long={long_output!r}")

paneid_form = _run_tmux_compat(cli, ["display", "-p", "#S:#I %<paneid>"])
_must(paneid_form.returncode == 0, f"display with paneid token failed: {paneid_form.stderr}")
paneid_output = paneid_form.stdout.strip()
_must(long_output.split(":")[0] in paneid_output and long_output.split(":")[1] in paneid_output, f"paneid form should include expanded short fields: {paneid_output!r}")
_must("%<paneid>" in paneid_output, f"Expected literal %<paneid> to remain: {paneid_output!r}")
print("PASS")

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

Tighten the alias regression coverage.

This only exercises #S, #I, and #W, so it can still miss regressions in the newly added #P, #T, and #D mappings. The split(":") check is also brittle if tmux output ever contains : in a field.

♻️ Suggested test shape
 def test_display_short_format_aliases(cli: str) -> None:
     """Short tmux format aliases match long-form output."""
-    print("  test_display_short_format_aliases ... ", end="", flush=True)
-    long_form = _run_tmux_compat(cli, ["display", "-p", "#{session_name}:#{window_index}:#{window_name}"])
-    _must(long_form.returncode == 0, f"display with long-form failed: {long_form.stderr}")
-    long_output = long_form.stdout.strip()
-    _must(long_output, f"Expected long-form output, got empty: {long_form.stdout!r}")
-
-    short_form = _run_tmux_compat(cli, ["display", "-p", "#S:`#I`:`#W`"])
-    _must(short_form.returncode == 0, f"display with short-form failed: {short_form.stderr}")
-    short_output = short_form.stdout.strip()
-    _must(short_output == long_output, f"Short-form output mismatch: short={short_output!r} long={long_output!r}")
-
-    paneid_form = _run_tmux_compat(cli, ["display", "-p", "#S:`#I` %<paneid>"])
-    _must(paneid_form.returncode == 0, f"display with paneid token failed: {paneid_form.stderr}")
-    paneid_output = paneid_form.stdout.strip()
-    _must(long_output.split(":")[0] in paneid_output and long_output.split(":")[1] in paneid_output, f"paneid form should include expanded short fields: {paneid_output!r}")
-    _must("%<paneid>" in paneid_output, f"Expected literal %<paneid> to remain: {paneid_output!r}")
+    cases = [
+        ("#S", "#{session_name}"),
+        ("#I", "#{window_index}"),
+        ("#W", "#{window_name}"),
+        ("#P", "#{pane_index}"),
+        ("#T", "#{pane_title}"),
+        ("#D", "#{pane_id}"),
+    ]
+
+    for short_fmt, long_fmt in cases:
+        short_form = _run_tmux_compat(cli, ["display", "-p", short_fmt])
+        long_form = _run_tmux_compat(cli, ["display", "-p", long_fmt])
+        _must(short_form.returncode == 0, f"display with short-form failed: {short_form.stderr}")
+        _must(long_form.returncode == 0, f"display with long-form failed: {long_form.stderr}")
+        _must(
+            short_form.stdout.strip() == long_form.stdout.strip(),
+            f"Short-form output mismatch for {short_fmt}: short={short_form.stdout!r} long={long_form.stdout!r}",
+        )
+
+    paneid_form = _run_tmux_compat(cli, ["display", "-p", "#S:`#I` %<paneid>"])
+    _must(paneid_form.returncode == 0, f"display with paneid token failed: {paneid_form.stderr}")
+    paneid_output = paneid_form.stdout.strip()
+    _must(paneid_output.endswith(" %<paneid>"), f"Expected literal %<paneid> to remain: {paneid_output!r}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests_v2/test_tmux_compat_geometry.py` around lines 161 - 179, Update
test_display_short_format_aliases to also exercise the newly added short aliases
(`#P`, `#T`, `#D`) instead of only `#S/`#I/#W: build a mapping of short tokens to their
long-form tokens, request tmux display once using the long-form tokens joined by
a delimiter unlikely to appear in fields (e.g. ASCII unit separator) and once
using the corresponding short tokens joined by the same delimiter, then assert
the two outputs are equal; replace the brittle long_output.split(":") checks
with this delimiter-based equality check and apply the same approach to the
paneid_form check (use the delimiter and verify the expected literal %<paneid>
behavior) so the test covers `#P/`#T/#D and avoids colon-splitting fragility while
keeping checks around long_form, short_form, and paneid_form.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

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

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

__tmux-compat: short tmux format codes (#S, #I, #W, ...) are not substituted, breaking cmux omc

2 participants