Skip to content

Raise the core tmux minimum to 3.3a - #69

Draft
tony wants to merge 31 commits into
masterfrom
bump-min-tmux-000
Draft

tony wants to merge 31 commits into
masterfrom
bump-min-tmux-000

Conversation

@tony

@tony tony commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

The minimum daemon version is tmux 3.3a across Java, Kotlin, Scala, MCP and workspace entry points. Ordinary reads, writes, raw commands and batches validate the connected daemon and carry an exact native version guard before requested effects. An unused endpoint validates the executable before creation; standalone version/help diagnostics remain available.

The admission probe is run-shell -C '<marker>#{version}'. display-message -p '#{version}' fires a user's after-display-message hook on every tmux release, so the probe itself had effects before a refusal could stop anything. run-shell has no after-hook, and the parse failure of its -C argument carries the version on stderr as unknown command: <marker><version> without reaching command-error. Another request's marker, a plain success or any other stderr is not an answer.

This removes obsolete detached-size and prompt-inventory adaptations while preserving incarnation fences, request deadlines, process ownership and compatibility required within the supported range. The CI matrix starts at 3.3a. External Python builders and shells require the coordinated Python core admission change; tmuxp alone does not provide it. CI explicitly qualifies released tmuxp 1.74.0 with Python source 26123fe7e4fc497a9ddb454ce654e468afe916ab on PYTHONPATH, asserting the import path and declared floor. Released tmuxp still pins Python libtmux 0.61, so this overlay does not qualify a coherent installed package pair; the coordinated tmuxp dependency change remains pending.

Validation, on this Linux/WSL host:

  • AdmissionHooksIntegrationTest arms after-display-message and, from 3.5, command-error, runs admitted calls and requires neither to fire; a control call fires each hook the ordinary way. It passes on 3.3a (the command-error case is skipped, the hook does not exist there), 3.5, 3.6b and master (next-3.9). Restoring display-message as the probe fails the after-display-message case on 3.3a and 3.5 ("admission ran the user's after-display-message hook").
  • Unit cases pin the probe shape, a foreign marker, master and other unknown suffixes, and next-3.9; the test doubles of every module answer the new probe.
  • VersionGrammarTest holds admission to the canonical version vectors (admit, below the floor, unparseable, and the order next-X.Y < X.Y-rc < X.Y-rcN < X.Y < X.Ya); the floor compares candidates strictly through TmuxSupport.compareStrictly, while TmuxVersion.compareTo keeps rating a candidate as its release for feature gates. Making the strict comparison ignore candidates fails the ordering and neighbour cases.
  • isAlive answers false when tmux refuses the admission probe (a daemon exiting under it), which WaitForIntegrationTest hit on the JDK 27 lane.
  • The whole real-tmux integration lane for 3.3a passes. The :libtmux, :libtmux-junit5, :libtmux-workspace and :libtmux-scala-cats test tasks pass, as do :docs:test and the workspace CLI ExecutionTest with the Python source overlay.
  • Fixtures that assumed the old probe were corrected: a stalled-control wrapper that blocked on any -C argument (it blocked the probe), a probe count keyed by display-message, a documentation snippet keyed by first verb, and two lifecycle programs whose one-second budget did not fit a probe per call.
  • The MCP WaitingForTextTest cases failed on the JDK, 3.5 and master lanes with PRESENT_AT_ENTRY: keys sent to a shell that is still starting are echoed by the terminal and the prompt is drawn glued to that echo. Trunk's libtmux-mcp:test is restored FROM-CACHE in CI, so its own run never exercised them. With 20 busy loops on a 10-core host, bash and a long prompt, the trunk fixtures fail sendThenWaitDoesNotMatchTheEchoedCommandLine; waiting for the pane to settle before typing makes RunningCommandsTest, TypingTest and WaitingForTextTest pass under the same load.

Control acquisition has a bounded preflight; endpoint pinning and unsafe optional feature boundaries belong to the separate optional-feature change. Admission assumes native built-in semantics, including if-shell and run-shell; configurations redefining them through command-alias are outside this qualification. Reads that print a version with display-message inside an already admitted call (option decoding, printed text) are unchanged. Local evidence covers Linux/WSL; the other supported tmux releases, JDKs and macOS are CI qualification. Clojure draft PR20 is a separate integration follow-up.

