From 62a7fbf695405d8804220c1cf1b280ee8d612082 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 19 Aug 2026 15:02:32 -0700 Subject: 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. --- claude-templates/bin/agent-text | 62 +++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 21 deletions(-) (limited to 'claude-templates/bin') 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 # -# 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 " >&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' '' --persist" >&2 + echo "agent-text: phone message failed ($why); fall back to the desktop channel: notify info 'Message' '' --persist" >&2 fi exit "$rc" -- cgit v1.2.3