Skip to content

tmux: keep tab groups across detach and reattach - #803

Open
i8ramin wants to merge 1 commit into
gnachman:masterfrom
i8ramin:tmux-tab-groups
Open

i8ramin wants to merge 1 commit into
gnachman:masterfrom
i8ramin:tmux-tab-groups

Conversation

@i8ramin

@i8ramin i8ramin commented Oct 22, 2026 •

Copy link
Copy Markdown

Fixes: https://gitlab.com/gnachman/iterm2/-/issues/13167 (duplicate: https://gitlab.com/gnachman/iterm2/-/issues/13069)
Related: https://gitlab.com/gnachman/iterm2/-/issues/13085 (Python API for tab groups)

Problem

With tmux -CC, tab titles, tab colors and window affinity survive detach and reattach, but tab groups don't. After reattaching, users rebuild every group (membership, name, color, collapsed state) by hand.

Change

Each tmux tab already has an entry in the @per_tab_settings session option (currently only t=<base64 title>). This change adds four keys to that entry:

Key Value
g= base64 group ID
gn= base64 group name
gc= group color as #rrggbb or p3#rrrrggggbbbb (-hexStringPreservingColorSpace, as the Python API uses)
gx=1 present if the group is collapsed

Every member tab carries its group's attributes, the same way window arrangements store them, so no new tmux option is needed.

  • Restore: the keys are applied to each tab as its window opens. When the last pending window has opened (or failed to open), -[TmuxController applyRestoredTabGroups] calls the new -[PseudoTerminal tmuxTabGroupsDidLoadForTabs:] with the tabs that just opened. It runs the same steps as arrangement restore: enforce contiguity, normalize collapsed state, update the tab bar. Layout updates also end in windowDidOpen:, so only windows that were actually pending count.
  • Joining an open group: a window reopened from the dashboard may have been saved before its group was renamed or recolored. If the group is already open in that window, the reopened tab takes the open members' name, color and collapsed state.
  • Save: -[PseudoTerminal updateTabGroups] calls the new -[TmuxController tabGroupsDidChange]. Because updateTabGroups runs on every tab bar update, this coalesces to one check per run-loop pass and saves only if the open tabs' settings changed since the last save.
  • Read is add-only: a setting without g= never ungroups a tab, so an entry written by an older version can't undo grouping done since.

Changes to savePerTabSettings that this depends on:

  • It doesn't save while detaching or switching sessions, or before the session's @per_tab_settings has been read. Before, when the client switched sessions, closing the old session's tabs could write their settings into the new session's option before it was read.
  • It merges the open tabs' settings into the cached ones instead of rebuilding the option from open tabs only. Windows without an open tab (hidden, still opening, or being reopened from the dashboard) keep their entries. An entry is dropped on %window-close, and on attach for windows that no longer exist.
  • It only counts tabs that are live in this controller. Tabs from another tmux server attached in the same app, and tabs left open after a detach, used to leak into this session's option.

Backward compatibility

The 3.7.3 parser ignores unknown keys in @per_tab_settings, so older builds attach fine. See the known limitation below about older builds saving.

Testing

ModernTests/PTYTabTmuxTabGroupSettingsTests.swift (6 tests, all passing with tools/run_tests.expect ModernTests/PTYTabTmuxTabGroupSettingsTests): group attributes round-trip, title and group round-trip together, unknown keys are ignored, a setting without a group leaves an existing group alone, an ungrouped tab with no title produces no setting, and group colors (sRGB, Display P3 and a system color) save to the same value after repeated restore-and-save cycles.

Manual tests with a dev build against an isolated tmux server (tmux -L tgtest), with "Open tmux windows as tabs in an existing window":

  1. Attach, put two tabs in a group, name it, set a color. Detach and reattach: the group comes back with its name and color.
  2. Rename the group and collapse it: @per_tab_settings is updated right away (gx=1 present). After another detach and reattach the option still has the new name and gx=1.
  3. Session switch: two sessions, each with a different saved group in @per_tab_settings. Attach to the first, then tmux switch-client -t the second, then switch back. Each session's option still holds only its own group, and a collapsed group whose session has an ungrouped tab selected stays collapsed.

Known limitations

  • If tabs are reordered at attach to keep a group contiguous, that order isn't pushed back to tmux's window order.
  • An older iTerm2 that attaches and then saves @per_tab_settings will drop the group keys.
  • Two clients attached to the same session each save their own view of the groups when they change them; the last change wins.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WL2PvY6a8osQsVf8xCE1eU

@i8ramin
i8ramin marked this pull request as draft October 22, 2026 18:57
…ue 13167.

Tab groups of tmux tabs were lost on reattach because nothing stored
them in tmux. Each tab now writes its group's ID, name, color and
collapsed flag into its entry in the existing @per_tab_settings option,
as g=, gn=, gc= and gx= keys. Every member carries the group's
attributes, as window arrangements do, so no new tmux option is needed.
Older versions ignore the unknown keys.

On attach, the keys are applied as each tab opens. Once the last pending
window has opened (or failed to), each window with newly opened tabs
enforces group contiguity, normalizes collapsed state and updates its
tab bar, as arrangement restore does. A reopened tab joining a group
that is already open takes the open members' attributes. Reading a
setting only ever adds a group, so an entry written by an older version
never ungroups a tab.

Tab bar updates request a save, coalesced to one check per run-loop
pass, which writes the option only if the open tabs' settings changed.
savePerTabSettings no longer saves while detaching or switching
sessions, or before the session's option has been read, so switching
sessions can't write the old session's settings into the new one. It
merges open tabs into the cached settings, so windows without a tab
(hidden, opening or being reopened) keep their entries until the window
closes, and it skips tabs from other tmux servers and tabs left open
after a detach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@i8ramin
i8ramin marked this pull request as ready for review October 22, 2026 19:20
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