Closes #67.

tony added 17 commits October 16, 2026 21:40
why: A newer client can reach an older daemon, and the socket can change
after a version probe. Refuse unsupported ordinary Server operations
before requested effects while retaining existing incarnation and
ownership fences.

what:
- Admit the daemon's complete patch-aware version; consult a fresh
  executable report only on an unused endpoint. Preserve standalone -V
  and -h diagnostics.
- Prepend a native version guard without rewriting raw argv, batches,
  existing guards or stdin. Use -N after live admission and on absent
  non-starting paths.
- Share bounded request execution with carrier probes without exposing a
  remote-carrier ProcessTransport instance. Retain request budgets,
  borrowed ownership and dispatch certainty.
- Remove detached-size and prompt-inventory adaptations made obsolete by
  the 3.3a floor. Keep compatibility still needed inside the supported
  range.
- Add admission, replacement, disappearance, malformed-version and
  carrier regressions. Update recording fixtures without weakening data
  batching, input cohort or native PID guard checks.
- Regenerate the operations reference and Kotlin ABI for call-time
  compatibility preflight. Preserve JDK process-argument inspection with
  an ASCII-space native no-op.
why: Python builders and shells execute outside the Java dispatcher. A
Java preflight alone cannot enforce the core floor through an obsolete
installed Python libtmux backend.

what:
- Check the selected daemon, or the executable only when absent, before
  external builder and shell entry.
- Require the Python backend's declared core floor to be at least 3.3a
  before loading builder or shell code. Repeat the handshake in every
  child process.
- Report the selected interpreter and compatible backend requirement
  without inventing a release pin. The coordinated Python core change is
  PR779.
- Remove the CLI's obsolete retry without detached session dimensions.
- Cover old-client refusal, obsolete backend refusal before effects,
  qualified backend success and real tmuxp 1.74.0 extensions using the
  coordinated Python source.
why: Facade transports must distinguish admission from requested work
so their lifecycle, cancellation and cohort assertions keep measuring
the operation under test.

what:
- Answer exact daemon-version probes in Kotlin, Cats and workspace
  fixtures without counting them as application effects.
- Inspect original commands after the separate native guard in MCP and
  fixture recording transports.
- Retain the native wait-process readiness and cancellation assertions,
  staging-session cleanup and PID incarnation checks.
why: The supported matrix and user-facing requirements must agree with
core admission. The extra admission probe also changes process-count
guidance.

what:
- Remove 3.2a and unpatched 3.3 from required CI and local matrix lanes;
  keep the remaining supported and preview lanes.
- Update JVM facade, example, workspace CLI and contributor requirements
  to 3.3a.
- Describe version-probe reports and the extra process before ordinary
  command groups, captures and filtered reads.
- Update benchmark generator explanations while leaving historical
  generated measurements at their recorded commit.
why: Users must upgrade the running daemon as well as the client, and
external Python backends need their own command-level admission.

what:
- Describe daemon-first refusal, unused endpoints, diagnostics and full
  version-token handling.
- Document call-time compatibility preflight and the bounded control-
  startup check without claiming endpoint pinning.
- Link the unreleased Python core PR and distinguish its dispatcher
  guarantee from Java preflight.
- State the requirement that configured command aliases preserve the
  native built-ins used for admission.
why: The raised daemon minimum changes which installations and Python
backends can run JVM products.

what:
- Record the core 3.3a requirement under Unreleased, reference issue 67
  and link the migration instructions.
why: tmux 3.3a prints its executable basename before the version. A
renamed or symlinked supported binary must still pass cold admission.

what:
- Accept the exact configured basename or canonical tmux banner
- Keep full version-token checks and bare daemon reports unchanged
- Cover mismatched names, malformed releases and daemon authority
why: A delivered marker can cross terminal rows and disappear from a
literal substring assertion even though both intended panes received it.

what:
- Join soft-wrapped rows in the shared typing capture helper
- Reproduce wrapping with ready cat consumers at a fixed pane width
- Retain effective-recipient and disabled-pane assertions
why: Released tmuxp pins Python libtmux before core admission, so the
Python bridge correctly refuses the existing CI installation.

