Developing and Testing

The tests live in tests/, written with pytest. They run against a real tmux server on a separate socket ($ tmux -L test_case), so they never disturb your own sessions.

Install the latest code from git

Get the source

Check out the code from GitHub:

$ git clone [email protected]:tmux-python/tmuxp.git
$ cd tmuxp

Bootstrap

The easiest way to set up a dev environment is with uv, which manages the virtualenv and Python dependencies for you. (See uv’s documentation to install uv itself.)

Create the virtualenv and install everything locked in uv.lock:

$ uv sync --all-extras --dev

To refresh those packages later:

$ uv sync --all-extras --dev --upgrade

Then prefix any Python command with uv run:

$ uv run [command]

That’s it — you’re ready to code.

Advanced: manual virtualenv

Prefer to manage the virtualenv yourself? Create one:

$ virtualenv .venv

Activate it in your current shell:

$ source .venv/bin/activate

Install tmuxp in editable mode, so your edits take effect immediately:

$ pip install -e .

With a uv-managed project, add the checkout as an editable dev dependency instead:

$ uv add --dev --editable .

Prefer a one-off, pipx-style run while you hack? Call tmuxp through uvx:

$ uvx tmuxp

Test runner

pytest runs the tests. Inside the virtualenv, the tmuxp command and a project-local python are already on your PATH.

Rerun on file change

Watch files and re-run tests on every save, via pytest-watcher:

$ just start

Manual

$ uv run py.test

Or:

$ just test

pytest options

Pass extra arguments through PYTEST_ADDOPTS. See the pytest usage docs for everything it accepts.

Verbose:

$ env PYTEST_ADDOPTS="--verbose" just start

Pick a file:

$ env PYTEST_ADDOPTS="tests/workspace/test_builder.py" just start

Drop into a single test and stop on the first error:

$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" \
    just start

Drop into pdb on the first error:

$ env PYTEST_ADDOPTS="-x -s --pdb" just start

With ipython installed:

$ env PYTEST_ADDOPTS="--pdbcls=IPython.terminal.debugger:TerminalPdb" just start

Manual invocation

Test a single file:

$ py.test tests/test_config.py

A single test inside it:

$ py.test tests/test_config.py::test_export_json

Several at once, space-separated:

$ py.test tests/test_{window,pane}.py tests/test_config.py::test_export_json

Visual testing

You can watch the suite build sessions in real time by keeping a client open in a second terminal.

Terminal 1 — start a server on the test socket:

$ tmux -L test_case

Terminal 2 — from the tmuxp checkout (and your virtualenv, if you use one), run the builder tests:

$ py.test tests/workspace/test_builder.py

Terminal 1 flickers as sessions build before your eyes — the building tmuxp normally hides from users.

Testing options

Set RETRY_TIMEOUT_SECONDS if certain workspace-builder tests are stubborn on your machine, e.g. RETRY_TIMEOUT_SECONDS=10 py.test. CI runs the same suite:

name: tests

on: [push, pull_request]

jobs:
  build:
    # Don't run twice for internal PRs from our own repo
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.14']
        tmux-version: ['3.2a', '3.3a', '3.4', '3.5', '3.6', '3.7', 'master']
    steps:
      - uses: actions/checkout@v6

      - name: Install uv
        uses: astral-sh/setup-uv@v7
        with:
          enable-cache: true

      - name: Set up Python ${{ matrix.python-version }}
        run: uv python install ${{ matrix.python-version }}

      - name: Test runtime dependencies
        run: |
          uv run --no-dev -p python${{ matrix.python-version }} -- python -c '
          from tmuxp import _internal, cli, workspace, exc, log, plugin, shell, types, util, __version__
          from tmuxp._internal import config_reader, types
          from tmuxp.cli import convert, debug_info, edit, freeze, import_config, load, ls, shell, utils
          from tmuxp.workspace import builder, constants, finders, freezer, importers, loader, validation
          from libtmux import __version__ as __libtmux_version__
          print("tmuxp version:", __version__)
          print("libtmux version:", __libtmux_version__)
          '

      - name: Install dependencies
        run: uv sync --all-extras --dev

      - name: Setup tmux build cache for tmux ${{ matrix.tmux-version }}
        id: tmux-build-cache
        uses: actions/cache@v5
        with:
          path: ~/tmux-builds/tmux-${{ matrix.tmux-version }}
          key: tmux-${{ matrix.tmux-version }}

      - name: Build tmux ${{ matrix.tmux-version }}
        if: steps.tmux-build-cache.outputs.cache-hit != 'true'
        run: |
          sudo apt install libevent-dev libncurses5-dev libtinfo-dev libutempter-dev bison
          mkdir ~/tmux-builds
          mkdir ~/tmux-src
          git clone https://github.com/tmux/tmux.git ~/tmux-src/tmux-${{ matrix.tmux-version }}
          cd ~/tmux-src/tmux-${{ matrix.tmux-version }}
          git checkout ${{ matrix.tmux-version }}
          sh autogen.sh
          ./configure --prefix=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }} && make && make install
          export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH
          cd ~
          tmux -V

      - name: Lint with ruff check .
        run: uv run ruff check .

      - name: Format with ruff
        run: uv run ruff format . --check

      - name: Lint with mypy
        run: uv run mypy .

      - name: Print python versions
        run: |
          python -V
          uv run python -V

      - name: Test with pytest
        continue-on-error: ${{ matrix.tmux-version == 'master' }}
        run: |
          sudo apt install libevent-2.1-7
          export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH
          ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin
          tmux -V
          uv run py.test --cov=./ --cov-report=xml --verbose

      - uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  release:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
    permissions:
      id-token: write
      attestations: write

    strategy:
      matrix:
        python-version: ['3.14']

    steps:
      - uses: actions/checkout@v6

      - name: Install uv
        uses: astral-sh/setup-uv@v7
        with:
          enable-cache: true

      - name: Set up Python ${{ matrix.python-version }}
        run: uv python install ${{ matrix.python-version }}

      - name: Install dependencies
        run: uv sync --all-extras --dev

      - name: Build package
        run: uv build

      - name: Publish package
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          attestations: true
          skip-existing: true

Documentation

Rebuild the docs whenever a source file changes:

$ just watch-docs

tmuxp developer config

../../_images/tmuxp-dev-screenshot.png

After you Install the latest code from git, load the project’s own workspace from the checkout root:

$ tmuxp load .

This loads the .tmuxp.yaml at the project root:

session_name: tmuxp
start_directory: ./ # load session relative to config location (project root).
shell_command_before:
- uv virtualenv --quiet > /dev/null 2>&1 && clear
windows:
- window_name: tmuxp
  focus: True
  layout: main-horizontal
  options:
    main-pane-height: 67%
  panes:
  - focus: true
  - pane 
  - just watch-mypy
  - just watch-test
- window_name: docs
  layout: main-horizontal
  options:
    main-pane-height: 67%
  start_directory: docs/
  panes:
  - focus: true
  - pane
  - pane
  - just start

Formatting

Linting

The project uses ruff for linting, import sorting, and formatting.

Lint:

$ just ruff

Autofix what ruff can:

$ uv run ruff check . --fix --show-fixes

Formatting

ruff format handles formatting:

$ just ruff-format

Type checking

mypy does static type checking:

$ just mypy

Re-check on change:

$ just watch-mypy

Continuous integration

tmuxp uses GitHub Actions for continuous integration. To see the tmux and Python versions under test, read .github/workflows/tests.yml. Builds run on master and on pull requests, and are visible on the build site.