Skip to content

feat: tmuxinator/teamocil feature parity#1025

Open
tony wants to merge 190 commits intomasterfrom
parity
Open

feat: tmuxinator/teamocil feature parity#1025
tony wants to merge 190 commits intomasterfrom
parity

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Mar 17, 2026

Summary

Bring tmuxp to feature parity with tmuxinator and teamocil. This adds the missing CLI commands, config keys, lifecycle hooks, config templating, and importer improvements that users migrating from those tools expect — plus comprehensive documentation including a full feature comparison page.

New CLI commands

tmuxp stop — kill a session with cleanup

$ tmuxp stop mysession

Runs the on_project_stop lifecycle hook before killing the session, giving projects a chance to tear down background services, save state, etc.

tmuxp new — create a workspace config

$ tmuxp new myproject

Creates a new workspace config from a minimal template and opens it in $EDITOR.

tmuxp copy — copy a workspace config

$ tmuxp copy myproject myproject-backup

Copies an existing workspace config to a new name. Source is resolved using the same logic as tmuxp load.

tmuxp delete — delete workspace configs

$ tmuxp delete old-project

Deletes workspace config files. Prompts for confirmation unless -y is passed.

Lifecycle hooks

Workspace configs now support four hooks, matching tmuxinator's hook system:

session_name: myproject
on_project_start: docker compose up -d
on_project_exit: docker compose down
on_project_stop: docker compose down -v
on_project_restart: echo "Reattaching..."
windows:
  - window_name: editor
    panes:
      - vim
Hook When it runs
on_project_start Before session build (every invocation)
on_project_restart When reattaching to an existing session
on_project_exit On client detach (via tmux client-detached hook)
on_project_stop Before tmuxp stop kills the session

Config templating

Workspace configs now support {{ variable }} placeholders with values passed via --set:

# mytemplate.yaml
session_name: "{{ project }}"
start_directory: "~/code/{{ project }}"
windows:
  - window_name: editor
    panes:
      - vim
$ tmuxp load --set project=myapp mytemplate.yaml

New config keys

Pane titles

session_name: dashboard
enable_pane_titles: true
pane_title_position: top
pane_title_format: "#{pane_title}"
windows:
  - window_name: main
    panes:
      - title: logs
        shell_command: tail -f /var/log/syslog
      - title: editor
        shell_command: vim

synchronize shorthand

windows:
  - window_name: multi-server
    synchronize: before    # or: after, true
    panes:
      - ssh server1
      - ssh server2

Desugars synchronize: beforeoptions: {synchronize-panes: on} and synchronize: afteroptions_after: {synchronize-panes: on}. true is equivalent to before.

shell_command_after and clear

windows:
  - window_name: dev
    shell_command_after:
      - echo "Window ready"
    clear: true
    panes:
      - vim
      - npm run dev

New tmuxp load flags

Flag Description
--here Reuse the current tmux window instead of creating a new session
--no-shell-command-before Skip all shell_command_before entries
--debug Show tmux commands as they execute (disables progress spinner)
--set KEY=VALUE Pass template variables for config templating

Importer improvements

tmuxinator

  • preon_project_start, pre_windowshell_command_before
  • cli_args (-f, -S, -L) parsed into tmuxp equivalents
  • synchronize window key converted
  • startup_window / startup_panefocus: true on the target
  • Named panes (hash-key syntax) → title on the pane
  • Window names coerced to str (fixes numeric/emoji YAML keys)

teamocil

  • v1.x format support (windows at top level, commands key in panes)
  • focus: true on windows and panes converted
  • Window options passed through

Bug fixes

  • Only fire on_project_start hook when load actually proceeds (not on cancellation)
  • Only fire on_project_restart after the user confirms reattach

Documentation

  • Feature comparison page (docs/comparison.md): Side-by-side of tmuxp vs tmuxinator vs teamocil — architecture, config keys, CLI flags, hooks
  • Top-level config docs (docs/configuration/top-level.md): New keys, lifecycle hooks, synchronize, pane titles
  • Config examples (docs/configuration/examples.md): Working examples for each new feature
  • CLI docs: Pages for stop, new, copy, delete, plus updated load docs
  • Import docs: Updated with notes on new importer capabilities

