Skip to content

Instantly share code, notes, and snippets.

@glassdimly
Created May 18, 2026 14:50
Show Gist options
  • Select an option

  • Save glassdimly/f7086eaee3ef40f38e0f69d6ecd1b8de to your computer and use it in GitHub Desktop.

Select an option

Save glassdimly/f7086eaee3ef40f38e0f69d6ecd1b8de to your computer and use it in GitHub Desktop.
Kitty + tmux + opencode: session restore plan and options

Kitty + tmux + opencode: Session Restore Plan

The Problem

After reboot (or just opening a fresh kitty window), you want to restore your workspace:

  • One kitty tab per tmux session
  • Each tmux session named for the task/ticket it was working on
  • Each tmux session had opencode running inside it
  • You want to resume the right opencode session (conversation history) in each tab

The missing link: opencode does not expose its session ID in any env var or file at runtime.


What We Know

Kitty tab management

  • No native multi-row/wrapping tab bar — tabs scroll horizontally when full
  • select_tab action gives a searchable overlay list (best workaround for many tabs)
  • kitten @ launch --type=tab can open tabs programmatically from a script
  • A restore script can loop over tmux sessions and open a kitty tab per session

tmux persistence

  • tmux sessions survive reboots if using tmux-resurrect + tmux-continuum
  • tmux-continuum auto-saves and restores session layout + running commands
  • After restore, tmux attach -t <name> reconnects to the session

opencode session identity

What opencode DOES expose at runtime (in child process env):

  • OPENCODE=1
  • OPENCODE_PID=<pid>
  • OPENCODE_RUN_ID=<uuid> — a UUID, NOT the session ID, no mapping anywhere

What it does NOT expose:

  • OPENCODE_SESSION_IDdoes not exist
  • No socket file, no port registry, no state file linking run to session

What does exist:

  • The log file ~/.local/share/opencode/log/<timestamp>.log contains service=session id=ses_XXXXX shortly after startup
  • The DB (~/.local/share/opencode/opencode.db) has the session with time_created
  • opencode prints the session ID to the terminal when it closes
  • The daily-log protocol writes ~/Notes/YYYY-MM-DD-<task>.md with a Resume block (but only when you say "done" — which doesn't always happen)

The daily-log protocol (existing)

On "done"/"wrap up", opencode writes:

~/Notes/YYYY-MM-DD-<task>.md
  ## Resume
  cd <dir> && opencode --session ses_XXXXX

This IS the index for restoring sessions — but only populated when sessions end cleanly.


Options for Linking tmux ↔ opencode Session

Option A: Wrapper script (log polling)

Run oc instead of opencode. The wrapper launches opencode, then polls the log until ses_XXXXX appears (usually within 1-2 seconds), then stores it in tmux:

#!/bin/bash
# ~/bin/oc
opencode "$@" &
OC_PID=$!
sleep 2  # opencode writes session ID to log within ~1s of startup
logfile=$(ls -t ~/.local/share/opencode/log/*.log | head -1)
ses_id=$(grep -oP 'service=session id=\Kses_\S+' "$logfile" | head -1)
if [ -n "$ses_id" ]; then
  tmux set-environment OPENCODE_SESSION "$ses_id"
  tmux set-option -w @opencode_session "$ses_id"
fi
wait $OC_PID

Pro: Works today, no opencode changes needed
Con: sleep is inelegant; race condition if machine is slow

Option B: DB polling (cleaner than log)

Same wrapper but poll the DB instead of the log — query for sessions newer than script start time:

START_MS=$(date +%s%3N)
opencode "$@" &
OC_PID=$!
for i in $(seq 1 20); do
  sleep 0.5
  ses_id=$(sqlite3 ~/.local/share/opencode/opencode.db \
    "SELECT id FROM session WHERE parent_id IS NULL AND time_created > $START_MS ORDER BY time_created DESC LIMIT 1;")
  [ -n "$ses_id" ] && break
done
[ -n "$ses_id" ] && tmux set-option -w @opencode_session "$ses_id"
wait $OC_PID

Pro: DB is authoritative; no log format dependency
Con: Still polling; sqlite3 must be available

Option C: tmux-resurrect save hook

Hook into tmux-resurrect's save process. Before saving, for each pane running opencode, query the DB for the most recent session matching that pane's cwd:

# In ~/.tmux/plugins/tmux-resurrect/scripts/save.sh hook or
# ~/.config/tmux/resurrect-hook.sh
tmux list-panes -a -F '#{session_name} #{pane_current_path}' | while read name dir; do
  ses=$(sqlite3 ~/.local/share/opencode/opencode.db \
    "SELECT id FROM session WHERE directory='$dir' AND parent_id IS NULL
     ORDER BY time_updated DESC LIMIT 1;")
  [ -n "$ses" ] && tmux set-option -t "$name" @opencode_session "$ses"
done

Pro: No wrapper needed; works even for sessions you forgot to "done"
Con: Matches by directory, not by which opencode was actually running (ambiguous if you ran opencode multiple times in same dir)

Option D: opencode plugin

Write an opencode plugin (TypeScript) that subscribes to session.created bus events and writes the session ID to a known file or sets a tmux env var:

// ~/.config/opencode/plugins/tmux-session-id.ts
export default {
  onSessionCreated(session) {
    const { execSync } = require('child_process')
    execSync(`tmux set-option -w @opencode_session "${session.id}"`)
    // or: fs.writeFileSync('/tmp/opencode-session-id', session.id)
  }
}

Pro: Clean, event-driven, no polling
Con: Need to understand the plugin API; plugins currently have limited event access

Option E: Feature request to opencode

Request that opencode set OPENCODE_SESSION_ID in the environment at startup (like it already does with OPENCODE_PID). This would make everything trivial:

# From any bash tool call inside opencode:
tmux set-environment OPENCODE_SESSION "$OPENCODE_SESSION_ID"

Or the daily-log protocol could do it automatically at session start.
Pro: The right fix
Con: Requires upstream change


Restore Script (the other half)

Once tmux windows have @opencode_session set, the restore script is straightforward:

#!/bin/bash
# ~/.config/kitty/restore-workspace.sh
# Open a kitty tab per tmux session, resume opencode if session ID is stored

tmux list-sessions -F '#{session_name} #{session_path}' | while read name path; do
  ses_id=$(tmux show-option -gqv @opencode_session 2>/dev/null)
  # Build the command to run in the tab
  if [ -n "$ses_id" ]; then
    cmd="cd '$path' && opencode --session '$ses_id'"
  else
    cmd="cd '$path' && tmux attach -t '$name'"
  fi
  kitten @ launch --type=tab --tab-title "$name" --cwd "$path" -- bash -c "$cmd"
done

Bind in kitty.conf:

map ctrl+shift+r launch --type=background bash ~/.config/kitty/restore-workspace.sh

Recommended Approach (today)

  1. Option B (DB polling wrapper) as ~/bin/oc — best balance of reliability and simplicity
  2. Option C (resurrect save hook) as a safety net — catches sessions you didn't start with oc
  3. Notes protocol already handles clean exits via "done"
  4. File Option E as a GitHub issue against opencode

Longer term

  • opencode exposes OPENCODE_SESSION_ID in env → everything simplifies dramatically
  • Plugin API matures → Option D becomes clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment