macOS-like session restore for Omarchy / Hyprland.
Reboot, log out, or crash — and come back to the same apps on the same workspaces: terminals in the folders you left them, Chrome profiles with their tabs, coding agents back in the same conversation. Dock your laptop and get the docked layout; undock and get the laptop layout. No manual rearrange.
Omarchy already has a great desktop. What it does not ship is “put my windows back.” This plugin fills that gap with a keep-loaded service that autosaves quietly and restores once on the next login.
| Plugin id | eric.session-restore |
| Version | 1.3.6 |
| License | MIT |
| Author | Eric Westphal <BonanzaEric@gmail.com> |
| Based on | wbarakat/omarchy-session-restore (MIT, © 2026 Waleed) |
Plugins such as Tableau by Novuon save a desktop under a name
(Capture Current Desktop) and switch to that desk when you ask.
Super + Alt + T opens the switcher. Switching closes the desktop you
are on. Tableau waits until you pick a desk; it does not restore one at login.
Session Restore is the automatic one. It keeps a quiet copy of whatever you are already doing and puts those same windows back after a reboot or logout.
You need Omarchy 4.5 or newer, with the plugin system (omarchy plugin …).
Open a terminal (Super + Return) and paste:
omarchy plugin add https://github.com/westphae/omarchy-session-restore.git --enableOmarchy warns you that a plugin runs as your user, then asks you to confirm.
Say yes. Omarchy clones this repo into
~/.config/omarchy/plugins/eric.session-restore and turns it on. Autosave
and restore start on their own.
That Git URL is the supported install. Use the same URL in the menu below.
Super + Space → Setup → Plugins → Add Plugin.
A terminal opens and asks for a Git URL. Paste:
https://github.com/westphae/omarchy-session-restore.git
Confirm the prompts. When it asks whether to enable the plugin, say yes.
The command above passes --enable for you; the menu waits for that answer.
If you leave it off, turn it on later from
Setup → Plugins → Enable Plugin, or:
omarchy plugin enable eric.session-restoreSkip all of these and the plugin still saves and restores. Each one adds something specific.
1. A short command. So you can type omarchy-session in a terminal.
Omarchy’s default shell already includes ~/.local/bin. The menu rows in
step 2 call this path.
ln -sf ~/.config/omarchy/plugins/eric.session-restore/bin/omarchy-session ~/.local/bin/omarchy-session2. Menu rows, for a snapshot as you leave. System → Logout, Reboot, and Shutdown save your windows once more before Omarchy closes them, and System → Save Session shows up. Do step 1 first.
Without these rows, a reboot still restores the latest autosave, which can be about a minute old. The rows matter because Omarchy closes windows on the way out, and a save in the middle of that can record a half-empty desk. The menu snapshot runs first and then freezes autosave so those closing windows cannot replace it.
If ~/.config/omarchy/extensions/omarchy-menu.jsonc does not exist yet:
mkdir -p ~/.config/omarchy/extensions
cp ~/.config/omarchy/plugins/eric.session-restore/extensions/omarchy-menu-snippet.jsonc \
~/.config/omarchy/extensions/omarchy-menu.jsonc
omarchy menu refreshIf that file already exists, add the four keys from the snippet
(system.session-save, system.logout, system.reboot,
system.shutdown) inside the existing { }. They override the stock
logout, reboot, and shutdown actions. Keep a single object: a second { or
a missing comma makes Omarchy ignore the whole extensions file. Then run
omarchy menu refresh.
The snippet uses $HOME, which the menu expands. If you already customized
those three system actions, put omarchy-session save --final --quiet; in
front of your command instead of replacing it.
You do not need to change ~/.bashrc or ~/.zshrc. The folder a terminal
reopens in is the interactive shell's directory, read from /proc at save
time. That includes Ghostty and Omarchy's floating terminal.
Optional: Atuin session and Ghostty tabs. If you want the same Atuin history and the saved program started inside the new shell, install the hook yourself. It is not part of the normal install and nothing writes it for you:
omarchy-session install-hookThat adds one line to ~/.bashrc (and ~/.zshrc if you have one). New
shells pick it up; ones already open keep working as they are until you
restart them. Uninstall it with omarchy-session uninstall-hook.
Update (Omarchy shows the diff before it applies):
omarchy plugin update eric.session-restorePause autosave and auto-restore, and leave the plugin installed:
omarchy-session disableomarchy plugin disable eric.session-restore, or
Setup → Plugins → Disable Plugin, unloads the plugin.
Turn it back on with omarchy-session enable or
omarchy plugin enable eric.session-restore.
Remove:
omarchy-session uninstall-hook # only if you installed the shell hook
omarchy plugin remove eric.session-restore
rm -f ~/.local/bin/omarchy-sessionSetup → Plugins → Remove Plugin removes it the same way. If you merged
the menu snippet, delete those four keys from
~/.config/omarchy/extensions/omarchy-menu.jsonc and run
omarchy menu refresh. Saved sessions stay in
~/.local/state/omarchy/session-restore, and settings stay in
~/.config/omarchy/session-restore.json, until you delete them.
-
Install and enable (above), and say yes to the prompt.
-
Open a few apps and arrange your workspaces. Wait about a minute, or save now:
omarchy-session save
Without the short command:
~/.config/omarchy/plugins/eric.session-restore/bin/omarchy-session save -
Reboot or log out the way you usually do. With the menu rows installed, use System → Reboot (or Logout / Shutdown) so the last snapshot is taken as the windows close.
-
Log back in. The windows come back on their workspaces.
See what is saved, or what a restore would launch, without changing anything:
omarchy-session status
omarchy-session restore --dry-runBrowsers only reopen their tabs if the browser itself restores them. In Chrome (and Chromium-family browsers):
Settings → On startup → Continue where you left off
Do that once per profile you care about. This plugin launches each profile
with --profile-directory=… and --restore-last-session.
A normal logout, and a reboot that Chrome records as SessionEnded, closes
windows one by one, so Chrome then remembers only the last window of each
profile. On every save that changes the window list, and on the final
pre-logout save, the plugin keeps a private copy of that profile's Chrome
session files and puts that copy back before launch. A crash is left alone —
Chrome's own file still has the full session.
Several Chrome profiles share one browser process. The process's
--profile-directory only names the profile that was launched first, and
Chrome sometimes rewrites that argument as an unquoted Profile 1. With more
than one saved profile, windows are matched by title instead of by that flag.
A terminal whose class is not the emulator's own (Omarchy's floating terminal
is org.omarchy.terminal on top of ghostty) is relaunched with --class=
so the new window is the one restore is waiting for.
Windows are matched to workspaces by title. Unread counts and a shortened
tab title (JLCPCB, Gmail) still match, and each profile only takes its
own windows. If something is still missing after new windows stop appearing,
the plugin sends Chrome's reopen-closed-window shortcut (the same action as
History → recently closed), once per missing window.
Each window is placed by its name: the XDG tag when the app sets one,
otherwise the title Hyprland saw when the window was created (initialTitle),
otherwise the current title. Before launch, a temporary Hyprland rule opens
that named window on its saved workspace, so a late Chrome window does not
sit on workspace 1 until something moves it. The rule is removed when restore
finishes. A later pass moves any window whose name is recognizable but which
is still on the wrong workspace. The move is checked, and if the numeric
workspace id does not stick, it is retried with the workspace's name.
Tiled windows are then ordered left to right. On the scrolling layout each window is given its own column; a directional "swap right" is not used, because that dispatcher has no neighbor there and leaves the windows stacked.
- One reboot, same desk. Apps, workspaces, floating size/position, fullscreen, maximized, and pin come back without a restore wizard.
- Terminals that feel continuous. Each window returns in its working directory, read from the shell itself. The optional shell hook adds the same Atuin session and Ghostty tabs. A coding agent resumes into the same conversation (Claude, Codex, Grok, Cursor Agent, Gemini, …).
- Multi-profile Chrome that just works. Chromium-family browsers launch
once per profile with
--profile-directory=…, so the profile picker never interrupts restore. A clean logout no longer collapses each profile to its last window. Pair with Chrome → Settings → On startup → Continue where you left off. - Docked vs undocked layouts. A separate layout is remembered for each display fingerprint (make/model/serial + geometry). Hotplug settles, then windows re-place themselves. First time on a new dock or USB-C display you arrange once; after that it is automatic.
- Safe by default. Atomic state files (mode 600), restore-at-most-once, dry-run mode, and optional menu rows that take a final snapshot before Omarchy closes windows for logout / reboot / shutdown.
- Install in one command. Public Git URL — no cloning required.
| Area | What you get |
|---|---|
| Windows | Apps relaunched on the workspaces they were on; floating geometry, fullscreen, maximized, pin |
| Terminals | ghostty (including org.omarchy.terminal), kitty, alacritty, foot, wezterm, and any TerminalEmulator — the shell's cwd, no shell rc changes; optional Atuin session + tabs + agent resume |
| Browsers | Chrome / Chromium / Brave / Edge / Vivaldi / … once per profile; Firefox-family once; web apps via .desktop / --app= |
| Other apps | Flatpak, AppImage, and argv from /proc — multi-window apps launch once, extras matched by title |
| Per-display | Fingerprint-keyed layouts under ~/.local/state/omarchy/session-restore/layouts/ |
| Autosave | Every 60 s and ~3 s after window changes; skipped while the system is stopping |
| Menu | Optional: Save Session, plus Logout / Reboot / Shutdown that snapshot first |
| CLI | status, save, restore --dry-run, --force, --prev, disable/enable |
| Kind | Detection | Restore behavior |
|---|---|---|
| Terminals | process / class table + TerminalEmulator desktop category |
One window per saved window. The directory is /proc/<shell>/cwd of the interactive shell that is the terminal's direct child — not vim, ssh, or an agent. Several Ghostty windows in one process are paired with those shells in start order. If a window cannot be paired, a fresh hook directory is used when it identifies exactly one folder, otherwise $HOME. Known TUIs relaunch; coding agents resume. The shell hook is optional and only adds the Atuin session and tab handoff. |
| Browsers | WebBrowser desktop category / binary table |
Chromium-family: once per profile with --profile-directory=…. A pre-logout session snapshot is restored after a clean exit. Firefox-family: once. Windows moved to saved workspaces by title. |
| Web apps | chrome-<site>-Default-style classes |
Via .desktop (StartupWMClass), else --app=URL, else URL from class. |
| Everything else | /proc/<pid>/cmdline, flatpak cgroup, $APPIMAGE, .desktop |
Same argv / flatpak run / AppImage / desktop Exec. Multi-window apps launch once; extras matched by title. |
Floating state, size/position (floating windows), fullscreen/maximized and
pin are re-applied. Per-workspace layout mode (dwindle / scrolling / floating)
is restored when Omarchy’s o.workspace_mode is available. Windows on special
(scratchpad) workspaces are saved/restored when restore_special is true
(default).
A separate window layout is remembered for each display configuration,
like macOS. The fingerprint is the sorted list of connected monitors
identified by make/model/serial (Hyprland’s description — the same identity
Omarchy uses for workspace blocks) plus resolution, scale and relative
position — not the connector name (HDMI-A-1), which a dock can rename.
Layouts live under ~/.local/state/omarchy/session-restore/layouts/<hash>/
and are never consumed on restore, so docking again can find them. On monitor
hotplug the daemon waits monitor_settle_seconds (default 3), saves the
previous layout, then re-places already-open windows to match the layout for
the new fingerprint when one exists. A brand-new display config (first
time on that dock or portable display) gets Hyprland’s defaults until you
arrange windows and a save runs.
| Feature | Support | Notes |
|---|---|---|
| App launch + workspace | full | terminals (cwd/Atuin/tabs/agents), browsers (per Chrome profile), web apps, other apps |
| Floating / tiled | full | floating flag toggled on restore |
| Floating size + position | full | clamped onto a live monitor if the saved display is gone |
| Fullscreen / maximized | full | |
| Pin (all workspaces) | full | Hyprland pin; not macOS-style edge snap |
| Monitor placement | full | workspace moved onto the matching physical monitor by identity |
| Per-display layouts | full | fingerprint-keyed; hotplug settle + relayout |
| Workspace layout mode | partial | dwindle / scrolling / floating via o.workspace_mode. Tiled windows are ordered left to right (scrolling: one column each). Exact dwindle split tree is not exposed by Hyprland |
| Scrolling column widths | unsupported | hyprscrolling options are global; per-column widths not in IPC |
| Special / scratchpad | partial | saved/restored by name (special:scratchpad); may interact with Omarchy qconsole |
| Window groups | unsupported | grouped count is recorded; membership is not rebuilt |
There is no native Hyprland “layout dump.” Everything goes through
hyprctl -j plus this plugin’s own session JSON.
No shell rc change is required. At save time each terminal window's pid
comes from Hyprland. The interactive shell that is that process's direct
child (bash, zsh, fish, and the other shells the plugin already knows) is
the one whose /proc/<pid>/cwd is stored. That is the folder you cd'd
into, even when vim, ssh, or an agent is running in front of it. Ghostty
windows that share one process, including Omarchy's floating
org.omarchy.terminal, are matched to those shells in the order they
started. A unique directory in the window title can break a tie. If a
window still cannot be matched, or /proc cannot be read, a fresh hook
file is used only when it names exactly one directory; otherwise the
terminal reopens in your home directory.
The hook is optional. Install it only for the same Atuin session and for Ghostty tabs that claim their saved state:
omarchy-session install-hookIt adds one line to ~/.bashrc (and ~/.zshrc if present) right after
atuin init, with a backup:
[ -f ~/.config/omarchy/plugins/eric.session-restore/shell/session-hook.sh ] && . ~/.config/omarchy/plugins/eric.session-restore/shell/session-hook.sh # omarchy-session-restore hookAt shell start the hook records $ATUIN_SESSION and $PWD under
$XDG_RUNTIME_DIR/omarchy-session-restore/ (mode 600). The directory there
is a fallback, not the normal source. When a restore opened the terminal,
the hook claims that tab’s saved state: cd, export ATUIN_SESSION=…,
and the program to resume at the first prompt so the shell stays open after
it exits. Values are validated and shell-quoted.
Atuin. Reusing a session id is safe — Atuin stores it as a plain column
with no uniqueness rule. The session filter (Ctrl-R, cycle filter mode) shows
that window’s commands from before the reboot. Optional:
filter_mode_shell_up_key_binding = "session" for per-window up-arrow.
Tabs. Ghostty tabs come back via D-Bus (new-tab) with each tab’s cwd,
Atuin session and program. Splits come back as tabs (split layout isn’t
readable from outside). Other terminals get extra tabs as separate windows on
the same workspace.
Coding agents. Covers claude, codex, grok, cursor-agent, gemini, opencode,
kimi, and many others (binary, mise/npm shim, or node …/cli.js). Session id
from live state, original argv, or newest transcript — otherwise the agent’s
own “continue latest” (claude --continue, codex resume --last, …).
Permission-skipping flags are never added; one-shot runs (claude -p, …) are
not restarted; no prompt is ever sent. Agents inside herdr are left to herdr.
Without the hook, terminals still return in the shell's folder with the program and agent resumed, but each gets a new Atuin session, and a Ghostty window's extra tabs open as separate windows on the same workspace.
Not restored. Windows running a one-shot command — Omarchy's floating
update or install window (bash -c 'omarchy-update …'), or any terminal
started with -e <script> — are not saved; relaunching them would only leave
an empty floating shell. Tabs that attach to the same persistent session
(herdr, tmux new -A -s …, zellij attach …) come back once, not once per
tab, since every copy would show the same session.
Service.qml(headless omarchy-shell service) runsbin/omarchy-session daemon.- On start the daemon restores once, then autosaves every 60 s and ~3 s after window open/close/move/float/fullscreen events.
- With the optional menu snippet, Logout / Reboot / Shutdown run
omarchy-session save --finalfirst (exact final snapshot; freezes autosave for 2 minutes so Omarchy’s “close all windows” step cannot overwrite it), then the stockomarchy-systemcommand. - Launch commands are computed at save time (while
/procstill knows everything) and stored as argv lists. Restore never runs a saved string throughsh -c.
- State in
~/.local/state/omarchy/session-restore/(mode 700);session.jsonis mode 600, written atomically; previous distinct version kept assession.prev.json. - Restored at most once: moved to
restored/before launch; hash inlast-restore.json. - Skipped if saved by the current Hyprland session, older than
max_age_hours(default 7 days), or Hyprland has been up longer than 15 minutes. - Apps already open (e.g. autostart) are claimed, not double-launched.
omarchy-session status # daemon, saved session, last restore
omarchy-session save # save now (also: System → Save Session, after the menu snippet)
omarchy-session restore --dry-run # print what would launch (alias --print)
omarchy-session restore --force # restore now even if checks would skip
omarchy-session restore --prev # previous good copy
omarchy-session restore --file F # specific file (not consumed)
omarchy-session restore --file F --workspace 4
omarchy-session install-hook # optional: Atuin session + tab handoff (not required for directories)
omarchy-session uninstall-hook # only if you installed that hook
omarchy-session disable | enableLog: ~/.local/state/omarchy/session-restore/session-restore.log
{
"enabled": true,
"max_age_hours": 168,
"max_login_age_seconds": 900,
"save_interval_seconds": 60,
"browser_wait_seconds": 150,
"browser_settle_seconds": 12,
"restore_chromium_session": true,
"skip_classes": ["org.pulseaudio.pavucontrol"],
"resume_commands": ["herdr", "tmux", "zellij", "nvim", "btop", "lazygit", "yazi"],
"notify": true,
"resume_agents": true,
"per_display": true,
"restore_special": true,
"monitor_settle_seconds": 3
}| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Autosave + auto-restore |
max_age_hours |
168 |
Don’t restore sessions older than this |
max_login_age_seconds |
900 |
Only auto-restore this soon after Hyprland starts |
save_interval_seconds |
60 |
Periodic autosave interval |
browser_wait_seconds |
150 |
Cap on how long to wait for browser windows. Restore returns as soon as every saved window has matched. While new windows are still appearing the cap can stretch by half again |
browser_settle_seconds |
12 |
Quiet period with no new windows before reopening a missing Chrome window |
restore_chromium_session |
true |
After a clean Chrome exit, restore the pre-logout session snapshot so every window comes back |
skip_classes |
[] |
Window classes never saved (full-match regexes) |
resume_commands |
(built-in TUI list) | Foreground programs to relaunch in terminals |
notify |
true |
Desktop notifications on restore |
resume_agents |
true |
Resume coding agents into their conversation |
per_display |
true |
Separate layout per display fingerprint |
restore_special |
true |
Save/restore special/scratchpad workspaces |
monitor_settle_seconds |
3 |
Delay after monitor hotplug before switching layouts |
- Dwindle split tree — Hyprland does not expose the exact split tree over IPC. Workspace mode (dwindle / scrolling / floating) is restored; exact split ratios are not. Tiled windows are ordered left to right (on the scrolling layout, each in its own column). If the layout still disagrees, drag one past the other once.
- Scrolling column widths — hyprscrolling column widths are not in IPC.
- First time on a new dock / display — no saved layout yet; arrange once, then autosave remembers it for next time.
- Browsers need their own session restore (Chrome: Continue where you
left off). Titles are used to match windows to workspaces. After a clean
exit the plugin restores Chrome's pre-logout session so windows the logout
close dropped are not lost; turn that off with
restore_chromium_session. - Terminal scrollback and arbitrary running processes are not preserved — only the shell's folder, an Atuin session (only if you installed the optional hook), known TUIs, and coding-agent conversations.
- Ghostty splits come back as tabs; tab-to-window assignment is based on
shell start times. Ghostty restored windows run with
--gtk-single-instance=falseso--working-directoryis honored. - In-app documents reopen however the app normally does, unless they were on the original command line.
- A hard power-off still restores the last autosave (≤ 60 s old). Logging out
via
uwsm stop/ a keybinding instead of the menu skips the final snapshot.
- On Logout / Reboot / Shutdown (
save --final), Chromium-family browsers are asked to quit cleanly before Omarchy closes windows, so profiles record a normal exit and the pre-logout session snapshot can be installed on the next login instead of Chrome's crash-restore bubble. - Ctrl+Shift+T reopen waits longer while
--restore-last-sessionis still opening windows, and never fires within a few seconds of a profile's first window — avoiding duplicate tabs/windows after a crashy exit. - A New Tab / What's New placeholder for a profile with unmatched saved windows is claimed as that profile's stand-in, moved to the saved workspace, and used as the reopen target when title matching would fail.
Derived from
wbarakat/omarchy-session-restore
(MIT, © 2026 Waleed) — rewritten in Python with generic app detection, atomic
state, restore-once guarantees, dry-run mode, per-profile Chrome launch, and
per-display layouts. See LICENSE.
MIT © 2026 Waleed and Eric Westphal.
Questions / bugs: BonanzaEric@gmail.com · github.com/westphae/omarchy-session-restore