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.


Shoji saves the following per-space state:

Data Description
Layout name Active layout for each space (e.g. tall, bsp)
Main ratio The main area ratio (default 0.5)
Main count Number of main windows
Window order Ordered list of window IDs
Layout state Layout-specific data (BSP split ratios, Partition regions)
Fingerprint Snapshot for detecting stale state (see below)

Per-window state is also saved:

Data Description
Floating flag Whether a window was manually set to floating

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


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

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.


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.


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.


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.


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.


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.