diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-19 15:02:32 -0700 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-19 15:02:32 -0700 |
| commit | 62a7fbf695405d8804220c1cf1b280ee8d612082 (patch) | |
| tree | 66f5c4123b512fcdb2422b335a7e14d5e5446167 /claude-templates | |
| parent | 10d4e3b17dddbe8df47a80444688d13be15dc016 (diff) | |
| download | rulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.tar.gz rulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.zip | |
fix(agent-text): relay through a host that holds the account
The relay target was hardcoded to velox, and velox's reinstall wiped its signal-cli registration. A send from velox then relayed to itself, and any machine without a local account relayed into the same dead end. Only ratio still worked, because it holds the account as a linked device and sends directly. The old failure text blamed velox for being down. It was up, just unregistered.
The target is now an ordered list, overridable per invocation, and the loop skips any candidate matching this machine's name. The skip is the actual fix. Reaching the relay branch means the account isn't local, so relaying to ourselves can't work whatever the list order. Both sides of that comparison are domain-stripped, or an FQDN nodename would silently disable the guard.
A relay host that is reachable but unregistered exits 1, not 0, so the loop advances instead of reporting a delivery that never happened. I checked that rather than assume it. Silent non-delivery is the worst failure this tool has, and a test pins it.
The failure message now names which branch failed, so a local send failure no longer sends anyone chasing the tailnet.
protocols.org and the pager runbook both asserted the topology the reinstall destroyed. They now say how to derive which machine holds the account instead of naming one. The snapshot form is what rotted. The runbook's linking recipe is marked impossible for now, since the primary's keys are gone and a linked device can't authorize another.
Diffstat (limited to 'claude-templates')
| -rw-r--r-- | claude-templates/.ai/protocols.org | 10 | ||||
| -rwxr-xr-x | claude-templates/bin/agent-text | 62 |
2 files changed, 48 insertions, 24 deletions
diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index 3e32074..bf0e6f7 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -469,13 +469,17 @@ Two channels, two trigger words. "page me" is the desktop, "text me" is the phon agent-text "Message for Craig's phone" #+end_src - =agent-text= (in =~/.local/bin= via the rulesets install) sends from the dedicated Signal identity (+15045173983) to Craig's Signal account UUID, firing a normal mobile push. The account is registered on velox (primary) and ratio (linked device), so either sends directly; a machine without it ssh-relays to velox. Verified end to end 2026-07-13 (velox) and 2026-07-20 (ratio). Never target Craig's phone *number* (it reads as unregistered in Signal's directory); the script targets the UUID. + =agent-text= (in =~/.local/bin= via the rulesets install) sends from the dedicated Signal identity (+15045173983) to Craig's Signal account UUID, firing a normal mobile push. Never target Craig's phone *number* (it reads as unregistered in Signal's directory); the script targets the UUID. - Caveats: a relay from a non-linked machine needs velox up on the tailnet, and each device holding the account wants a periodic =receive= (the signal-receive timer handles that). The full runbook lives in rulesets =docs/design/=. + *Which machine holds the account is a runtime fact, not a fixed one — derive it, don't assume it.* Any machine whose local =signal-cli= holds the identity sends directly, whether as the registered primary or as a linked device; a machine without it ssh-relays to the first relay host that does. Check the local half with =signal-cli listAccounts=. I write it this way because the old wording named velox as the permanent primary, and velox's 2026-08-13 reinstall wiped that registration — which broke the doc and the script together, since =agent-text= had the same host hardcoded as its only relay target. + + The relay list lives in =AGENT_TEXT_RELAYS= at the top of the script, tried in order, and is overridable for a one-off (=AGENT_TEXT_RELAYS="host1 host2" agent-text "..."=). It skips any candidate whose short name matches =uname -n=: reaching the relay branch means the account is *not* local, so an ssh round trip to this machine lands on the same empty store and cannot succeed. + + Caveats: a relay needs at least one listed host up on the tailnet *and* holding the account, and each device holding it wants a periodic =receive= (the signal-receive timer handles that). A relay to a host that is reachable but unregistered fails cleanly rather than silently — =signal-cli send= exits 1 with "User <number> is not registered", so the loop moves on to the next candidate (verified 2026-08-19). The full runbook lives in rulesets =docs/design/=. - *"text and page me" — both.* Fire =agent-text= and =notify= together. The phone reaches him now, the desktop note waits for his return. This is the default when a run can't tell whether he's away. -On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same identity), fine to use there, but it exists only in velox's local MCP config, so =agent-text= is the portable habit. The tool was named =agent-page= before 2026-07-20; a deprecated =agent-page= shim still delegates to =agent-text=. Do *not* use the old =page-signal= shell script (removed 2026-06-12). +A *signal-mcp* tool (=send_message_to_user=, same identity) has existed in one machine's local MCP config before now. Treat it as a nicety that may or may not be registered on the machine you're on — it was gone from velox after the 2026-08-13 reinstall — and use =agent-text= as the habit, since that one travels with the rulesets install. The tool was named =agent-page= before 2026-07-20; a deprecated =agent-page= shim still delegates to =agent-text=. Do *not* use the old =page-signal= shell script (removed 2026-06-12). * Session Protocols diff --git a/claude-templates/bin/agent-text b/claude-templates/bin/agent-text index 86aa933..2eccb2e 100755 --- a/claude-templates/bin/agent-text +++ b/claude-templates/bin/agent-text @@ -5,31 +5,38 @@ # # Usage: agent-text <message...> # -# The Signal identity (+15045173983) is registered in velox's signal-cli, and -# any daily driver linked as a device of that account (ratio, 2026-07-20) can -# send directly too. So the dispatch is: if the account is registered in the -# local signal-cli, send directly; otherwise ssh-relay the send to velox over -# the tailnet. A direct send from a linked device still lands when velox is -# down (the reason ratio was linked). The recipient is Craig's Signal account -# UUID; his phone number reads as unregistered in Signal's directory, so never -# target the number. Verified end to end 2026-07-13 (velox) and 2026-07-20 -# (ratio, direct). +# The Signal identity (+15045173983) is held by any daily driver that has it in +# its local signal-cli, whether as the registered primary or as a linked device +# (ratio, 2026-07-20). So the dispatch is: if the account is registered locally, +# send directly; otherwise ssh-relay the send to the first relay host that +# actually holds it — a reachable but unregistered host fails and the loop +# advances. The recipient is Craig's Signal account UUID; his phone +# number reads as unregistered in Signal's directory, so never target the +# number. Verified end to end 2026-07-13 (velox) and 2026-07-20 (ratio, +# direct). The relay path was verified velox -> ratio on 2026-08-16 by +# archsetup, who reported the exact relay command returning a Signal +# timestamp with rc 0; that one is on report, not re-run here. # # This is the AWAY channel. At his desk, use the desktop channel instead: # notify info "Title" "Message" --persist # See protocols.org "Reaching Craig" for choosing between them. # -# Known caveats (full runbook in rulesets docs/design/): a relay from a -# non-linked machine needs velox up on the tailnet, and each device holding the -# account wants a periodic `receive` (staleness warnings appear otherwise); the -# signal-receive timer handles that. +# Known caveats (full runbook in rulesets docs/design/): a relay needs at least +# one listed host that is both up on the tailnet AND holding the account -- a +# reachable host without it fails and the loop moves on. Each device holding the +# account wants a +# periodic `receive` (staleness warnings appear otherwise); the signal-receive +# timer handles that. # # Source: ~/code/rulesets/claude-templates/bin/agent-text # Install: make -C ~/code/rulesets install SIGNAL_ACCOUNT="+15045173983" CRAIG_UUID="b1b5601e-6126-47f8-afaa-0a59f5188fde" -VELOX_HOST="velox.tailf3bb8c.ts.net" +# Relay hosts, tried in order until one sends. ratio leads because it is the +# always-on desktop at home, while velox is the laptop that travels and sleeps. +# Override for a one-off with AGENT_TEXT_RELAYS="host1 host2". +AGENT_TEXT_RELAYS="${AGENT_TEXT_RELAYS:-ratio.tailf3bb8c.ts.net velox.tailf3bb8c.ts.net}" if [ $# -eq 0 ]; then echo "usage: agent-text <message...>" >&2 @@ -37,21 +44,34 @@ if [ $# -eq 0 ]; then fi msg="$*" +self="$(uname -n)" # The account is local if this machine's signal-cli holds it: the registered -# primary (velox) or any linked device. Those send directly. +# primary or any linked device. Those send directly. if signal-cli listAccounts 2>/dev/null | grep -q "$SIGNAL_ACCOUNT"; then signal-cli -a "$SIGNAL_ACCOUNT" send -m "$msg" "$CRAIG_UUID" rc=$? + why="local signal-cli send failed" else - # printf %q hardens the message for the remote shell. - ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \ - "$VELOX_HOST" \ - "signal-cli -a $SIGNAL_ACCOUNT send -m $(printf '%q' "$msg") $CRAIG_UUID" - rc=$? + rc=1 + why="no relay reachable in: $AGENT_TEXT_RELAYS" + for host in $AGENT_TEXT_RELAYS; do + # Never relay to this machine. Reaching this branch means the account is + # NOT local, so an ssh round trip to ourselves lands on the same empty + # signal-cli and cannot succeed. velox hit exactly that after its + # 2026-08-13 reinstall wiped the registration: the only relay target was + # velox, so the fallback pointed at the one machine guaranteed to fail. + [ "${host%%.*}" = "${self%%.*}" ] && continue + # printf %q hardens the message for the remote shell. + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \ + "$host" \ + "signal-cli -a $SIGNAL_ACCOUNT send -m $(printf '%q' "$msg") $CRAIG_UUID" + rc=$? + [ "$rc" -eq 0 ] && break + done fi if [ "$rc" -ne 0 ]; then - echo "agent-text: phone message failed (velox down or unreachable?); fall back to the desktop channel: notify info 'Message' '<message>' --persist" >&2 + echo "agent-text: phone message failed ($why); fall back to the desktop channel: notify info 'Message' '<message>' --persist" >&2 fi exit "$rc" |
