feat(konsole): ✨ add session save/restore with staggered agent resume - #73
feat(konsole): ✨ add session save/restore with staggered agent resume#73justapithecus wants to merge 1 commit into
Conversation
## Summary
Add `konsole/session/`, an openSUSE Tumbleweed + KDE Plasma 6 utility that
snapshots every Konsole window and tab before a restart and rebuilds them at
the next login, resuming the Claude Code and Codex conversations that were
running in them instead of starting fresh sessions.
## Highlights
- **`konsole-session save`** — records desktop, screen, geometry, tab order,
titles, colors, profiles, working directories, and a resume command per tab
(Claude Code session id, Claude Code wrapper transcript, Codex rollout id).
Privileged commands are never recorded for replay.
- **`konsole-session restore`** — opens tabs immediately but starts their
commands one every `--stagger` seconds (default 20), most recently active
first, to avoid a CPU/memory spike; Ctrl-C in a tab skips its command.
Conversations already running elsewhere are not resumed twice.
- **`konsole-session-restore.service`** — oneshot login unit wanted by
`xdg-desktop-autostart.target`; hooking it to `plasma-workspace.target`
is an ordering cycle that systemd silently drops.
- **X11 and Wayland** — window class matching and maximize handling work
under both sessions.
- **`install.sh`** — copies (no symlinks) into `~/.local/bin` and
`~/.config/systemd/user`, guarded to openSUSE; installed separately from
`konsole/install.sh`.
## Known Limitations
- Scrollback, split views, and in-flight agent replies are not restored.
- Screen names differ between X11 and Wayland, so a snapshot taken under one
and restored under the other can place windows on the wrong screen.
## Test plan
- [x] Restore on a throwaway window: commands start most-recent-first at the
stagger interval, countdown shows, Ctrl-C skips, an already-running
conversation opens as a plain shell
- [x] Restore of a real 3-window / 28-tab snapshot under Wayland: windows on
their desktops and maximized, all 24 commands started on schedule
- [x] `save` on a live Wayland session
- [x] `systemd-analyze --user verify` shows no ordering cycle for the X11,
Wayland, and autostart targets
- [ ] Restore triggered by the unit at an actual login
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeWM5Cg4gj6bBkaTBWjsaT
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: befd0cf12b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for pid in pids(): | ||
| ids.update(UUID_RE.findall(" ".join(proc_argv(pid)))) |
There was a problem hiding this comment.
Include live Codex rollouts in duplicate detection
When a snapshot is restored manually while its original codex process is still running, a session started as plain codex has no conversation UUID in its argv, so this scan misses it and launches codex resume <id> concurrently despite the stated duplicate-prevention behavior. Inspect live Codex processes' rollout file descriptors here, as codex_rollout() already does during saving, to avoid forking or concurrently writing the conversation.
Useful? React with 👍 / 👎.
| shutil.move(path, os.path.join(archive, "snapshot.json")) | ||
| log(f"restored {launched} windows; {len(delays)} commands start over {max(delays.values(), default=0)}s; archived in {archive}") |
There was a problem hiding this comment.
Preserve the snapshot when window launches fail
If launch_window() fails for one or even every window—for example because Konsole or its D-Bus service is temporarily unavailable—the exception is only logged and this unconditional move consumes the default snapshot while the service still exits successfully. The next login therefore cannot retry the missing windows, and manually restoring the archived full snapshot would also duplicate any windows that did succeed; retain a retryable snapshot containing failed windows or return a failure instead.
Useful? React with 👍 / 👎.
| else: # several windows in one process: the caption is the active tab's title | ||
| placement = next((w for w in kwin_windows if w["caption"].startswith(tabs[active]["title"])), None) |
There was a problem hiding this comment.
Disambiguate windows that share active-tab titles
When one Konsole process owns multiple windows whose active tabs have the same title—or one title is a prefix of another—next() selects the same first KWin window for each match. Both saved windows consequently receive identical placement data while another window's desktop, screen, and geometry are lost; matched KWin entries need to be consumed or correlated using a unique property.
Useful? React with 👍 / 👎.
| if tab["command"]: | ||
| launcher = os.path.join(tab_dir, f"window{index}-tab{n}") | ||
| write_tab_launcher(launcher, tab, delays[id(tab)]) | ||
| fields.append(f"command: {launcher}") # Konsole splits this on spaces, hence a script path |
There was a problem hiding this comment.
Base stagger deadlines on one restore start time
Each delay is relative to when that tab's launcher starts, but windows are launched sequentially, so tabs in later windows begin their countdown later than tabs with the same global schedule. If an earlier window takes longer than the stagger interval to initialize, an older rank in that window can run before the rank-zero conversation in a later window, defeating both the promised ordering and spacing; pass absolute deadlines from a common restore epoch instead.
Useful? React with 👍 / 👎.
| os.makedirs(os.path.dirname(path), exist_ok=True) | ||
| with open(path + ".tmp", "w") as f: | ||
| json.dump({"savedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "windows": windows}, f, indent=2) |
There was a problem hiding this comment.
Keep captured command lines private
On a system using a conventional 022 umask with a traversable home or custom state directory, this creates snapshot.json as mode 0644, even though it records arbitrary process argv that can contain access tokens, passwords, or private URLs. The archived launcher scripts repeat those commands and are explicitly made 0755, so the state directory and command-bearing files should be created with user-only permissions.
Useful? React with 👍 / 👎.
Summary
Add
konsole/session/, an openSUSE Tumbleweed + KDE Plasma 6 utility that snapshots every Konsole window and tab before a restart and rebuilds them at the next login, resuming the Claude Code and Codex conversations that were running in them instead of starting fresh sessions.Highlights
konsole-session save— records desktop, screen, geometry, tab order, titles, colors, profiles, working directories, and a resume command per tab (Claude Code session id, Claude Code wrapper transcript, Codex rollout id). Privileged commands are never recorded for replay.konsole-session restore— opens tabs immediately but starts their commands one every--staggerseconds (default 20), most recently active first, to avoid a CPU/memory spike; Ctrl-C in a tab skips its command. Conversations already running elsewhere are not resumed twice.konsole-session-restore.service— oneshot login unit wanted byxdg-desktop-autostart.target; hooking it toplasma-workspace.targetis an ordering cycle that systemd silently drops.install.sh— copies (no symlinks) into~/.local/binand~/.config/systemd/user, guarded to openSUSE; installed separately fromkonsole/install.sh.Known Limitations
Test plan
saveon a live Wayland sessionsystemd-analyze --user verifyshows no ordering cycle for the X11, Wayland, and autostart targetsNo-Issue: personal dotfiles tooling, not tracked in Linear
🤖 Generated with Claude Code
https://claude.ai/code/session_01DeWM5Cg4gj6bBkaTBWjsaT