Skip to content

Make OpenCode tmux panes restore their exact sessions #8

Description

@itse4elhaam

Problem

I use tmux + OpenCode heavily, often with multiple OpenCode panes in the same repository. opencode --continue is not pane-aware, so tmux-resurrect cannot safely restore each pane to the exact OpenCode session it had before.

Example failure:

Tmux session: XYZ
Window 1, pane 1 -> OpenCode session A
Window 1, pane 2 -> OpenCode session B

If B was touched most recently, restoring both panes with `opencode --continue` can reopen B in both panes.

The desired behavior is:

XYZ:1.1 -> session A
XYZ:1.2 -> session B

After tmux-resurrect:
XYZ:1.1 opens session A exactly
XYZ:1.2 opens session B exactly

Source check before implementation

Verified against current source/config before creating this issue:

  • Dotfiles .tmux.conf already uses tmux-resurrect, tmux-continuum, and @continuum-restore 'on'.
  • Dotfiles .tmux.conf has base-index 1, pane-base-index 1, and renumber-windows on, so tests and keys should use the actual tmux coordinate values, not assume zero-based indexes.
  • Dotfiles .config/opencode/tui.json currently only has keybindings and no TUI plugin registration.
  • OpenCode current CLI supports --continue / -c and --session / -s.
  • OpenCode current TUI implementation handles --continue by sorting synced sessions by time.updated descending and selecting the newest root session where parentID === undefined. This is explicitly not pane-aware.
  • OpenCode current --session path navigates directly to the supplied sessionID, which is the primitive we need for exact restore.
  • OpenCode current session picker switches sessions by directly navigating to { type: "session", sessionID: option.value }, so any solution that only observes CLI startup will miss in-TUI session switches.
  • OpenCode current TUI plugin API supports file/path plugins in tui.json, exposes api.route.current, and for session routes exposes { name: "session", params: { sessionID } }.
  • tmux-resurrect saves pane rows with session name, window index, pane index, pane current path, current command, and full command.
  • tmux-resurrect restores pane processes by targeting ${session_name}:${window_number}.${pane_index} and supports @resurrect-processes with fuzzy process matching and -> custom restore commands.

Useful source links:

Proposed architecture

Keep tmux-resurrect dumb. It should only restore one generic command for OpenCode panes.

tmux-resurrect restores:
  opencode-continue-pane

opencode-continue-pane:
  identifies the current tmux session/window/pane/cwd
  looks up the exact OpenCode session bound to that pane identity
  launches `opencode --session <sessionID>`

The persistent binding should be written while OpenCode is running:

OpenCode TUI plugin:
  observe current TUI route
  when route is a session route, read exact sessionID
  ask tmux for current pane coordinate
  write binding:
    tmux_session_name + window_index + pane_index + cwd/project_path
      -> OpenCode sessionID

Recommended v1 pane identity:

<tmux-session-name>:<window-index>.<pane-index>|<cwd>

Example:

XYZ:1.2|/home/elhaam/projects/peasy -> ses_abc123

Do not use raw tmux %pane_id as the permanent database key. %pane_id is a runtime object ID and should not be assumed to survive a tmux server restart. It is fine to use $TMUX_PANE at runtime only to ask tmux for the stable-ish coordinate.

SQLite is fine for the binding store, but a small file store is also acceptable if it is atomic and easy to inspect. Prefer something debuggable over something clever.

Minimal tmux-side change

The only tmux-resurrect behavior we should need is process restoration through one generic command, roughly:

set -g @resurrect-processes '"~opencode->opencode-continue-pane"'

Exact command/name can change during implementation, but the important constraint is: do not deeply patch tmux-resurrect or generate per-pane restore commands.

Fallback behavior

opencode-continue-pane should behave conservatively:

  1. If an exact binding exists for current tmux session/window/pane/cwd, open it with opencode --session <sessionID>.
  2. If no exact binding exists, either start normal opencode or opencode --continue.
  3. Optional flag: opencode-continue-pane --pick should show an FZF picker for sessions in the current cwd/project, then persist the chosen binding for this pane.

The FZF picker is a manual repair hatch, not the primary restore mechanism.

Correct real-life tests before implementation

These tests are intentionally integration-first. The bug is about tmux/OpenCode behavior, so unit tests alone are not enough.

Test 1: prove opencode --continue is the wrong primitive

Setup:

  1. Create a tmux session named oc-restore-test in one repo.
  2. Create one window with two panes in the same cwd.
  3. Pane 1 opens OpenCode session A.
  4. Pane 2 opens OpenCode session B.
  5. Touch/interact with session B last so it becomes the newest updated session.
  6. In both panes, run opencode --continue manually.

Expected current failure:

Both panes prefer the newest root session, not the pane-specific session.

Why this is the correct baseline test:

OpenCode source currently implements --continue by sorting sessions by time.updated and selecting the newest root session. Therefore this test should fail without pane binding, and it proves the feature is necessary.

Test 2: plugin writer updates when switching sessions inside the TUI

Setup:

  1. Start OpenCode in tmux pane oc-restore-test:1.1.
  2. Confirm the binding store records oc-restore-test:1.1|cwd -> session A.
  3. Inside the same OpenCode TUI, use the session picker / /sessions to switch to session B.
  4. Inspect the binding store again.

Expected:

oc-restore-test:1.1|cwd now maps to session B, not session A.

Why this is the correct test:

OpenCode's session picker directly navigates the TUI route to the selected sessionID. A solution that only hooks CLI startup or process args will pass simple startup tests but fail this real workflow.

Test 3: two panes in same repo restore to different exact sessions

Setup:

  1. oc-restore-test:1.1 has session A.
  2. oc-restore-test:1.2 has session B.
  3. Both panes are in the same cwd.
  4. Save tmux-resurrect state.
  5. Kill the tmux server completely.
  6. Restore with tmux-resurrect.

Expected:

Pane 1 reopens session A exactly.
Pane 2 reopens session B exactly.

This is the money test. If this fails, the feature does not work.

Test 4: in-TUI switch before save restores the last active session for that pane

Setup:

  1. Pane oc-restore-test:1.1 starts with session A.
  2. Inside OpenCode, switch to session C using the session picker.
  3. Save tmux-resurrect.
  4. Kill tmux server.
  5. Restore.

Expected:

Pane oc-restore-test:1.1 opens session C, not session A.

Why this is correct:

The binding must track the session currently displayed in the pane, not the session OpenCode originally launched with.

Test 5: stale binding protection by cwd/project path

Setup:

  1. Create binding for oc-restore-test:1.1|/repo-a -> session A.
  2. Later create/reuse a tmux session with the same name and pane coordinate but in /repo-b.
  3. Run opencode-continue-pane.

Expected:

It must not open session A from /repo-a.
It should fall back or ask via picker.

Why this is correct:

Tmux session names and coordinates are reusable. CWD/project path must be part of the identity or validation layer.

Test 6: raw tmux pane id is not required after restore

Setup:

  1. Record $TMUX_PANE and the coordinate before killing tmux.
  2. Save and restore from scratch.
  3. Record $TMUX_PANE and coordinate after restore.

Expected:

The feature should work even if `$TMUX_PANE` changed.
The lookup should be based on session/window/pane/cwd, not the old `%pane_id`.

Why this is correct:

Tmux-resurrect reconstructs panes by layout/coordinates and sends restore commands to ${session}:${window}.${pane}. It does not require preserving the old runtime %pane_id.

Test 7: manual picker repair flow

Setup:

  1. Delete or corrupt the binding for a pane.
  2. Run opencode-continue-pane --pick.
  3. Select a known session from the current project.
  4. Exit and run opencode-continue-pane again without --pick.

Expected:

The selected session opens and becomes the stored binding for that pane.
The next non-picker run opens the same selected session automatically.

Why this is correct:

This proves the fallback can repair bad/stale mappings without editing SQLite/files manually.

Acceptance criteria

  • opencode-continue-pane exists and can be used as the one generic restore command for OpenCode panes.
  • OpenCode TUI plugin is registered in tui.json and records active session changes.
  • Binding key includes tmux session name, window index, pane index, and cwd/project path.
  • Binding updates when the user switches sessions from inside the OpenCode TUI.
  • tmux-resurrect restores two OpenCode panes in the same repo to two different exact sessions.
  • No permanent reliance on raw tmux %pane_id.
  • Missing/stale binding fails safely instead of opening a wrong session silently.
  • Optional --pick mode can repair/create a binding interactively.

Non-goals

  • Do not modify tmux-resurrect internals.
  • Do not rely on terminal title, because OpenCode titles are human-readable/truncated and not stable session IDs.
  • Do not use opencode --continue as the exact restore mechanism.
  • Do not make this depend on a single repo name or hardcoded project path.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions