Rule: Any launcher / dispatch / orchestration script that produces external side-effects (ntfy ping / email / Slack message / webhook POST / GitHub PR comment / git push to non-personal-fork / external API call) MUST:
- Have a
--dry-runflag that prints what it WOULD do but does NOT execute the side-effect. - Default-validate scripts in
--dry-runbefore running the real path during testing. - Route ALL side-effects through a single helper function (e.g.
ntfy_send,slack_post,email_send) that respects the dry-run flag — never inlinecurl ntfy.sh/...in multiple places.
Why (Rich-trigger 2026-05-03T21:30 BST): I added an A2-dispatch precheck to ~/testatetech/docs-strategy/scripts/dispatch-zeta-3-spike.sh that fires BLOCKED [zeta-3 A2]: A1 closure-bundle missing; A2 not dispatched to ntfy.sh/davieshq2026 when A2 is dispatched without A1’s closure-bundle. I then ran the script live to test the precheck. Rich’s phone pinged unexpectedly — he hadn’t asked for a spike to run, and the message looked like a real failure rather than a smoke-test artefact. He flagged it: “i received a ntfy zeta-3 A2 A1 closure-bundle missing. A2 not dispatched”.
Important nuance — Rich likes ntfy and wants more of it (Rich-clarification 2026-05-04):
This is NOT a “minimise ntfy” rule. Rich’s stated position:
“i like using ntfy when we are using claude, and would like to use it more”
ntfy is a deliberate Claude→Rich phone-alert channel for the moments where Rich is genuinely waiting to hear something. Use it more, not less. Specifically:
- Heartbeats during long autonomous runs (~every 15 min with
[N/M]progress markers per global CLAUDE.md §7). - Completions as the LAST action after a verified push (per autopilot prompt techniques).
- Decisions made during unattended sessions —
DECISION-MADE [N/7]: <what> — <rationale>rather than asking and blocking. - Real blockers the operator genuinely needs to action — but only when the launcher is in real mode, not smoke-testing.
The rule below is narrower than “avoid ntfy”: it says smoke-tests must not fire ntfy through the real topic. The fix is --dry-run-by-default + a single suppression-aware helper, not “send fewer messages.”
Topic: default to ntfy.sh/davieshq2026 unless project CLAUDE.md overrides. Subscribe via the ntfy.sh app or https://ntfy.sh/davieshq2026.
How to apply:
- Before authoring any launcher script with side-effects, plan the
--dry-runflag from the start. - Route side-effects through
<verb>_sendhelpers — single point of suppression. - Smoke-test new dispatch logic with
--dry-runfirst; only exercise the real side-effect path when (a) Rich is expecting it OR (b) the side-effect target is a test/sandbox topic (e.g.ntfy.sh/test-davies-noise), never the real one. - For ntfy specifically: the canonical TT topic
davieshq2026is the user’s personal phone alert. A test topic lives in stale instructions but isn’t currently configured — if smoke-testing requires real ntfy, ask Rich to confirm the test topic before running. - This applies retroactively too: any existing launcher script without
--dry-runshould have one added when next touched.
Anti-pattern caught 2026-05-03: curl -fsS -d "$msg" ntfy.sh/$NTFY_TOPIC inlined directly inside the main flow + smoke-tested live without —dry-run = real phone ping during validation pass.
Cross-reference:
- Fixed at commit pending in docs-strategy main;
ntfy_send()helper introduced +--dry-runflag added. - Related discipline:
feedback_unattended_mode_when_scope_locked(don’t ping when not asked);feedback_executing_actions_with_care(low-blast-radius before high-blast-radius).