Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ Optional: enable **Tailscale Serve** on your tailnet for HTTPS preview URLs —
run `tailscale serve --bg <port>` for `https://<name>.<tailnet>.ts.net`, and `tailscale serve off` when done.
- **`docker run -p 8080:80 …`** publishes to loopback, so containers are reachable at `devbox:8080`
over the tailnet and invisible to the internet. Publishing to `0.0.0.0` bypasses this — see FOOTGUNS.
- **`claude` in any repo** pushes to your phone when it finishes or needs input (and stays quiet
while you're typing in tmux). A **"devbox health"** push is the hourly monitor flagging low disk
or a failed unit.
- **`claude` in any repo** pushes to your phone only when it's genuinely waiting on you — idle
after handing control back, or blocked on a decision — plus turn failures. It deliberately does
*not* ping on every completed turn (that floods during autonomous multi-step work), and stays
quiet while you're active in tmux. A **"devbox health"** push is the hourly monitor flagging low
disk or a failed unit.

## Rebuild and teardown

Expand Down
25 changes: 18 additions & 7 deletions files/claude-notify.tmpl
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
#!/usr/bin/env bash
# Claude Code hook -> Pushover push notifications. Hook JSON on stdin.
# Skips pushes when you're actively typing in the tmux session running Claude.
# Pushes ONLY when Claude genuinely needs you — it's idle waiting for your next
# prompt, or blocked on a permission — or a turn errored. It deliberately does
# NOT fire on Stop: Stop runs once per turn and has no field distinguishing
# "done, over to you" from "stopped mid-workflow, resuming" (confirmed via the
# hooks docs), so during autonomous multi-step work it floods you with a push
# per intermediate turn. `idle_prompt` is the real "handed control back to you"
# signal and self-filters those intermediate stops.
# Also skips pushes while you're active at the tmux session.
# Rendered from claude-notify.tmpl by setup-user.sh (placeholders from secrets.env).
PO_TOKEN="__PUSHOVER_TOKEN__"
PO_USER="__PUSHOVER_USER__"
PRESENCE_WINDOW=60

payload=$(cat)
event=$(jq -r '.hook_event_name // "event"' <<<"$payload")
ntype=$(jq -r '.notification_type // empty' <<<"$payload")
proj=$(basename "$(jq -r '.cwd // "?"' <<<"$payload")")

# Presence check: if a tmux client on this session saw keyboard input in the
Expand All @@ -23,14 +31,17 @@ fi
sound=""
case "$event" in
Notification)
title="Claude needs input · $proj"
# Denylist the pure-noise sub-types (login confirmations, MCP form
# internals); everything else pushes — notably idle_prompt (Claude waiting
# on you), permission_prompt, agent_needs_input, elicitation_dialog. Using a
# denylist (not an allowlist) means a missing/renamed notification_type
# fails LOUD (still pushes) rather than silently swallowing every alert.
case "$ntype" in
auth_success|elicitation_complete|elicitation_response) exit 0 ;;
esac
title="Claude needs you · $proj"
body=$(jq -r '.message // "Claude is waiting for you"' <<<"$payload")
po_prio=1; sound=intermission ;;
Stop)
title="Claude finished · $proj"
body=$(jq -r '.last_assistant_message // empty' <<<"$payload" | tr '\n\t' ' ' | sed 's/ */ /g' | head -c 280)
body=${body:-"Turn complete"}
po_prio=0 ;;
StopFailure)
title="Claude turn FAILED · $proj"
body="The turn ended on an API error (rate limit / server / auth). Check the session."
Expand Down
11 changes: 0 additions & 11 deletions files/claude-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/home/__DEV_USER__/.local/bin/claude-notify",
"async": true
}
]
}
],
"StopFailure": [
{
"hooks": [
Expand Down
Loading