what:
- Install released tmuxp normally and overlay reviewed Python source
- Pin the source commit and assert its import path and 3.3a floor
- Apply the same qualification to JDK and supported tmux lanes
why: Cold admission and delivered-text assertions depend on native
behavior that differs from a simple version or substring assumption.

what:
- Record renamed tmux 3.3a version banners from copies and symlinks
- Record soft-wrap capture results on native 3.3a and 3.7d
why: The reviewed source overlay qualifies command admission without
claiming a released or coherently installed tmuxp and libtmux pair.

what:
- Link the exact Python source used by the CI overlay
- State the import-path check and pending tmuxp dependency update
why: A user may configure a renamed or symlinked tmux 3.3a executable
when creating the first session on an unused endpoint.

what:
- Clarify that cold admission accepts configured executable names
why: A wrapped completion marker or output arriving before a wait's
entry capture made JDK checks fail for unrelated fixture timing.

what:
- Join soft-wrapped completion capture and force that regression.
- Release shell output after the real wait entry capture.
- Preserve cold and warm echo assertions without a fixed delay.
why: Admission asked the daemon for `display-message -p '#{version}'`.
display-message fires a user's `after-display-message` hook on every
tmux release, so the probe itself had effects before a refusal could
stop anything.

what:
- Probe with `run-shell -C '<marker>#{version}'`, marker
  `libtmux-version-<random hex>-`. The daemon expands the format and
  fails to parse the result as a command, so the version is read from
  `unknown command: <marker><version>` on stderr. run-shell has no
  after-hook, and a parse failure inside its -C callback never reaches
  command-error. The live endpoint still gets -N.
- Another request's marker, a plain success, or any other stderr is not
  an answer: absent-endpoint wording still means an unused endpoint,
  anything else is a rejected probe.
- Test doubles (core, MCP, workspace, junit5, integration, Kotlin and
  Cats fake binaries) answer the new probe through VersionProbes;
  FakeTmux answers it for fixtures and carriers.
- AdmissionHooksIntegrationTest arms after-display-message and, on
  3.5+, command-error hooks, runs admitted calls and requires neither
  to fire, with a control that fires each hook the ordinary way.
- Unit cases pin the probe shape, a foreign marker, `master` and other
  unknown suffixes, and `next-3.9`.

Verified: AdmissionHooksIntegrationTest passes on 3.3a (command-error
case skipped, no such hook), 3.5, 3.6b and master (next-3.9). Restoring
display-message as the probe fails the after-display-message case on 3.3a
and 3.5 ("admission ran the user's after-display-message hook").
:libtmux, :libtmux-junit5, :libtmux-workspace and :libtmux-scala-cats
test tasks pass.
why: The operations guide said admission emits a display-message probe
report, which is no longer the command sent.

what:
- Name run-shell and state that admission fires none of a user's
  after-* or command-error hooks.
why: JDK 25/27 and the 3.5 and master lanes failed four
WaitingForTextTest cases with PRESENT_AT_ENTRY. Keys sent to a shell
that is still starting are echoed by the terminal and the shell draws
its prompt straight after that echo on the same row, so the echo is
glued to the prompt and no longer stands apart as a typed line. That is
not the case these tests pin. The previous follow-up replaced the
fixtures with a capture-gated release that still sent keys into the
starting shell and failed the same way.

what:
- Restore the trunk fixtures of WaitingForTextTest.
- Wait for the pane to show something and hold still for half a second
  before typeAndSubmit and send write to it.

Verified with 20 busy loops on a 10-core host, bash with a long prompt,
tmux 3.5 lane. The trunk fixtures fail sendThenWaitDoesNotMatchTheEchoedCommandLine
(PRESENT_AT_ENTRY) and the previous follow-up fails four cases with the
same outcome; with the wait, RunningCommandsTest, TypingTest and
WaitingForTextTest pass under the same load. Trunk's libtmux-mcp:test is
restored FROM-CACHE in CI, so its own run never exercised these cases.
Records that admission reads the daemon version with run-shell -C and fires none of a user's hooks.
@tony
tony force-pushed the bump-min-tmux-000 branch from 48122c0 to f4c28f4 Compare October 17, 2026 09:06
tony added 3 commits October 17, 2026 04:24
Applies the project formatter to the capture call added by the settle wait; no behaviour change.
why: The stalled-control fixture wrapped tmux and blocked on a FIFO for
any argument equal to `-C`. The admission probe is now
`run-shell -C '<marker>#{version}'`, so the wrapper blocked the probe
and Server.cmd timed out before the control attach was ever reached
("cancelling control ends promptly and kills the stalled process"
failed with DispatchException$TimedOut). The process scan matched the
transient probe process the same way.

