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:
| 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.
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_lastaction’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.jsonIf XDG_STATE_HOME is set, Shoji uses that instead:
$XDG_STATE_HOME/shoji/state.jsonShoji 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:
rm ~/.local/state/shoji/state.jsonThen 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:
hs -c 'spoon.Shoji:cmd("reset")'This resets the current space’s layout, ratio, and main count to defaults.