documentation

Troubleshooting and security

When a wake did not happen, start with unsnooze doctor. The security model and the local dev loop follow.

Troubleshooting

  • Something looks off? unsnooze doctor checks the whole install; --fix repairs what it can (including retiring leftovers of the old claude-session-guard install).
  • What has it been doing? unsnooze logs -f follows the log live; the dashboard's logs tab scrolls back with the mouse wheel.
  • A wake didn't happen? unsnooze preview <id> tells you exactly what's holding it back. After every real wake the pane is re-captured; if the limit banner reappears, unsnooze reschedules from the fresh banner, capped at five attempts.
  • A banner wasn't detected? unsnooze report [agent] captures the pane so you can paste it into an issue — that's how the experimental adapters get good.
  • Panes piling up? unsnooze reap lists finished panes and empty sessions (dry-run); --yes closes them.
  • Leaving? unsnooze uninstall removes wrappers and hooks; --purge removes state too.

Start with the symptom

Almost every report falls into one of these. The distinction that matters most is whether the session was recorded at all — that separates a detection problem from a wake problem, and they have different fixes.

  • Typing claude starts nothing watched. The shell wrapper lives in ~/.zshrc or ~/.bashrc, so it only applies to shells started after unsnooze setup ran. Open a new terminal, then confirm with unsnooze doctor. Nothing is protected until the wrapper is loaded, because the wrapper is the entry point — you never invoke unsnooze directly.
  • The limit hit but nothing was recorded. A detection problem. Either the StopFailure hook is not installed (doctor reports it) or the banner wording was not recognised. Capture it with unsnooze report — an unmatched banner is a one-release fix, but only if someone sends the text.
  • It was recorded but never woke. A wake problem, and unsnooze preview <id> names the reason rather than guessing. The usual answers are a guard deliberately holding it — see guards — or the five-attempt cap having been reached.
  • It woke, but at the wrong time. Read the provenance in unsnooze status: a reset shown as (absolute, from hook) came from the agent itself, while (absolute, from scrape) was read off the pane. A reset that looks hours out is a parsing bug worth reporting with the banner text attached — reset times are always absolute, never a relative countdown.
  • It resumed while still rate-limited. Expected and handled: the pane is re-captured after every wake, and if the banner is still there unsnooze reschedules from the fresh one. Overload is not a limit, so a transient overload message is not treated as one.
  • The machine was asleep at reset time. Wakes are dispatched by the daemon — a launchd agent on macOS, a systemd user unit on Linux. If you declined it during setup, nothing runs while the terminal is closed; doctor reports whether it is running and under which pid.

Security model

unsnooze is a scheduler that presses your keys — not an auto-approver. The short version of the contract:

  • Keys are typed only after proving the pane is yours — identity (ownership stamp or process-id + birth-time lease; pane ids get recycled, so a mismatch vetoes) and liveness (your agent foreground, not mid-stream). Unprovable → a fresh session is opened instead of typing.
  • Claude's limit menu is read before any key is sent; unreadable → nothing is pressed. It will never select "Upgrade your plan."
  • No --dangerously-skip-permissions, no auto-trust, no auto-approve, no touching MCP config — your agent's own permission model governs everything after the wake.
  • Nearly zero network: one daily version check, plus ntfy only if you configure it. Zero telemetry; state stays in ~/.unsnooze.
  • Releases are published to npm by CI with provenance.

Honest limits: unsnooze does inject keystrokes into your live terminal, and it does not sandbox your agent or defend against prompt injection — that's your agent's job. Full threat model and vulnerability reporting: SECURITY.md.

Development

dev loop
$ npm test                      # unit tests (node:test)
$ ./scripts/e2e-simulate.sh     # full detect → wait → re-open cycle in a
                                # scratch tmux session (no real limits needed)
$ bash -n scripts/e2e-zellij.sh # syntax-check the Zellij smoke test
$ vhs demo/demo.tape            # regenerate the demo gif (brew install vhs)

Releases are tagged (git tag v<version>, then git push origin v<version>) and published to npm by CI with provenance via trusted publishing — see .github/workflows/release.yml. Contributions: open an issue first for anything behavioral; adapter banner captures (unsnooze report) are always welcome.