what:
- Block on `attach-session`, which only the control attach carries.
- Look for the stalled client by `-C` together with `attach-session`.

Verified: the case failed on this branch before the change and passes
after it (`:libtmux-kotlin:test --tests '*WaitsTest*'`).
why: aFilterThatMatchesNothingCostsOneFilteredRead counted the
version probes as `display-message` requests. The probe is now
`run-shell -C`, so the count was zero and the case failed on every
real-tmux lane.

what:
- Count the `run-shell` probes; the total of eight requests, four of
  them probes, is unchanged.

Verified on the tmux 3.3a lane (`--tests '*Pushdown*'`).
tony added 2 commits October 17, 2026 05:28
why: Two CatsLifecycleSuite programs ran under a one second
`timeout` for the whole program. Every call now spends a version
probe process before its own command; on the tmux 3.3a lane the full
suite then exceeded the second while each test alone passed. The
programs carry their own 800 ms per-wait deadlines, so a real hang
still fails.

what:
- Raise the outer budget of the shared-wait and owned-release programs
  to five seconds.

Verified: the lane failed both programs before the change and passes
after it (`:integration-tests:test-tmux-3.3a`, whole lane).
why: The operating guide's observer snippet read `seen.get("display-message")`
and passed only because the admission probe was itself a display-message
request whose report put its first verb. The probe is now a run-shell
request, and the call's own report starts with the `if-shell` version
guard, so the key was never written and the documentation check failed.

what:
- Key the map by the last verb of each report, which is the requested
  command for a call and `run-shell` for admission.

Verified with `:docs:test` after the change.
tony added 2 commits October 17, 2026 06:51
why: WaitForIntegrationTest.aServerDyingUnderAPaneWaitIsNotATimeout
failed on the JDK 27 lane: the pane wait asked `isAlive()` while the
server was exiting, admission's version probe was refused with "server
exited unexpectedly", and the rejection escaped as an exception instead
of the documented false, so the wait reported an error and not
SERVER_GONE.

what:
- isAlive(Duration) answers false when tmux refuses the admission probe,
  as it already does when tmux refuses its own pid read; transport
  failures still throw, and a raw command is still rejected.
- A unit case covers a daemon that exits under the probe.

Verified: the case fails without the catch ("tmux version probe failed
(exit 1): server exited unexpectedly") and passes with it; the
WaitForIntegrationTest class passes three times on the 3.4 lane.
why: The admission floor should treat every version string tmux has
reported the same way in every port: `next-X.Y < X.Y-rc < X.Y-rcN <
X.Y < X.Ya`, bare `-rc` real and admitted, and forms no tmux reported
(`3.3a-master`, `3.3a-dev`, `next-3.3a`, `next-3.9-master`) refused.
TmuxVersion.compareTo rates a candidate equal to its release because
feature gates measure what the candidate ships, so a policy floor cannot
use it.

what:
- TmuxSupport.compareStrictly orders a candidate below its release and a
  bare rc below rc1; the 3.3a floor uses it, replacing a special case
  for the floor's own candidate.
- VersionGrammarTest runs the vectors: admit, below the floor,
  unparseable, the release-train ordering, and the control-gate
  neighbours (`next-3.7` and `3.7-rc2` below 3.7; `3.8-rc2` and
  `next-3.8` above).

Verified: making compareStrictly ignore candidates fails the ordering
and the neighbour cases.
@tony
tony force-pushed the bump-min-tmux-000 branch from e967af3 to d34e6d0 Compare October 17, 2026 11:51
why: TmuxVersion.compareTo rated `3.4-rc` equal to `3.4` while equals
said they differ, so a sorted set collapsed them and Cats' Order
reported Eq. Gates were split too: range gates (`atLeast`) counted a
candidate as its release while exact gates used equals, so one
candidate had the feature and not the bug. parse accepted any prefix
(`3.3a garbage`, `3.3a-master`, `3.3abc`).

what:
- compareTo is the strict order `next-X.Y < X.Y-rc < X.Y-rcN < X.Y <
  X.Ya`, which agrees with equals. The support floor and the control
  gate use it directly; TmuxSupport.compareStrictly goes.
- atLeast still counts a candidate as its release (a gate asks what
  the build ships); sameRelease does the same for gates on one
  release's own bug, and the `$`-escape and 3.7 break-pane gates use it.
- parse is anchored to the canonical grammar with one patch letter.
- VersionGrammarTest holds parse to the unparseable vectors and checks
  that the order, equals, atLeast and sameRelease agree as documented.

Verified: the unit suites of the core, junit5, workspace and Cats
modules pass.
tony added 6 commits October 17, 2026 08:57
… refuses

why: Every call spent a version probe process before its own command,
doubling process spawns, and a daemon replaced between the probe and
the dispatch surfaced as TargetGoneException, so a caller that retries
stale handles retried into the same refusal.

what:
- Admission remembers the version it read for each transport and
  endpoint. The fence every dispatch carries is what keeps that honest:
  a replaced daemon fails the fence before any command runs, the entry
  is dropped and the daemon is read again, so an older replacement
  raises the version error and a supported one is used. A daemon that
  has gone answers without running anything and is read again too.
- A replacement between a fresh read and the dispatch still raises
  TargetGoneException, after one more read.
- Cases: the second call reuses the first's admission; a refused fence
  reads again; a daemon replaced by 3.2a raises UnsupportedFeatureException
  with no effect. The tests that counted probes (snapshot, handle,
  fixture, field reads, filter pushdown, mirror rebuild) count one per
  daemon. The mirror case builds on a pane without a shell, so a
  prompt cannot retitle it.

Verified: reading the daemon again is replaced by TargetGone in a
deliberate break, which fails both new cases; the whole 3.3a lane
passes.
…lane

why: Refusal below the floor was tested only against FakeTmux, so a
change in how `run-shell -C` fails on 3.2a or 3.3 would have passed CI.
The 3.2a and 3.3 lanes had simply been deleted, and there was no
released 3.8 lane for a 3.8-only failure to show in.

what:
- AdmissionLaneIntegrationTest starts the lane's own binary on a private
  socket, makes a sentinel session, option and buffer with that
  binary's client, then drives the library: below 3.3a every call (raw,
  batch, typed, lenient list, buffers, kill) raises
  UnsupportedFeatureException and the sentinels are unchanged; on a
  supported release the same calls work.
- `rejectionLanes` (3.2a, 3.3) run only that class and belong to
  testTmuxMatrix; the workflow builds them as "must be refused" lanes.
  `3.8` joins the preview lanes beside 3.8-rc2 and master.

Verified on the real binaries: the class passes on 3.2a, 3.3 and 3.3a;
with the floor not enforced it fails on both 3.2a and 3.3 ("Expected
UnsupportedFeatureException to be thrown, but nothing was thrown").
…ides

why: The batching guide cited "64 versus 5" processes from a benchmark
that predates admission, and said every request costs two; two pages
still named 3.2a as the start of the supported range.

what:
- State the cost without a ratio: one process per request once the
  daemon is known, and link the measured rows.
- Say 3.3a through 3.7c in the tmux behaviour page, and cite only the
  releases the streaming measurement used.
Names compareTo, atLeast, sameRelease and the stricter parse for callers of TmuxVersion.
Notes that a known daemon is not probed again, the version error for an older replacement, and the strict order and parse. References issue #67.
…s send

why: The cohort, typing and run cases asserted a version probe request
between the last preflight and the send, or ahead of the snapshot. A
known daemon is no longer probed again, so the send follows the
preflight directly.

what:
- The send is the request right after the final preflight, and the
  cohort snapshot is the only request on an admitted daemon.

Verified with :libtmux-mcp:test.

This branch has not been deployed

No deployments
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.

Require tmux 3.3a for core operations

1 participant