# Ownership and cleanup

Source: https://libtmux.org/en/tmux/topics/context-managers/

> Scope-based cleanup for tmux objects, and when your program must kill them explicitly.

A tmux session, window, or pane normally remains until you kill it. Scope-based
cleanup can kill it when your code leaves a block, including after an exception.
See [Workspaces](https://libtmux.org/en/tmux/concepts/workspaces/) for a temporary layout example.

Python provides context managers for tmux objects. C# provides ownership
scopes for servers, sessions, and windows. Other ports require explicit cleanup
or offer guards for test servers:

| Port | Server | Session | Window | Pane |
|------|:------:|:-------:|:------:|:----:|
| Python | yes | yes | yes | yes |
| C# | yes | yes | yes | - |
| Java | closes conn. | - | - | - |
| Rust | test-only | - | - | - |
| C++ | test-only | - | - | - |
| TypeScript | - | - | - | - |
| Go | - | - | - | - |
| Swift | - | - | - | - |
"test-only" means a guard owns an entire disposable test server. Java's
[`AutoCloseable`](<https://docs.oracle.com/en/java/javase/21/docs/api/java/lang/AutoCloseable.html>) server releases its transport but leaves tmux running. A dash
means no built-in cleanup scope is listed for that object; use an explicit kill
call with the cleanup mechanism appropriate to your language.

<a id="python-every-level-including-nested"></a>

## Nested context managers

Python's [`Server`](<https://libtmux.org/en/py/latest/reference/libtmux-server/>), [`Session`](<https://libtmux.org/en/py/latest/reference/libtmux-session/>), [`Window`](<https://libtmux.org/en/py/latest/reference/libtmux-window/>), and [`Pane`](<https://libtmux.org/en/py/latest/reference/libtmux-pane/>) support context managers.
Entry returns the existing object; exit kills it, including when the block
raises:

```python
with Server() as server:
    with server.new_session() as session:
        with session.new_window() as window:
            with window.split() as pane:
                pane.send_keys('echo "Hello"')
                # everything above is killed on the way out, in reverse order
```

Nested scopes exit in reverse order: pane, window, session, then server.

<a id="net-an-explicit-ownership-type-stopping-at-window"></a>

## Owned sessions and windows

C#'s [`OwnedSessionScope`](<https://libtmux.org/en/csharp/latest/reference/libtmux-ownedsessionscope/>) and [`OwnedWindowScope`](<https://libtmux.org/en/csharp/latest/reference/libtmux-ownedwindowscope/>) wrap the created object and
implement [`IAsyncDisposable`](<https://learn.microsoft.com/dotnet/api/system.iasyncdisposable>). The [`Session`](<https://libtmux.org/en/csharp/latest/reference/libtmux-session/>) and [`Window`](<https://libtmux.org/en/csharp/latest/reference/libtmux-window/>) handles themselves are
not disposable:

```csharp
await using OwnedSessionScope session = await server.CreateOwnedSessionAsync();
await using OwnedWindowScope window = await session.Value.CreateOwnedWindowAsync();

await window.Value.SendTextAsync("echo hello");
// window, then session, killed on the way out
```

For tests that need an owned pane,
[`TmuxTestFactory.CreateHierarchyAsync()`](<https://libtmux.org/en/csharp/latest/reference/libtmux-testing-tmuxtestfactory-createhierarchyasync/>) returns a [`TemporaryHierarchyScope`](<https://libtmux.org/en/csharp/latest/reference/libtmux-testing-temporaryhierarchyscope/>)
containing a private server, session, window, and pane. Disposing it kills the
server.

<a id="java-server-is-closeable-but-closing-one-doesnt-kill-it"></a>

## Closing a server connection

Java's [`Server`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-server-server/>) implements [`AutoCloseable`](<https://docs.oracle.com/en/java/javase/21/docs/api/java/lang/AutoCloseable.html>). Exiting `try (Server server =
Server.open(config))` releases the owned transport while tmux and its sessions
remain running. Kill sessions, windows, panes, or the server explicitly when
your program owns their cleanup.

```java
ServerConfig config = ServerConfig.builder()
        .endpoint(ServerEndpoint.socketPath(socket))
        .build();

try (Server server = Server.open(config)) {
    Session session = server.newSession("demo");
    Window window = session.newWindow("build");
    Pane pane = window.split();

    pane.sendLine("echo hello from libtmux");
    // session, window, and pane all outlive this block: only the
    // connection this `server` handle held is released on the way out.
}
```

<a id="rust-no-async-drop-so-cleanup-is-explicit-or-best-effort"></a>

## Explicit asynchronous cleanup

Rust's [`Drop`](<https://doc.rust-lang.org/std/ops/trait.Drop.html>) is synchronous and cannot await an async tmux kill. Use
explicit shutdown when you need to observe cleanup failures:

- **`kill(self)` consumes the handle.** Session, window, and pane kill methods
  take `self` by value, preventing subsequent use of that handle.
- **[`libtmux::test::TestServer`](<https://libtmux.org/en/rs/latest/reference/test-testserver/>) provides a test guard.** Call
  [`TestServer.shutdown`](<https://libtmux.org/en/rs/latest/reference/test-testserver-shutdown/>) to handle cleanup errors. Its [`Drop`](<https://doc.rust-lang.org/std/ops/trait.Drop.html>) implementation
  makes a synchronous cleanup attempt.

```rust
use libtmux::test::TestServer;

let guard = TestServer::new().await?;
let server = guard.server();

let session = server.new_session("work").await?;
session.new_window("editor").await?;

// Await shutdown to handle cleanup errors.
guard.shutdown().await?;
```

<a id="c-raii-exists-but-only-for-a-private-test-server"></a>

## Owning a test server

C++'s [`Session`](<https://libtmux.org/en/cxx/latest/reference/libtmux-session/>), [`Window`](<https://libtmux.org/en/cxx/latest/reference/libtmux-window/>), and [`Pane`](<https://libtmux.org/en/cxx/latest/reference/libtmux-pane/>) are non-owning values; destroying a handle
does not kill its tmux object. [`libtmux::test::ScopedTmuxServer`](<https://libtmux.org/en/cxx/latest/reference/libtmux-test-scopedtmuxserver/>), in the
separate `testing` CMake component, owns a private test server and its temporary
socket directory:

```cpp
auto fixture = libtmux::test::ScopedTmuxServer::start(
    {.socket_namespace = libtmux::test::SocketNamespace::consumer("my-suite")});
// fixture killed, and its tree removed, when this scope ends:
// even if the test that follows fails
```

<a id="typescript-go-swift-no-built-in-scoping-at-all"></a>

## Release connections and kill owned sessions

Control connections and notification streams implement `[Symbol.asyncDispose]`.
`await using` releases those handles; it leaves the watched session and panes
running. Use `finally` to kill a session your program owns:

```typescript
const session = await server.newSession({ name: "work" });
try {
  const window = await session.newWindow({ name: "editor" });
  await window.panes.at(0)?.sendKeys("echo hi");
} finally {
  await session.kill();
}
```

## Defer cleanup with a fresh context

A [`Session`](<https://libtmux.org/en/go/latest/reference/tmux-session/>), [`Window`](<https://libtmux.org/en/go/latest/reference/tmux-window/>), or [`Pane`](<https://libtmux.org/en/go/latest/reference/tmux-pane/>) handle does not own its tmux object. Dropping
the value leaves tmux running. Register cleanup after successful creation and
use a separate, bounded context so cancellation of the work cannot prevent
cleanup. Return cleanup failures along with any work failure:

```go
func temporarySession(ctx context.Context, server tmux.Server) (err error) {
    session, err := server.NewSession(ctx, tmux.NewSessionRequest{})
    if err != nil {
        return err
    }
    defer func() {
        cleanup, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        err = errors.Join(err, session.Kill(cleanup))
    }()
    _, err = session.SearchWindows(ctx, nil)
    return err
}
```

This function uses `context`, `errors`, `time`, and the [`tmux`](<https://libtmux.org/en/go/latest/reference/#tmux>) package. It owns
only the session it creates. Do not kill a shared server as session cleanup.

[`ControlClient`](<https://libtmux.org/en/go/latest/reference/tmux-controlclient/>), [`PaneObservation`](<https://libtmux.org/en/go/latest/reference/tmux-paneobservation/>), and [`NotificationStream`](<https://libtmux.org/en/go/latest/reference/tmux-notificationstream/>) implement
[`io.Closer`](<https://pkg.go.dev/io#Closer>). Close those resources separately from killing tmux objects.
For tests, [`tmuxtest.NewServer`](<https://libtmux.org/en/go/latest/reference/tmuxtest-newserver/>) registers isolated server cleanup with the
Go test runner.

## Kill objects your program owns

Session, window, and pane values are non-owning. Call
[`try await server.kill(session)`](<https://libtmux.org/en/swift/latest/reference/server-kill(_-)/>) or the corresponding window or pane overload
when cleanup is required. Perform cleanup on both success and failure paths;
Swift's synchronous `defer` cannot await a tmux command.

<a id="what-this-means-in-practice"></a>

## Testing cleanup

Use explicit cleanup for objects whose handles have no disposal hook. For an
entire disposable test server, prefer your port's test fixture or server guard;
see [Testing with libtmux](https://libtmux.org/en/tmux/guides/testing-with-libtmux/).
