Skip to content

Fix break_pane forcing 'libtmux' window name on tmux 3.7a/3.7b#699

Merged
tony merged 7 commits into
masterfrom
fix/break-pane-3.7-placeholder-name
Jul 4, 2026
Merged

Fix break_pane forcing 'libtmux' window name on tmux 3.7a/3.7b#699
tony merged 7 commits into
masterfrom
fix/break-pane-3.7-placeholder-name

Conversation

@tony

@tony tony commented Jul 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Fix Pane.break_pane() forcing the window name to libtmux on tmux 3.7a/3.7b when called without an explicit window_name — it now keeps tmux's natural auto-name.
  • Add libtmux.common.get_version_str() — the raw tmux version string with its point-release suffix intact ("3.7a"), for telling patch releases apart where get_version() can't.
  • Refactor tmux-version detection into two plain, typed @functools.cache helpers; get_version output stays byte-identical.

The bug

break_pane() works around a tmux 3.7 NULL-deref (break-pane with no -n crashes the server) by injecting a placeholder -n libtmux and renaming afterward. The gate was has_version("3.7") — but get_version() strips the letter suffix for numeric comparison, so it also matched 3.7a/3.7b, where the crash was already reverted upstream (tmux 166267c8, shipped in 3.7a). With no explicit name, the placeholder leaked as the window name.

Shipped in v0.59.0/v0.60.0. Reproduced on tmux 3.7b: an unnamed break_pane() produced a window named libtmux instead of the running command's name.

The fix

Gate the workaround on the raw version string being exactly "3.7" — which the numeric get_version() can't express. get_version_str() exposes that raw token, and break_pane now compares get_version_str(...) == "3.7", so 3.7a/3.7b (and master, and older tmux) keep tmux's natural window naming.

Version-cache design

get_version and get_version_str are two plain @functools.cache functions deriving from a shared uncached _query_version, each with native .cache_clear()/.cache_info() — no shared-cache machinery, no object.__setattr__, no callable-class wrapper. get_version stays byte-identical across every tmux -V variation (real releases, master, next-3.8, OpenBSD, and both error paths); the OpenBSD/no--V synthetic is distinguished by control flow (an internal exception), not a value sentinel that could collide.

