Skip to content

Add zellij multiplexer support to agent-cli dev#590

Merged
basnijholt merged 3 commits into
mainfrom
zellij
Jun 10, 2026
Merged

Add zellij multiplexer support to agent-cli dev#590
basnijholt merged 3 commits into
mainfrom
zellij

Conversation

@basnijholt

Copy link
Copy Markdown
Owner

Summary

Adds zellij as a fully supported multiplexer in agent-cli dev, alongside tmux. Zellij 0.44.0 introduced the programmatic control this needs: stable tab IDs returned from new-tab, initial commands for new tabs, list-panes --json, and close-tab-by-id — all working against detached sessions.

What's new

  • --multiplexer zellij on dev new and dev agent: when run outside zellij, creates or reuses a detached repo-scoped session (zellij attach --create-background) and reports the tab handle plus a zellij attach <session> hint
  • --zellij-session NAME: mirrors --tmux-session, implies --multiplexer zellij; the two session flags are mutually exclusive and validated early
  • Worktree cleanup closes zellij tabs: dev clean / worktree removal now inventories tabs across all live sessions by matching pane_cwd against the worktree path (zellij's equivalent of tmux window tagging) and closes them via close-tab-by-id, skipping the tab the process itself runs in (resolved via ZELLIJ_PANE_ID)
  • Inside zellij, agents launch in a new tab of the current session using new-tab --cwd ... -- sh -c <cmd> instead of the old write-chars hack (which needed a sleep and typed into the focused pane)

Implementation

  • New Multiplexer ABC in terminals/base.py with a shared deterministic session_name_for_repo, plus abstract open_in_session / current_session_name / attach_command; Tmux and Zellij both implement it
  • launch.py's tmux-specific launch path is generalized to any Multiplexer
  • Version-gated on zellij >= 0.44.0 (parsed from zellij --version); on older versions the multiplexer path declines gracefully and the legacy --layout default + write-chars in-session path is kept as a fallback, so existing behavior is preserved

Evidence

Per the repo convention, every external CLI assumption is documented in tests/dev/test_verification.py docstrings with sources (zellij docs, CHANGELOG entries, and PR links: zellij-org/zellij#4690, #4846, #4273, #3257) and was verified live against zellij 0.44.3, including detached-session behavior (attach --create-background on an existing session exits 1 with "Session already exists" instead of blocking).

Testing

  • 1428 tests pass; 27 new tests cover command syntax, session reuse/race handling, version gating, the legacy fallback, cwd-based inventory, current-tab protection, and CLI flag validation
  • Verified end-to-end against a real detached zellij session: open tab with command + cwd → inventory by worktree → close by ID
  • Note: the jscpd pre-commit hook fails on the dev machine due to a missing cpd-darwin-arm64 platform package; verified it also fails on the untouched tree (environment issue, unrelated)

Zellij >= 0.44.0 gained stable tab IDs, initial commands for new tabs,
and detached-session control, enabling tmux-parity support:

- New Multiplexer ABC (shared session naming, open_in_session,
  attach_command); Tmux and Zellij both implement it
- --multiplexer zellij on dev new/agent: creates or reuses a detached
  repo-scoped session when outside zellij
- --zellij-session flag mirroring --tmux-session
- Worktree cleanup closes zellij tabs by matching pane cwd, across all
  live sessions, skipping the current tab
- Legacy write-chars tab opening kept for zellij < 0.44 inside a session
- Evidence-backed verification tests (docs/PRs + live probes on 0.44.3)
@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown

Greptile Summary

Adds zellij >= 0.44.0 as a first-class multiplexer in agent-cli dev, alongside tmux. A new Multiplexer ABC in terminals/base.py captures shared session-name derivation and abstract open_in_session / attach_command contracts; both Tmux and Zellij implement it, and launch.py's tmux-specific path is unified into a generic _launch_in_multiplexer.

  • Zellij gains session management (attach --create-background), tab inventory via list-panes --json + pane_cwd matching, close-tab-by-id cleanup, and version gating behind _supports_cli_control() (cached per-instance); pre-0.44 zellij falls back to the legacy write-chars path so existing behaviour is preserved.
  • cli.py adds --zellij-session with mutual-exclusion validation against --tmux-session, and a generalised _attach_hint helper replaces hardcoded tmux attach strings in the summary output.
  • cleanup.py runs Zellij().close_tabs_for_worktree() after the existing tmux cleanup on every worktree removal, with a fail-safe that skips the caller's own tab when ZELLIJ_PANE_ID cannot be resolved.

Confidence Score: 5/5

Safe to merge — the new zellij path is well-isolated behind version gating and the fail-safe current-tab protection addresses the main hazard in cleanup.

The core logic is straightforward: version-gated subprocess calls with clear error propagation, deterministic session-name derivation shared from the base class, and worktree cleanup correctly skips the caller's own tab even when pane lookup fails. Previous reviewer concerns (session-name space handling, version-check caching, current-tab fail-safe) are all addressed. No regressions to existing tmux behaviour were found. Tests are comprehensive and cover all edge cases.

No files require special attention.

Important Files Changed

Filename Overview
agent_cli/dev/terminals/zellij.py Core new file: Zellij now implements the Multiplexer ABC with session management, tab inventory via pane_cwd matching, and current-tab fail-safe protection. Version gating (>= 0.44) is correctly cached per-instance.
agent_cli/dev/terminals/base.py Adds Multiplexer ABC (session_name_for_repo, current_session_name, attach_command, open_in_session) and subprocess_error_text helper; shared deterministic session-name logic moved here from Tmux.
agent_cli/dev/terminals/tmux.py Tmux now inherits from Multiplexer; session_name_for_repo and _error_text removed (moved to base); attach_command added; no functional change to existing tmux behavior.
agent_cli/dev/launch.py _launch_in_tmux generalised to _launch_in_multiplexer; fallthrough to open_new_tab preserved for legacy zellij; tmux_session param renamed multiplexer_session; effective_multiplexer_name removed.
agent_cli/dev/cli.py Adds --zellij-session flag, refactors _normalize_tmux_session to _normalize_multiplexer_session with mutual-exclusion validation, and generalises the attach-hint summary line via the new _attach_hint helper.
agent_cli/dev/cleanup.py remove_worktree now runs Zellij.close_tabs_for_worktree after Tmux cleanup; errors from both are surfaced as warnings; zellij cleanup is skipped when git removal fails.
tests/dev/test_terminals.py 27 new Zellij tests covering version gating, session create/reuse, pane inventory, tab close-by-id, current-tab fail-safe, and version-check caching.
tests/dev/test_verification.py Adds docstring-documented verification tests for each zellij CLI assumption, citing CHANGELOG entries and live verification on zellij 0.44.3.

Sequence Diagram

sequenceDiagram
    participant CLI as dev agent / dev new
    participant NMS as _normalize_multiplexer_session
    participant LA as launch_agent
    participant LIM as _launch_in_multiplexer
    participant Z as Zellij
    participant T as Tmux

    CLI->>NMS: (tmux_session, zellij_session, multiplexer)
    NMS-->>CLI: (multiplexer_session, multiplexer_name)
    CLI->>LA: launch_agent(..., multiplexer_name, multiplexer_session)
    LA->>LIM: _launch_in_multiplexer(requested, session_override)
    alt "zellij >= 0.44 or tmux"
        LIM->>Z: open_in_session(path, cmd, session_name)
        alt session does not exist
            Z->>Z: _ensure_session
        end
        Z-->>LA: TerminalHandle
        LA-->>CLI: attach hint
    else "zellij < 0.44 fallback"
        LIM-->>LA: None
        LA->>Z: open_new_tab legacy
    end
    Note over T,Z: dev clean
    T->>T: kill_windows_for_worktree
    Z->>Z: _live_session_names
    Z->>Z: _list_panes per session
    Z->>Z: _current_tab fail-safe
    Z->>Z: close-tab-by-id
Loading

Reviews (4): Last reviewed commit: "Fix zellij tests on Windows: compare aga..." | Re-trigger Greptile

Comment thread agent_cli/dev/terminals/zellij.py Outdated
Comment thread agent_cli/dev/terminals/zellij.py Outdated
Comment thread agent_cli/dev/terminals/zellij.py
- Parse session names from list-sessions with a regex so names
  containing spaces are preserved (was: first token only)
- Fail safe in close_tabs_for_worktree: when inside zellij and the
  current tab cannot be resolved, skip all tabs in the current session
  instead of risking closing our own tab
- Cache the zellij version check per instance to avoid repeated
  `zellij --version` subprocess calls
@basnijholt basnijholt merged commit 5d374ae into main Jun 10, 2026
12 checks passed
@basnijholt basnijholt deleted the zellij branch June 10, 2026 18:13
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