Conversation
Codecov Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
d64904e to
db303db
Compare
Code reviewFound 4 issues:
tmuxp/src/tmuxp/workspace/builder.py Lines 674 to 680 in db303db
Lines 82 to 88 in db303db Lines 81 to 88 in db303db Lines 81 to 88 in db303db Lines 82 to 88 in db303db
Lines 326 to 334 in db303db
tmuxp/src/tmuxp/workspace/importers.py Lines 93 to 107 in db303db 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
60cd8fc to
92ba6f4
Compare
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. Review scope (5 parallel agents):
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 👎. |
Code reviewFound 1 issue:
Lines 334 to 337 in aa69986 Lines 403 to 406 in aa69986 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Code reviewFound 2 issues:
Lines 792 to 819 in 395b1e1
tmuxp/src/tmuxp/workspace/loader.py Lines 43 to 48 in 395b1e1 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
…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
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
…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
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 mysessionRuns the
on_project_stoplifecycle 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 myprojectCreates a new workspace config from a minimal template and opens it in
$EDITOR.tmuxp copy— copy a workspace config$ tmuxp copy myproject myproject-backupCopies 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-projectDeletes workspace config files. Prompts for confirmation unless
-yis passed.Lifecycle hooks
Workspace configs now support four hooks, matching tmuxinator's hook system:
on_project_starton_project_restarton_project_exitclient-detachedhook)on_project_stoptmuxp stopkills the sessionConfig templating
Workspace configs now support
{{ variable }}placeholders with values passed via--set:$ tmuxp load --set project=myapp mytemplate.yamlNew config keys
Pane titles
synchronizeshorthandDesugars
synchronize: before→options: {synchronize-panes: on}andsynchronize: after→options_after: {synchronize-panes: on}.trueis equivalent tobefore.shell_command_afterandclearNew
tmuxp loadflags--here--no-shell-command-beforeshell_command_beforeentries--debug--set KEY=VALUEImporter improvements
tmuxinator
pre→on_project_start,pre_window→shell_command_beforecli_args(-f,-S,-L) parsed into tmuxp equivalentssynchronizewindow key convertedstartup_window/startup_pane→focus: trueon the targettitleon the panestr(fixes numeric/emoji YAML keys)teamocil
windowsat top level,commandskey in panes)focus: trueon windows and panes convertedoptionspassed throughBug fixes
on_project_starthook when load actually proceeds (not on cancellation)on_project_restartafter the user confirms reattachDocumentation
docs/comparison.md): Side-by-side of tmuxp vs tmuxinator vs teamocil — architecture, config keys, CLI flags, hooksdocs/configuration/top-level.md): New keys, lifecycle hooks, synchronize, pane titlesdocs/configuration/examples.md): Working examples for each new featureTest plan
uv run py.testpassestmuxp loadwith lifecycle hooks,--here,--debug,--settmuxp stop,tmuxp new,tmuxp copy,tmuxp delete