Skip to content

State persistence

Shoji automatically saves tiling state to disk so your layout choices, window order, and per-space settings survive Hammerspoon restarts and reloads.


What is persisted

Shoji saves the following per-space state:

DataDescription
Layout nameActive layout for each space (e.g. tall, bsp)
Main ratioThe main area ratio (default 0.5)
Main countNumber of main windows
Window orderOrdered list of window IDs
Layout stateLayout-specific data (BSP split ratios, Partition regions)
FingerprintSnapshot for detecting stale state (see below)

Per-window state is also saved:

DataDescription
Floating flagWhether a window was manually set to floating

Extension state is persisted when an extension implements serialize and deserialize callbacks. See Extensions for details.


What is not persisted

The following state is transient and lost on restart:

  • Floating frames: The remembered position when toggling float is not saved
  • Zoom state: Temporarily zoomed windows revert to their tiled position
  • Window frame cache: Cached geometries used for diff-based updates
  • Index caches: O(1) lookup caches rebuilt on demand
  • Space index: Bidirectional window-to-space mappings
  • Focus history: The focus_last action’s history resets
  • Layout MRU: Most recently used layout ordering

File location

State is saved to a JSON file following the XDG Base Directory convention:

~/.local/state/shoji/state.json

If XDG_STATE_HOME is set, Shoji uses that instead:

$XDG_STATE_HOME/shoji/state.json

Shoji creates the directory automatically for the default path. A .bak backup file is maintained alongside the main file for crash recovery.


Save behavior

State is saved automatically when any tracked state changes (layout switch, window order change, ratio adjustment, etc.). Saves are debounced with a 2-second delay to coalesce rapid changes. On shutdown, any pending save is flushed immediately.

Writes are atomic: Shoji writes to a .tmp file first, backs up the existing file to .bak, then renames the temp file into place. If the rename fails, the backup is restored.


Fingerprinting

Each space’s persisted state includes a fingerprint — a snapshot of the window count, layout name, and screen UUID at save time. On load, Shoji compares the saved fingerprint with the current state of each space.

If the fingerprint does not match (for example, the window count changed because apps were opened or closed while Hammerspoon was not running), Shoji logs a warning but still applies the saved state. The restored window order is pruned of stale window IDs that no longer exist.

Fingerprints help with diagnostics: when state restoration produces unexpected results, the Hammerspoon console shows which spaces had mismatched fingerprints.


Space key stability

macOS assigns numeric space IDs that can change across restarts. Shoji uses stable keys in the format screenUUID:spaceIndex (e.g. 37D8832A-2D66-02CA-B9F7-8F30571A110E:2 for the second space on a screen) to map saved state back to the correct space.

If a stable key cannot be resolved (for example, after removing a monitor), Shoji falls back to the raw space ID stored alongside the key.


Version migration

The state file includes a version field (currently 2). When the version in the file does not match the expected version, Shoji discards the entire file and logs a warning:

Discarding state file with version 1 (expected 2)

Individual layouts can also version their state using stateVersion. When a layout’s saved state version does not match the current layout version, only that layout’s state is discarded — other data is preserved.


Resetting state

To clear all saved state, delete the state file:

Terminal window
rm ~/.local/state/shoji/state.json

Then reload Hammerspoon. Shoji starts fresh with default settings for all spaces.

To reset a single space without deleting the file, use the reset IPC command:

Terminal window
hs -c 'spoon.Shoji:cmd("reset")'

This resets the current space’s layout, ratio, and main count to defaults.