dsh-safe
Zero-config boot-safe wrapper for dsh: auto-disables failing plugins, restores broken configs via --patch overlay isolation. No dsh source changes needed.
- Stars
- 0
- Language
- JavaScript
- Created
- Sep 2, 2026
- Updated
- Sep 3, 2026
Introduction
dsh-safe
| Check | Status |
|---|---|
| CI | |
| dsh latest | |
| dsh alpha |
Requires Node.js ^22.19 || >=24.0.0. Works with dsh >= 0.1.0 (no source modification needed).
When a plugin misconfiguration or bad config causes dsh to fail on startup, dsh-safe
automatically detects the failure, disables the offending plugin or restores the previous
working configuration, so dsh starts again — all without modifying any dsh source code.
Quick start
npm install -g @wzwolook/dsh-safe
echo 'alias dsh="dsh-safe"' >> ~/.zshrc && source ~/.zshrc # you can also run dsh-safe directly
# PowerShell (Windows): add `Set-Alias dsh dsh-safe` to $PROFILE instead
# One-time setup
dsh safe init
Verified on macOS and Linux. Windows is supported in code (spawns dsh through
cmd.exe to resolve npm's .cmd shim) but untested on a real Windows machine —
please report issues.
# Enable for your web profile (auto-enabled after init)
dsh safe activate --profile web
# Use it normally — boot-safe supervision is automatic
dsh web
Commands
| Command | Effect |
|---|---|
dsh safe init | One-time setup (boot-safe directory + minimal profile) |
dsh safe activate --profile <name> | Enable auto-recovery for a profile |
dsh safe deactivate --profile <name> | Pause auto-recovery |
dsh safe plugin-enable --profile <name> <id> | Re-enable a disabled plugin |
dsh safe history [--profile <name>] | View recovery log |
dsh web | Start web profile with automatic supervision |
dsh --profile <name> | Start any profile with automatic supervision |
How it works
dsh-safe wraps dsh as a child process:
- Fingerprint check — compares
cordis.patch.ymlandpackage.jsonhashes against last-known-good (a non-empty disable overlay always forces supervision) - Unchanged → passthrough — zero overhead, runs real
dshdirectly - Changed → supervise — spawns real
dshas child, monitors for a ready-signal file written by a tiny Cordis plugin (ready.mjs) injected via--patch; the plugin writes the signal only after the whole plugin tree has finished loading (the same condition dsh's own boot verdict uses), so a crash mid-load can never look like a success. A child that exits 0 without a ready signal is warned about and not recorded as a success. On success the fingerprints are recorded and the config is snapshotted as last-known-good - Failure → disable — extracts the failing entry id and name from stderr (the innermost
failed to import/apply loader entry <id> (<name>)frame; falls back to a pluginname → idmap via--dump-config), writes the id YAML-quoted to thedisabled.patch.ymloverlay, and retries. The config loader itself (include) is never disabled — its failures are reported with the root cause instead - Repeated failure → classify — a second observation of the same failure sorts it out: same name with a changed id means the config row has no stable id (the loader assigns a random one per boot), so the dead overlay entry is removed and the row is reported with file:line, root cause, and a suggested
id:to add; same name with the same id means the overlay does not address the entry, so the ineffective entry is removed and reported - YAML error → restore — preserves the broken file under
versions/broken-<timestamp>/, restores the last-known-good snapshot ofcordis.patch.yml, and reports both paths; with no known-good snapshot it says so and exits 1 without touching the broken file - Exhausted → hint — suggests
dsh --profile minimal
Zero source modification
dsh-safe requires no patching of dsh. It works with any dsh version by:
- Using
dsh --profile <name> --dump-configto build a pluginname → idmap (instead of modifying dsh's error output) - Using a tiny Cordis plugin (
ready.mjs) injected via--patchto signal boot completion (instead of a ready-file hook in dsh) - Implementing recovery logic in a standalone CLI wrapper
Recovery location
All state is stored under ~/.dsh/boot-safe/:
~/.dsh/
boot-safe/
profiles/<name>/
state.json # attempts, disabled list, last-known-good hashes
versions/<sha>/ # snapshots of cordis.patch.yml
versions/broken-<ts>/ # broken configs preserved before a restore
disabled.patch.yml # auto-generated disable overrides (loaded via --patch)
audit.log # full recovery history
profiles/minimal/ # fallback profile (base bundle only)