Test plan

  • uv run py.test passes
  • Tests for every new feature: stop, new, copy, delete, load flags, lifecycle hooks, config templating, builder, importers
  • Importer fixtures for edge cases (numeric names, YAML aliases, named panes, v1.x teamocil format)
  • Manual: tmuxp load with lifecycle hooks, --here, --debug, --set
  • Manual: tmuxp stop, tmuxp new, tmuxp copy, tmuxp delete
  • Manual: Import from tmuxinator/teamocil configs with new features

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 88.31776% with 75 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.12%. Comparing base (1ee42b0) to head (c8dae37).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/tmuxp/cli/load.py 75.00% 22 Missing and 6 partials ⚠️
src/tmuxp/workspace/builder.py 84.61% 9 Missing and 7 partials ⚠️
src/tmuxp/workspace/importers.py 90.90% 3 Missing and 10 partials ⚠️
src/tmuxp/cli/copy.py 87.80% 3 Missing and 2 partials ⚠️
src/tmuxp/util.py 85.29% 3 Missing and 2 partials ⚠️
src/tmuxp/cli/delete.py 90.90% 2 Missing and 1 partial ⚠️
src/tmuxp/cli/import_config.py 90.00% 2 Missing ⚠️
src/tmuxp/cli/new.py 96.29% 1 Missing and 1 partial ⚠️
src/tmuxp/workspace/loader.py 97.95% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1025      +/-   ##
==========================================
+ Coverage   81.02%   83.12%   +2.09%     
==========================================
  Files          28       32       +4     
  Lines        2630     3182     +552     
  Branches      492      628     +136     
==========================================
+ Hits         2131     2645     +514     
- Misses        368      382      +14     
- Partials      131      155      +24     

☔ View full report in Codecov by Sentry.
📢 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 tony force-pushed the parity branch 3 times, most recently from d64904e to db303db Compare March 21, 2026 10:53
@tony
Copy link
Copy Markdown
Member Author

tony commented Mar 21, 2026

Code review

Found 4 issues:

  1. Shell command injection via start_directory in --here mode. The --here path constructs f'cd "{start_directory}"' and sends it as keystrokes via send_keys. A start_directory containing " or ; can inject arbitrary shell commands into the pane. The non---here path safely passes start_directory as a structured argument to new_window(). Consider using shlex.quote() or passing the directory through tmux's -c flag instead.

if start_directory:
active_pane = window.active_pane
if active_pane is not None:
active_pane.send_keys(
f'cd "{start_directory}"',
enter=True,
)

  1. Missing doctests on command_copy, command_delete, command_new, command_stop (CLAUDE.md says "All functions and methods MUST have working doctests.")

def command_copy(
source: str,
destination: str,
parser: argparse.ArgumentParser | None = None,
color: CLIColorModeLiteral | None = None,
) -> None:
"""Entrypoint for ``tmuxp copy``, copy a workspace config to a new name."""

def command_delete(
workspace_names: list[str],
answer_yes: bool = False,
parser: argparse.ArgumentParser | None = None,
color: CLIColorModeLiteral | None = None,
) -> None:
"""Entrypoint for ``tmuxp delete``, remove workspace config files."""
color_mode = get_color_mode(color)

def command_new(
workspace_name: str,
parser: argparse.ArgumentParser | None = None,
color: CLIColorModeLiteral | None = None,
) -> None:
"""Entrypoint for ``tmuxp new``, create a new workspace config from template."""
color_mode = get_color_mode(color)
colors = Colors(color_mode)

def command_stop(
args: CLIStopNamespace,
parser: argparse.ArgumentParser | None = None,
) -> None:
"""Entrypoint for ``tmuxp stop``, kill a tmux session."""
color_mode = get_color_mode(args.color)
colors = Colors(color_mode)

  1. Missing doctest on _load_here_in_current_session (CLAUDE.md says "All functions and methods MUST have working doctests.")

tmuxp/src/tmuxp/cli/load.py

Lines 326 to 334 in db303db

def _load_here_in_current_session(builder: WorkspaceBuilder) -> None:
"""Load workspace reusing current window for first window.
Parameters
----------
builder: :class:`workspace.builder.WorkspaceBuilder`
"""
current_attached_session = builder.find_current_attached_session()
builder.build(current_attached_session, here=True)

  1. socket_name extracted from cli_args: "-L mysocket" is silently overwritten if the tmuxinator config also has an explicit socket_name key. The -L value is parsed at line 103, then unconditionally replaced at lines 105-106. Consider only overwriting if the explicit key exists and differs, or logging a warning about the conflict.

