The Sentry-to-Slack alert script, pointed at this project

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric Wagoner
2026-09-22 22:13:11 -04:00
co-authored by Claude Fable 5.1
parent b25abd4d69
commit 107e1893bc
2 changed files with 78 additions and 0 deletions
+5
View File
@@ -66,6 +66,11 @@ and backup check in with Sentry Crons when /root/.waving-hands-sentry-cron-rollu
and /root/.waving-hands-sentry-cron hold their check-in URLs (the ingest and /root/.waving-hands-sentry-cron hold their check-in URLs (the ingest
URL with the project's cron path and public key), so a missed night is noticed. URL with the project's cron path and public key), so a missed night is noticed.
To have every new issue, regression and reappearance posted to Slack the way
wizwar's are, run `deploy/sentry-slack-alert.sh [#channel]` once from your own
shell with `SENTRY_TOKEN` (an org auth token with alerts:write) and
`SLACK_CHANNEL_ID` set; the token never leaves the shell.
Before every deploy, `deploy/verify-ledgers.sh <ip>` fetches every production Before every deploy, `deploy/verify-ledgers.sh <ip>` fetches every production
ledger and replays it with the local engine, comparing each room with what the ledger and replays it with the local engine, comparing each room with what the
server shows. A ledger the new engine refuses or replays differently stops the server shows. A ledger the new engine refuses or replays differently stops the
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# Route every Waving Hands issue to a Slack channel: new issues (any level or
# priority), regressions, and reappearances. Creates one Sentry workflow
# via the alerts API. The token never leaves your shell:
# SENTRY_TOKEN=sntryu_... SLACK_CHANNEL_ID=C... deploy/sentry-slack-alert.sh [#channel]
# The token needs the alerts:write scope (an org auth token from
# https://locallygrownnet.sentry.io/settings/auth-tokens/), and Slack must
# already be connected to the org under Settings → Integrations.
set -euo pipefail
: "${SENTRY_TOKEN:?SENTRY_TOKEN=sntryu_... is required (alerts:write)}"
ORG="${SENTRY_ORG:-locallygrownnet}"
PROJECT="${SENTRY_PROJECT:-waving-hands}"
CHANNEL="${1:-#waving-hands-notifications}"
API="https://us.sentry.io/api/0/organizations/$ORG"
AUTH="Authorization: Bearer $SENTRY_TOKEN"
# The Slack action takes the channel's ID as well as its name; the
# project's error detector is looked up by project id; a workflow is bound
# to its detectors by PUT after creation.
SLACK_ID="${SENTRY_SLACK_INTEGRATION:-345334}"
CHANNEL_ID="${SLACK_CHANNEL_ID:?SLACK_CHANNEL_ID=C... is required (the Slack ID of the channel)}"
PROJECT_ID="${SENTRY_PROJECT_ID:-4512133232787456}"
echo "Slack integration $SLACK_ID; project $PROJECT_ID; channel $CHANNEL ($CHANNEL_ID)"
DETECTOR_ID=$(curl -s "$API/detectors/?project=$PROJECT_ID" -H "$AUTH" | python3 -c '
import json, sys
rows = json.load(sys.stdin)
for d in (rows if isinstance(rows, list) else []):
if str(d.get("projectId")) == sys.argv[1] and d.get("type") == "error": print(d["id"]); break' "$PROJECT_ID")
[ -n "$DETECTOR_ID" ] || { echo "no error detector for project $PROJECT_ID" >&2; exit 1; }
PAYLOAD=$(CHANNEL="$CHANNEL" CHANNEL_ID="$CHANNEL_ID" SLACK_ID="$SLACK_ID" DETECTOR_ID="$DETECTOR_ID" python3 -c '
import json, os
print(json.dumps({
"name": "Waving Hands → Slack",
"enabled": True,
"environment": None,
"config": {"frequency": 0},
"triggers": {
"logicType": "any-short",
"conditions": [
{"type": "first_seen_event", "comparison": True, "conditionResult": True},
{"type": "regression_event", "comparison": True, "conditionResult": True},
{"type": "reappeared_event", "comparison": True, "conditionResult": True},
],
"actions": [],
},
"actionFilters": [{
"logicType": "all",
"conditions": [],
"actions": [{
"type": "slack",
"integrationId": int(os.environ["SLACK_ID"]),
"data": {},
"config": {"targetType": "specific", "targetIdentifier": os.environ["CHANNEL_ID"], "targetDisplay": os.environ["CHANNEL"]},
"status": "active",
}],
}],
"detectorIds": [int(os.environ["DETECTOR_ID"])],
}))')
echo "creating the workflow…"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API/workflows/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD")
CODE=$(printf '%s' "$RESPONSE" | tail -1)
BODY=$(printf '%s' "$RESPONSE" | sed '$d')
if [ "$CODE" != "201" ]; then
echo "Sentry answered $CODE:" >&2; echo "$BODY" >&2; exit 1
fi
WORKFLOW_ID=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))')
echo "created workflow $WORKFLOW_ID"
# Binding to the detector is done by PUT, the whole workflow resent.
curl -s -o /dev/null -w "bound to detector $DETECTOR_ID: %{http_code}\n" -X PUT "$API/workflows/$WORKFLOW_ID/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD"
echo "done: https://$ORG.sentry.io/monitors/alerts/$WORKFLOW_ID/"