Test plan

  • test_break_pane_no_name_uses_natural_name — regression test, added as a strict xfail then un-xfail'd once the fix landed (the fix commit's CI went red on exactly the 3.7a/3.7b cells, proving it)
  • get_version verified byte-identical to the prior implementation across all tmux -V variations
  • uv run mypy src tests clean (zero # type: ignore); ruff clean
  • get_version / get_version_str render in the API docs with signatures
  • CI grid green on tmux 3.7a/3.7b (added to the matrix in Fix window-name test for tmux 3.7a/3.7b and add them to CI #698)

why: On tmux 3.7a/3.7b, break_pane() without window_name forces the
"libtmux" placeholder as the window name instead of tmux's default.
has_version("3.7") matches 3.7a/3.7b because get_version() strips the
letter suffix, so the 3.7 crash workaround still fires even though the
crash was fixed in 3.7a.

what:
- Add test_break_pane_no_name_uses_natural_name asserting the natural
  name ("sleep"), not the placeholder
- Mark strict xfail gated on has_version("3.7") -- the buggy 3.7 family
- Import has_version in test_pane.py
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.78%. Comparing base (eef9944) to head (c0eda38).

Files with missing lines Patch % Lines
src/libtmux/common.py 60.86% 8 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #699      +/-   ##
==========================================
- Coverage   51.89%   51.78%   -0.11%     
==========================================
  Files          25       25              
  Lines        3623     3638      +15     
  Branches      733      733              
==========================================
+ Hits         1880     1884       +4     
- Misses       1439     1448       +9     
- Partials      304      306       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tony added 4 commits July 3, 2026 19:24
why: break_pane() without window_name forced the "libtmux" placeholder
on tmux 3.7a/3.7b. The crash workaround is gated on has_version("3.7"),
which matches 3.7a/3.7b because get_version() strips the letter suffix
-- but those releases already fixed the crash, so the placeholder leaked
as the window name instead of tmux's default.

what:
- Narrow breaks_without_name to the literal 3.7 release via the raw
  tmux -V string (only checked when has_version("3.7") already matched)
- 3.7a/3.7b/master now keep tmux's auto-generated window name
- Correct the stale "gated on exactly 3.7" comment
why: The natural-name regression test is no longer expected to fail --
the fix keeps tmux's default window name on 3.7a/3.7b, so the strict
xfail now XPASSes and must be removed.

what:
- Remove the strict xfail from test_break_pane_no_name_uses_natural_name
- Revert the now-unused has_version import
why: break_pane() gated its 3.7 crash workaround on has_version("3.7")
and then ran a second, un-memoized `tmux -V` to recover the letter
suffix get_version() strips -- so every call forked `tmux -V` again.

what:
- Extract an uncached _query_version() and layer both get_version and a
  new memoized get_version_str() on it, so `tmux -V` runs once per
  process regardless of how many callers need the raw version
- break_pane() now gates on get_version_str(...) == "3.7" (one memoized
  lookup, no per-call fork); behavior is unchanged
- Flush get_version_str's cache in the autouse test fixture
@tony
tony force-pushed the fix/break-pane-3.7-placeholder-name branch from c94c26b to 0d998db Compare July 4, 2026 11:20
@tony

tony commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

why: break_pane needs the raw tmux version string (letter suffix intact)
to gate its workaround on the literal 3.7 release, which get_version
can't express -- it strips the suffix for numeric comparison. get_version
also re-detected the OpenBSD "<max>-openbsd" synthetic by string value,
which could diverge from the pre-refactor result on a contrived token.

what:
- Route the OpenBSD/no--V fallback through a private
  _TmuxVersionUnavailable signal so get_version applies re.sub to real
  version tokens only -- byte-identical to master across every tmux -V
  variation (real releases, master, next-3.8, openbsd, error paths)
- get_version and get_version_str are two plain @functools.cache
  functions with native cache_clear()/cache_info(), no shared-cache
  machinery
- conftest clears both independent caches; format-tokens documents both
@tony
tony force-pushed the fix/break-pane-3.7-placeholder-name branch from b928ed3 to 71fe4cf Compare July 4, 2026 12:37
why: The new public raw-version helper is a user-facing addition on this
branch and belongs under What's new.

what:
- Add ### What's new note for get_version_str() above the break_pane fix
@tony
tony merged commit fe088f7 into master Jul 4, 2026
15 checks passed
@tony
tony deleted the fix/break-pane-3.7-placeholder-name branch July 4, 2026 12:47
@tony tony mentioned this pull request Jul 4, 2026
4 tasks
tony added a commit to tmux-python/tmuxp that referenced this pull request Jul 4, 2026
libtmux 0.61.0 hardens support for the tmux 3.7 patch line. tmuxp
inherits the fix and adopts the one new helper it benefits from, so
`tmuxp debug-info` now reports the exact tmux patch release.

- **Bump**: libtmux `~=0.60.0` -> `~=0.61.0`; no breaking changes,
  tmux 3.2a-3.6 unaffected.
- **debug-info**: report the raw tmux version, keeping the
  point-release letter (`3.7a`/`3.7b`) via libtmux's new
  `get_version_str()`. `get_version()` strips the letter for numeric
  comparison, so bug reports previously lost the exact patch release
  -- which can differ in behavior.
- **Inherited fix**: libtmux's `Pane.break_pane()` keeps tmux's own
  default window name on tmux 3.7a/3.7b; tmuxp never calls
  `break_pane`, so this is behavior-only.

`plugin.py` keeps `get_version()` for `LooseVersion` version-gate
comparisons, where the letter suffix must not participate in ordering.

Release: https://github.com/tmux-python/libtmux/releases/tag/v0.61.0
Changelog: https://libtmux.git-pull.com/history.html#libtmux-0-61-0-2026-07-04
See also: tmux-python/libtmux#699, tmux-python/libtmux#698
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant