Skip to content

Add captured tmux defaults and explicit ownership - #769

Closed
tony wants to merge 7 commits into
masterfrom
codex/lifecycle-defaults-ownership-20261009
Closed

tony wants to merge 7 commits into
masterfrom
codex/lifecycle-defaults-ownership-20261009

Conversation

@tony

@tony tony commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Continued in #771 on lifecycle-mvp. The replacement includes the latest reviewed preservation and cleanup changes. This draft's discussion and branch remain available.

Ordinary Server() examples use tmux defaults while an external harness can redirect the same code through LIBTMUX_SOCKET_PATH or LIBTMUX_SOCKET_NAME. A server captures its endpoint, executable and child environment at construction, so later environment changes do not redirect its commands.

resource.own() accepts destruction responsibility for an existing server, session, window or pane. Cleanup retains the accepted daemon generation and object ID, checks that generation inside tmux's destructive command dispatch, preserves body and cleanup failures, and permits retry. Session, window and pane contexts use this ownership behavior.

Session, window and pane creation retain a receipt before decoding their snapshots. Failed readback, context entry or generation checks roll back the reported resource against its creating daemon. Nonzero client results, timeouts and interruptions preserve readable creation receipts. A failed rollback exposes its owner through CreationCleanupError; missing receipts produce UnknownCreation without claiming that no resource exists. Existing selection-style window commands still do not supply an explicit created/reused result.

Failed-client output draining and reaping each allow up to 0.1 seconds, including when another process holds a pipe open. Receipt capture observes main-thread interruption between read intervals of at most 0.05 seconds. The reader retains bytes before closing either stream, so a close error preserves both the latest receipt and the original command failure.

Compatibility and preserved documentation

Python 3.10 remains the minimum declared at the PR base. Paired failures use the conditional exceptiongroup dependency below Python 3.11 and the built-in class on later interpreters. The Python 3.10 classifier, type-checker/formatter targets, conditional typing imports and tmux feature-version gates remain. The earlier unrelated compatibility cleanups in options.py, test/random.py, test/retry.py and tests/test_options.py are removed; those files, .github/CONTRIBUTING.md and docs/project/compatibility.md match the base.

The public Session/Window/Pane context methods retain their parameter and return sections. Existing Python docstring doctest source, expected output and options remain. The context-manager page checks remote session, window and pane removal before and after scope exit. Each ordinary example includes its import and normal constructor; the test harness supplies isolation externally.

Behavior changes within this PR's lifecycle/defaults scope: a plain Server context leaves the daemon alive, and whole-daemon destruction requires server.own(). Explicit socket paths must be absolute, ambiguous explicit selectors raise, and tmux_bin is a read-only captured path. Creation and adoption reserve @libtmux_owner_generation; an existing malformed or empty value fails before creation or acceptance.

Validation

  • At 2a1de09befc9382213e93e9ec12f6b23a10890a9 on Linux/WSL with tmux 3.7d, Python 3.10 and 3.12 each passed 211 selected command, creation-recovery, ownership, defaults, example-harness and context-page checks without reruns.
  • Eight inherited-pipe command cases and two real creation cases failed their timing assertions with the old helper. Eight reader-close cases failed before error preservation, and four later-output cases failed before the final drain repair. Those regressions now pass. Separate process probes on both runtimes confirm receipt and diagnostic retention, paired failures and client reaping.
  • The preceding preservation revision passed 395 collected documentation/docstring cases on each interpreter, then reran the seven context-manager cases after its final example clarification. The final close/drain repairs leave all 474 library docstrings and 866 examples unchanged. Compatibility metadata, the lockfile and _compat.py are also unchanged by those repairs.
  • Disabling session, window or pane cleanup makes the corresponding restored documentation assertion fail. Removing the Python 3.10 exception-note fallback fails both interruption tests. Earlier creation/ownership mutations cover receipt recovery, daemon replacement, paired failures and cleanup locks.
  • The preceding preservation wheel installed into a minimal Python 3.10 environment and retained interruption and cleanup errors. Current source passes Ruff, formatting, mypy with the Python 3.10 target and Git whitespace checks. The last documentation-changing revision passed Sphinx directory-HTML with warnings treated as errors.

Remaining work

This remains a draft. Independent preservation review passed at feee66aeb. Fresh review of 2a1de09bef found the late-receipt defect fixed with no new blocker in the two-file repair. It passed 12 close-matrix cases on Python 3.10, 32 close/inherited-pipe/live-creation cases on Python 3.12, both late-output variants on both runtimes, and 16 additional command-path cases per runtime. The reviewer verified the exact source and unchanged documentation/compatibility metadata. Later review findings and their repairs remain visible in the branch history. The newest focused outer runners retain roots when a nonresponding socket leaves daemon exit unverified. The earlier removed-root evidence limitation is not treated as exit proof. The pipe-holder tests and standalone reproductions observe their clients and descendants exiting before directory removal.

Owned daemon startup, bounded discovery and explicit created/reused find-or-create APIs remain pending in Python. Plain Markdown, Astro Markdown/MDX, Sphinx reST/MyST and Python doctest adapters remain in the intended scope; repository doctests do not establish all four integrations. Creation deadlines are per command or identity step; existing snapshot queries are not bounded by a new whole-call deadline. Process-crash recovery belongs to the external harness and is still pending. The full repository suite, supported-tmux matrix and nine-dimension acceptance assessment are not claimed.

tony added 2 commits October 9, 2026 17:18
why: Pane capture can finish before the shell executes a sent command.
The environment and capture tests must observe the expected output.

what:
- Wait for the initial prompt and exact command output
- Bound polling instead of relying on an immediate read or fixed sleep
why: Examples need ordinary tmux defaults and external test isolation.
Cleanup must retain its accepted target and expose failures for retry.

what:
- Capture endpoint, executable and child environment when constructing
  a Server, with explicit selectors ahead of environment defaults
- Add generation-bound ownership for servers, sessions, windows and
  panes, including bounded cleanup and paired body/cleanup errors
- Keep plain Server contexts borrowed and use owners for object scopes
- Test unchanged examples under external socket defaults and document
  the Python 3.11 floor, changed defaults and remaining lifecycle work
@codecov

codecov Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.68559% with 222 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.44%. Comparing base (500f99b) to head (2a1de09).

Files with missing lines Patch % Lines
src/libtmux/lifecycle.py 66.29% 77 Missing and 13 partials ⚠️
src/libtmux/server.py 56.06% 36 Missing and 22 partials ⚠️
src/libtmux/pane.py 52.00% 5 Missing and 19 partials ⚠️
src/libtmux/common.py 84.84% 16 Missing and 4 partials ⚠️
src/libtmux/session.py 65.38% 3 Missing and 6 partials ⚠️
src/libtmux/pytest_plugin.py 70.00% 5 Missing and 1 partial ⚠️
src/libtmux/hooks.py 0.00% 1 Missing and 4 partials ⚠️
src/libtmux/window.py 44.44% 2 Missing and 3 partials ⚠️
src/libtmux/_internal/env.py 92.30% 2 Missing ⚠️
src/libtmux/neo.py 60.00% 2 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #769      +/-   ##
==========================================
+ Coverage   52.37%   54.44%   +2.07%     
==========================================
  Files          26       27       +1     
  Lines        3729     4180     +451     
  Branches      747      810      +63     
==========================================
+ Hits         1953     2276     +323     
- Misses       1472     1582     +110     
- Partials      304      322      +18     

☔ 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 5 commits October 9, 2026 17:54
why: Creation could return a tmux ID and then fail during snapshot
decoding or context entry, leaving the resource behind. A later daemon
at the same endpoint could also be accepted as the original creation.

what:
- Retain daemon and object receipts on the creating connection
- Roll back failed materialization against the recorded generation
- Preserve receipt bytes across client timeout and interruption
- Expose failed rollback owners without replacing the original error
- Refuse replacement daemons before returning or entering the scope
why: The lifecycle changes do not require raising the Python floor or
removing the reference detail and cleanup examples users already have.

what:
- Restore Python 3.10 metadata, typing fallbacks and lock branches
- Use conditional exception-group support and compatible error notes
- Restore context-method reference sections and cleanup doctests
- Keep unrelated typing and lint cleanup outside the lifecycle diff
- Verify examples without requiring pidfd support from the interpreter
why: A descendant retaining an output pipe makes the post-timeout
communicate call wait beyond the command deadline. Receipt capture must
still finish before creation rollback handles an interruption.

what:
- Give draining and reaping finite cleanup intervals
- Let the receipt worker observe main-thread interruption between reads
- Retain partial output, text decoding and paired failures
- Test inherited pipes and real creation rollback on Python 3.10 and 3.12
why: A pipe-reader close failure can replace an interruption and skip
client reaping. A close failure inside communicate can also discard
output already captured before the timeout.

what:
- Retain the latest captured output across drain and close failures
- Close the remaining reader and attempt the bounded client wait
- Preserve the original failure alongside each cleanup failure
- Exercise both readers with timeout, interruption and inherited pipes
why: A close error during the final subprocess drain can discard a
receipt received after the preceding timeout snapshot, preventing
rollback of a known creation.

what:
- Retain bytes with a selector reader before closing output streams
- Keep bounded cancellation cleanup, grouped errors and client reaping
- Exercise late output with both reader errors, timeout and Ctrl-C
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