raw_args = workspace_dict.get("cli_args") or workspace_dict.get("tmux_options")
if raw_args:
tokens = shlex.split(raw_args)
flag_map = {"-f": "config", "-L": "socket_name", "-S": "socket_path"}
it = iter(tokens)
for token in it:
if token in flag_map:
value = next(it, None)
if value is not None:
tmuxp_workspace[flag_map[token]] = value
if "socket_name" in workspace_dict:
tmuxp_workspace["socket_name"] = workspace_dict["socket_name"]

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony tony force-pushed the parity branch 3 times, most recently from 60cd8fc to 92ba6f4 Compare March 23, 2026 01:28
@tony
Copy link
Copy Markdown
Member Author

tony commented Mar 23, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Review scope (5 parallel agents):

  1. CLAUDE.md compliance audit — 4 minor findings, all below confidence threshold after scoring
  2. Shallow bug scan — 3 candidates; 1 debunked (shlex.quote + replace is valid POSIX quoting), 1 confirmed as correct tmuxinator parity, 1 edge case with adequate logging
  3. Git history context — no regressions found vs. prior commits
  4. Prior PR comments — color hierarchy and prompt patterns from PRs CLI Colors #1006/feat(load): animated progress spinner for tmuxp load #1020 verified compliant
  5. Code comments compliance — 1 stale docstring (pre-existing, not introduced by this PR)

Prior review rounds: 3 rounds of 3-model (Claude/Gemini/GPT) loom reviews found 16 issues, all fixed in subsequent commits on this branch.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony
Copy link
Copy Markdown
Member Author

tony commented Mar 28, 2026

Code review

Found 1 issue:

  1. _load_here_in_current_session and _dispatch_build use callable() as a doctest placeholder, providing zero documentation or testing value (CLAUDE.md says "All functions and methods MUST have working doctests. Doctests serve as both documentation and tests." and "If you cannot create a working doctest, STOP and ask for help")

tmuxp/src/tmuxp/cli/load.py

Lines 334 to 337 in aa69986

--------
>>> from tmuxp.cli.load import _load_here_in_current_session
>>> callable(_load_here_in_current_session)
True

tmuxp/src/tmuxp/cli/load.py

Lines 403 to 406 in aa69986

--------
>>> from tmuxp.cli.load import _dispatch_build
>>> callable(_dispatch_build)
True

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@tony
Copy link
Copy Markdown
Member Author

tony commented Mar 29, 2026

Code review

Found 2 issues:

  1. on_project_start fires unconditionally in --here mode even when the session already exists. The guard at line 793 (not here) causes the session-exists block to be skipped entirely, so execution always falls through to line 815 which runs on_project_start unconditionally. This contradicts commit 54afda7 which deliberately tightened on_project_start to fire on new session creation only.

tmuxp/src/tmuxp/cli/load.py

Lines 792 to 819 in 395b1e1

# Session-exists check — outside spinner so prompt_yes_no is safe
if builder.session_exists(session_name) and not append and not here:
_confirmed = not detached and (
answer_yes
or prompt_yes_no(
f"{cli_colors.highlight(session_name)} is already running. Attach?",
default=True,
color_mode=cli_colors.mode,
)
)
# Run on_project_restart hook — only when actually reattaching
if _confirmed:
if "on_project_restart" in expanded_workspace:
_hook_cwd = expanded_workspace.get("start_directory")
util.run_hook_commands(
expanded_workspace["on_project_restart"],
cwd=_hook_cwd,
)
_reattach(builder, cli_colors)
_cleanup_debug()
return None
# Run on_project_start hook — fires before new session build
if "on_project_start" in expanded_workspace:
_hook_cwd = expanded_workspace.get("start_directory")
util.run_hook_commands(
expanded_workspace["on_project_start"],
cwd=_hook_cwd,

  1. _validate_template_values doctest uses ... in the expected ValueError output without # doctest: +ELLIPSIS. It passes under pytest (global ELLIPSIS in pyproject.toml) but fails under python3 -m doctest. CLAUDE.md requires "Ellipsis for variable output: # doctest: +ELLIPSIS".

>>> _validate_template_values({"key": "foo: bar"})
Traceback (most recent call last):
...
ValueError: --set value for 'key' contains YAML-unsafe characters ...
"""

Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 17 commits March 29, 2026 06:04
…amocil

Comprehensive side-by-side comparison covering architecture, config keys,
CLI commands, hooks, and config file discovery across all three tools.
Documents 12 feature gaps (hooks, stop command, pane sync, pane titles,
ERB templating, wemux, debug/dry-run, config management CLIs), import
behavior with bug inventory, and WorkspaceBuilder requirements.
Documents v0.x vs v1.x format differences, 3 feature gaps (--here flag,
debug mode, shell_command_after), import bugs (v1.x incompatibility,
redundant filter loops), and WorkspaceBuilder requirements.
Classifies each config key as difference (translatable) or limitation
(needs tmuxp feature). Identifies pre/pre_window bug, missing rvm/pre_tab
mappings, and 5 features requiring new tmuxp capabilities.
Documents v0.x-only targeting (v1.x unsupported), string pane TypeError
bug, redundant filter loop bug, and 6 missing v1.x key mappings
(commands, focus, options, string shorthand).
- Remove duplicate 'Attach on create' row in comparison table, keep
  corrected version with '(default: true)' near socket_path
- Annotate pre_tab as (deprecated) in comparison table
- Annotate startup_window as accepting name or index
- Fix pre_tab description: deprecated predecessor, not alias (it was
  renamed in tmuxinator, not aliased)
- Clarify startup_window renders as "#{name}:#{value}"
- tmuxinator min tmux is 1.8 (recommended), not 1.5; tmux 2.5 is
  explicitly unsupported
- teamocil has no documented min tmux version
- tmuxinator detach is via `attach: false` config or `--no-attach`
  CLI flag, not `-d` (which doesn't exist in tmuxinator)
- Add socket_path as item 16 (tmuxinator config key not handled)
- socket_path takes precedence over socket_name in tmuxinator
- tmuxp only accepts socket path via CLI -S flag
- Add to summary table as missing Difference
- with_env_var is an import-only fix (tmuxp already has environment
  key), not a Limitation — moved to new "Import-Only Fixes" section
- cmd_separator is irrelevant (tmuxp sends commands individually),
  clarified it needs no import
- Fix "1.5+" to "1.8+" in architecture description (was already
  fixed in overview table but missed in prose)
- Clarify YAML anchors: tmuxinator enables via YAML.safe_load
  aliases param, not a config key
- Clarify tmuxinator edit is alias of new command
…s equivalent

tmuxp doesn't have startup_window/startup_pane keys but achieves the
same result via focus: true on individual windows/panes. Add cross-reference
annotation so users aren't misled by (none).
- before_script maps to on_project_first_start (runs only when session
  doesn't exist), not on_project_start (runs every invocation)
- Add teamocil --here implementation details: sends cd via send-keys,
  decrements window count for index calculation
- import-teamocil.md: Code block comment said "Lines 144-149" but the
  `if "filters"` guard is on line 143, so range is 143-149
- parity-teamocil.md: Referenced "Line 142" for `clear` handling but
  actual code is lines 140-141 (line 142 is blank)
…, expand CLI table

- Fix min tmux: 1.5+ (not "1.8 recommended; not 2.5"), per tmux_version.rb
- Note teamocil renames session (rename-session) rather than creating new
- Add teamocil auto-generated session name detail
- Expand pre_window to show full deprecation chain (rbenv/rvm/pre_tab)
- Add synchronize values (true/before/after)
- Add --suppress-tmux-version-warning to CLI table
- Split deprecated pre/post into separate rows with hook mappings
- Fix tmuxp --append flag syntax
- Fix pane focus to note startup_pane equivalent
… chain, remove --here

- Fix startup_window: accepts name OR index (not just name)
- Document pre_window fallback chain: rbenv → rvm → pre_tab → pre_window
- Remove section 12 (--here) — this is a teamocil feature, not tmuxinator
- Renumber section 13 → 12
- Clarify freeze vs tmuxinator new comparison
- Add rvm source reference (project.rb:181)
- Add tmuxinator version range to header
…md_separator

- Add section 1: teamocil renames session (rename-session), not creates
- Note auto-generated session name (teamocil-session-RANDOM)
- Add window focus implementation detail (session.rb:24-25)
- Add --list and --edit note for teamocil CLI
- Reclassify with_env_var and cmd_separator as unverified (not in source)
- Add session rename mode to WorkspaceBuilder gaps
- Fix line number references (144-149, 147-149, 161-163)
- Renumber sections to account for new section 1
tony added 25 commits March 29, 2026 06:04
command_copy, command_delete, and command_new returned without
sys.exit(1) on error conditions (source not found, editor not
found, workspace not found).  The CLI dispatcher also returned 0
when required positional args were missing.  Match command_stop's
pattern of calling sys.exit(1) on all failure paths.
…ions

copy.py hardcoded .yaml extension for destination when given a pure
name.  Now uses os.path.splitext to preserve the source extension
(e.g. copying a .json workspace keeps .json, not .yaml).
…L-hostile chars

Reject names containing path separators (a/b), parent references (..),
YAML reserved words (yes/true/null), and YAML special characters
(#*&!|>'"%). Quote session_name in the YAML template to handle
remaining edge cases.
run_hook_commands() captured output but discarded it.  Now logs
stdout and stderr at DEBUG when the hook fails, aiding diagnosis.
…ubstitution

The implementation is a regex-based {{ variable }} replacer, not
Jinja2.  No filters, conditions, loops, or dotted names.
…ll note, fix version

- Add note that --debug executes (unlike tmuxinator debug which is dry-run)
- Document --here POSIX shell assumption
- Replace unreleased 1.68.0 with "Next" in comparison table
Development planning artifacts and Claude Code command prompts
should not ship in releases.
In --here mode, builder.session is the user's existing live session,
not a scratch session tmuxp created. The generic error recovery prompt
offering (k)ill would destroy the user's real work. Skip the kill
option and default to (d)etach when --here mode encounters a build
error.
get_session() previously fell back to server.sessions[0] when
TMUX_PANE was unset or stale. For destructive commands like
`tmuxp stop`, this could kill the wrong session. Now raises
SessionNotFound so callers must handle the error explicitly.
- on_project_start: now only fires on new session creation (not reattach)
- on_project_restart: now only fires on confirmed interactive reattach
  (not detached loads of existing sessions)
- Docs: remove "matches tmuxinator" claim for on_project_exit; document
  it as tmuxp-specific behavior that fires on every detach event
- CHANGES: correct hook descriptions to match new semantics
- Update test to assert detached loads do NOT trigger restart hook
…olution

Numeric startup_window/startup_pane values are resolved as 0-based
Python list indices, which may differ from tmuxinator's tmux base-index
semantics. Upgrade log level from INFO to WARNING to make this
deviation explicit to users importing tmuxinator configs.
The WORKSPACE_TEMPLATE uses single-quoted YAML for session_name, so
embedded quotes like foo'bar produce invalid YAML. Add validation to
reject names containing single or double quotes.
Compare os.path.realpath() of source and destination before calling
shutil.copy2() to prevent unhandled SameFileError when copying a
workspace to itself.
Support both space-separated (-L mysocket) and attached (-Lmysocket)
forms for -f, -L, and -S flags when parsing tmuxinator cli_args.
Previously only the space-separated form was recognized; attached
forms were silently dropped.
…cters

render_template() now rejects values containing colons, braces,
brackets, or newlines before substitution. These characters can
corrupt YAML document structure when injected as raw text before
parsing.
…broader

Add note explaining that tmuxp's --no-shell-command-before strips at
all levels (session/window/pane), which is intentionally broader than
tmuxinator's --no-pre-window that only targets the window chain.
Wrap the client-detached hook command in a #{session_attached} guard
so it only fires when the last client detaches. This prevents
premature cleanup in multi-client scenarios (pair programming, SSH
drops) where other clients are still attached.
The dict was re-created inside import_tmuxinator() on every call.
Move to module-level constant per Python convention for immutable
lookup tables.
Add require_pane_resolution parameter to get_session(). When True,
raises SessionNotFound instead of falling back to server.sessions[0]
when TMUX_PANE is unset/stale. tmuxp stop now uses strict mode to
prevent killing an unrelated session. Non-destructive commands
(shell, freeze) retain the fallback behavior.
…end_keys

Replace send_keys("export ...") and send_keys(window_shell) with tmux
primitives in --here mode:

- Environment: session.set_environment() + respawn-pane -e (inherited
  by new panes, no POSIX shell assumption)
- Shell replacement: respawn-pane -k (kills current process, starts
  fresh shell — no typing into foreground programs)
- Directory: respawn-pane -c (tmux primitive, no send_keys cd)

This eliminates all send_keys usage for infrastructure setup in --here
mode, matching teamocil's approach of using tmux primitives over
send_keys.  Fixes the fish/nu shell incompatibility and the "types
into vim" failure mode.

Closes #1031
Before calling respawn-pane -k, check pgrep -P <pane_pid> for child
processes. If the shell has running children (background jobs,
foreground programs), log a WARNING so users know their processes
will be terminated. Gracefully handles missing pgrep.
… error recovery

New builder tests (NamedTuple + test_id pattern):
- HereRespawnFixture: parametrized over 4 scenarios (dir-only, env-only,
  dir-and-env, nothing-to-provision) verifying PID changes on respawn,
  directory provisioning, and session environment
- test_here_mode_respawn_multiple_env_vars: 3 env vars via set_environment
- test_here_mode_respawn_warns_on_running_processes: background sleep job
  triggers pgrep WARNING before respawn-pane -k
- test_here_mode_no_warning_when_pane_idle: idle pane produces no warning

New load CLI tests (NamedTuple + test_id pattern):
- HereErrorRecoveryFixture: parametrized over 2 scenarios verifying
  --here mode skips (k)ill option (choices=[a,d], default=d) while
  normal mode retains it (choices=[k,a,d], default=k)
why: on_project_start had been triggered before dispatch, so it also ran for paths that reused an existing session. That made --here rebuilds and interactive append flows execute a hook documented as new-session-only.

what:
- Move on_project_start execution into the attached and detached new-session load paths
- Keep --here rebuilds inside tmux and append flows from invoking the hook
- Preserve the outside-tmux --here fallback behavior, which still creates a new session
- Add dispatch tests for attached, detached, append, and here routing
- Add an on_project_exit guard assertion and fix the loader doctest ellipsis
- Update the related load, comparison, and configuration docs to match current behavior
why: Keep the changelog aligned with the lifecycle behavior shipped on this branch so readers do not infer broader hook semantics than the code implements.

what:
- Document on_project_start as running only for new session creation
- Document on_project_exit as running when the last client detaches
- Match the guarded client-detached hook behavior in WorkspaceBuilder
tony added 4 commits March 29, 2026 06:48
…ures

why: --here and --append reuse an existing tmux session. Startup failures
must abort without destroying that live session, and duplicate target names
must stop before any plugin hooks or before_script side effects run.
what:
- track whether build created the session before cleaning it up on failure
- move the --here duplicate-session check ahead of startup hooks and script execution
- add builder coverage for reused-session failures and pre-hook rename conflicts
why: --here is a current-window workflow. Accepting multiple workspace files
silently changes behavior for earlier entries and leaves behind unexpected
sessions instead of failing fast.
what:
- reject --here when more than one workspace file is provided
- clarify the parser help text for the single-workspace contract
- add CLI coverage that exits before any workspace is loaded
- document the single-workspace restriction in the load guide
…port

why: tmuxinator numeric startup_window and startup_pane values are tmux
indices, not Python list offsets. Importing them as list positions changes
which window or pane receives focus and breaks compatibility with existing
configs, especially when base-index or pane-base-index are nonzero.
what:
- resolve numeric startup targets against tmux base-index and pane-base-index
- read live tmux index settings in the tmuxinator import CLI path
- add importer and CLI coverage for base-index aware conversion and fallback
…nd env intact

why: append and --here reuse a live tmux session rather than creating a tmuxp
owned session. Writing lifecycle hooks or stop metadata onto that reused
session can overwrite unrelated teardown behavior, and copying first-pane
environment into session state makes later windows inherit variables they were
never meant to see.
what:
- limit on_project_exit, on_project_stop, and start_directory session metadata
to sessions created by the current build
- keep --here first-pane provisioning local to respawn-pane instead of the
session environment
- add reused-session and non-leaking here-mode tests around hooks and env
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