Session closeout — 12 Sep 2026

2026-09-12 21:47 · 2 deliverable(s) · auto-closeout

tg-noise-audit.md SAVE-STATE-2026-09-12-A3.md

tg-noise-audit.md

12,852 bytes · modified 2026-09-12 21:45

Telegram Noise Audit — M2 — 2026-09-12

Read-only audit. Nothing sent, nothing patched. Scope: M2 local only (~/bin, ~/.aios/state,

~/AI_OS/ops/scripts, ~/AI_OS/ops/launchagents + ~/Library/LaunchAgents, ~/AI_OS/skills/*/scripts,

~/AI_OS/agents). .bak-* files excluded from counts (dead weight, not live code paths).

0. How the intended system works

${AIOS_TG_MUTE:-$HOME/.aios/state/tg-mute.conf} with grep -qxF "$TG_CLASS" "$mute"; a match

suppresses the send (logs to ~/.aios/state/tg-muted.log) and exits 0 before touching the

network. Sanitizes to plain text via tg-sanitize.py, never sets parse_mode.

fallback) used by agent scripts. It re-implements the same class/mute gate (tg_class_gate(),

lines 20-27) before either send path runs. Correct, but it is a second copy of the logic, not a

call into tg itself.

Both gates above grep -qxF this file. The mute mechanism itself works for any caller that

(a) goes through tg or send-tg.sh, and (b) passes the correct --class.

(rejects .md/.txt), SHA-256 dedup (24h), title validation, mobile parse-before-paint budget,

live-link probe, and (new 2026-09-12) HTML→PDF render so nothing unopenable ships. This is the

correct path for anything that should "open on the phone" and it enforces that well.

1. Answer to "is the mute conf effective?" — PARTIALLY, NOT COMPREHENSIVELY

The conf line-matching and both gate implementations are correct and verified working. But the

mute conf only protects traffic that **voluntarily routes through tg or send-tg.sh AND self-labels

correctly**. Two independent leaks bypass it entirely, both currently live:

1. Direct curl to api.telegram.org — 13 scripts do this (list below). None of them can be

muted by editing tg-mute.conf; the file is never read.

2. Unclassed calls through tg/send-tg.shTG_CLASS defaults to manual, which is never

muted. a0-inbox-watcher.sh and cleif-a6-orchestrator.sh call tg/send-tg.sh with no

--class for content that is functionally progress/status chatter, so it always reaches the

phone regardless of the mute list.

3. telegram-alert-lib.sh — a third, fully independent send path (own cooldown/repeat-cap

guard, aios_telegram_send_guarded()) with no concept of classes or the mute conf at all.

Sourced by 9 scripts.

So: editing tg-mute.conf today changes behavior only for nano-dispatch-watch.sh's two lines

(classed progress/alert — the one fully-correct example in the fleet) and any future caller

that copies that pattern. It does nothing for the other ~20 sender call-sites.

2. Every caller found (file:line, class, schedule)

Through tg / send-tg.sh (mute-conf-aware)

File:lineClassScheduled?
ops/scripts/nano-dispatch-watch.sh:58progress (correctly muted)LaunchAgent com.aios.nano-dispatch-watch, every 1200s
ops/scripts/nano-dispatch-watch.sh:61alert (correctly always-on)same
ops/scripts/a0-inbox-watcher.sh:84 (bash "$SEND" --msg "$summary", no --class)unclassed → manual (always sends)LaunchAgent com.aios.a0-inbox-watcher, every 300s
ops/scripts/cleif-a6-orchestrator.sh:35,48,56,66,165,179,181 (tg "...", no --class)unclassed → manualnot in any LaunchAgent/cron/ps right now — interactive-run only

Direct curl/requests.post to api.telegram.org (mute conf NEVER consulted)

File:linePayload noteScheduled?
ops/scripts/news-digest.py:593parse_mode: HTML (violates plaintext rule) and message body ends Full list: comms/outputs/intel/<date>-digest.md — a bare .md pathLaunchAgent com.aios.news-digest, 09:03 + 21:03 daily
ops/scripts/claude-inbox-executor.sh:203 (telegram_notify())"Starting task: …" / "TIMEOUT" / "FAILED" / "Done: …" — the Starting-task line is pure progress chatter, sent every task cycle, unmuteableLaunchAgent com.aios.claude-inbox-executor, every 300s
ops/scripts/email_triage/email_triage.py:409parse_mode: HTML (violates plaintext rule); "Email Triage — N processed" digestnot scheduled/running currently (no LaunchAgent, no ps hit)
ops/scripts/telegram-alert-lib.sh:219 (aios_telegram_send_guarded)shared lib, own cooldown/cap, no class awarenessnot itself scheduled, but sourced by 9 other scripts (below)
ops/scripts/watchkeeper.sh:529not scheduled/running currently
ops/scripts/watchkeeper-dispatch.sh:135not scheduled/running currently
ops/scripts/claude-session-close.sh:150"[Claude Session End] …"not wired to the live Stop hook (closeout-stop-hook.sh does not call it) — orphaned unless run manually
ops/scripts/fleet-status.sh:102markdown parse_mode=Markdownnot scheduled/running currently
ops/scripts/screen-preview.sh:32,41not scheduled/running currently
ops/scripts/dual-host-sync.sh:95not scheduled/running currently
ops/scripts/telegram-autopilot-control-daemon.sh:598ack replies to inbound commandsnot scheduled/running currently
ops/scripts/cleif-a6-orchestrator.sh:29,117test/heredoc example lines, same file also uses tg elsewherenot scheduled
ops/scripts/sally-relay/sally_relay.py:146separate bot (Sally), own tokennot scheduled/running currently

Sourcing telegram-alert-lib.sh (inherits the no-class, direct-curl behavior above)

ops/bin/notify, ops/scripts/mini-claude-tab-autopilot.sh, ops/scripts/heartbeat-notify.sh,

ops/scripts/claude-capacity-watcher.sh, ops/scripts/license-check.sh,

ops/scripts/auto-model-guard.sh, ops/scripts/mission-control.sh — none currently in a

LaunchAgent plist or running process on M2 (checked launchctl list + ps aux), so dormant unless

run by hand. heartbeat-notify.sh and claude-capacity-watcher.sh are exactly the name-pattern

Sean flagged ("heartbeat", "watcher") and would bypass the mute conf entirely the moment either is

re-enabled.

.md/.txt path or "saved to …" as the payload itself

subject: frontmatter line, the message falls back to the bare .md filename.

scheduled senders embed one inside an otherwise-fine message.

Bypasses tg via direct curl (ignore mute conf) — consolidated

13 files: news-digest.py, claude-inbox-executor.sh, email_triage.py, telegram-alert-lib.sh,

watchkeeper.sh, watchkeeper-dispatch.sh, claude-session-close.sh, fleet-status.sh,

screen-preview.sh, dual-host-sync.sh, telegram-autopilot-control-daemon.sh,

cleif-a6-orchestrator.sh (partial), sally_relay.py.

"watcher"/"heartbeat"/"digest"/"staleness" named + scheduled

false lead, ignore.

telegram string match; out of scope for this audit.

3. Counts

(3 via tg/send-tg.sh, 13 via direct curl, 7 more via sourcing telegram-alert-lib.sh)

(13 direct-curl + 7 lib-based; a0-inbox-watcher.sh and cleif-a6-orchestrator.sh also

effectively unclassed since they never pass --class)

a0-inbox-watcher.sh fallback case)

(a0-inbox-watcher.sh 300s, claude-inbox-executor.sh 300s, nano-dispatch-watch.sh 1200s,

news-digest.py 2x/day)

4. Top 5 offenders (file:line)

1. ops/scripts/news-digest.py:593 — scheduled 2x/day, parse_mode=HTML (plaintext-rule

violation) AND ships a bare .md path as part of the payload. Worst of the set: hits three

separate rules at once (HTML, unclassed/uncontrolled, path-as-payload).

2. ops/scripts/claude-inbox-executor.sh:203 (telegram_notify) — scheduled every 300s, direct

curl, zero class awareness; "Starting task" sends are pure progress chatter with no way to mute

without editing the script.

3. ops/scripts/telegram-alert-lib.sh:219 — shared library with its own independent guard/cooldown

system, completely outside the class/mute contract; 9 scripts source it, 2 of which

(heartbeat-notify.sh, claude-capacity-watcher.sh) are named exactly what Sean asked to stop

seeing, dormant only because nothing currently schedules them.

4. ops/scripts/a0-inbox-watcher.sh:84 — scheduled every 300s via send-tg.sh --msg with no

--class; always-sends by default, and falls back to a bare .md filename in the body when a

note lacks frontmatter (line 75).

5. ops/scripts/email_triage/email_triage.py:409 — direct curl, parse_mode=HTML; not currently

scheduled, but it's A8's own triage output path and the next thing wired to a cron/LaunchAgent

would ship HTML-formatted text straight past tg's sanitizer.

5. Proposed patch list (NOT applied — awaiting Sean's GO)

FileOne-line change
ops/scripts/news-digest.pyDrop "parse_mode": "HTML" from the _telegram_send payload; route the send through ~/bin/tg --class digest "<message>" instead of a raw requests/urllib POST, and replace the Full list: <path> line with a tg-publish.sh-delivered link (or drop it) so no .md path ships.
ops/scripts/claude-inbox-executor.shReplace the local telegram_notify() curl with "$HOME/bin/tg" --class progress "$msg" for the Starting-task line and --class done/--class alert for Done/Failed/Timeout, so the mute conf actually governs it.
ops/scripts/telegram-alert-lib.shAdd a TG_CLASS param to aios_telegram_send_guarded() and check tg-mute.conf before the curl call (same one-liner gate tg already uses), so every one of its 9 callers inherits muting for free.
ops/scripts/a0-inbox-watcher.shCall bash "$SEND" --class progress --msg "$summary" (line 84) instead of unclassed --msg, and change line 75's fallback from ${subj:-$f} to ${subj:-"(untitled note)"} so a bare filename never ships.
ops/scripts/email_triage/email_triage.pyDrop "parse_mode": "HTML" and swap the raw requests.post for a subprocess.run(["$HOME/bin/tg", "--class", "digest", text]) call so the sanitizer and mute conf both apply.
ops/scripts/cleif-a6-orchestrator.shAdd --class progress to the intermediate status lines (35,48,56,66,165) and keep the final complete/exit lines (179,181) as --class done/--class alert.
ops/scripts/watchkeeper.sh, watchkeeper-dispatch.sh, fleet-status.sh, screen-preview.sh, dual-host-sync.sh, telegram-autopilot-control-daemon.shReplace each direct curl .../sendMessage with a call to ~/bin/tg [--class <appropriate>] so none of these re-enabled-later scripts can silently bypass the mute conf again.
ops/scripts/claude-session-close.shEither delete (it's orphaned — the live Stop hook closeout-stop-hook.sh doesn't call it) or, if intentionally kept as a manual tool, route its send through ~/bin/tg --class done.

6. Open questions for Sean

before anyone deletes it — it may still be invoked by a device other than M2.

system by design. Worth deciding whether Sally should ever inherit class discipline, or stays

separate on purpose.

SAVE-STATE-2026-09-12-A3.md

5,508 bytes · modified 2026-09-12 21:29

SAVE STATE — A3 session, 2026-09-12 ~00:00 PT (M2)

Resume from here. Everything below is verified with a command unless marked PENDING.

Done this session

1. skills/computer-use/ built and live on M2 + Nano (23 files md5-identical). Router v1, predict.mjs, ICM v2 all resolve it; icm/blocks-lint.py clean on 101 blocks. Memory reference_computer_use_skill. Summary: https://claude.ai/code/artifact/7802b182-6fd8-4e3e-bd0f-b43431a58d93

2. Handoff audit (Sean's ask). Five cards were handed M2→Nano 23:23–23:44 PT:

slugstate at auditnow
overnight-council-to-nanoDONE 23:46 (3 receivers, tg done sent)HTML summary sent (artifact 797c8fc3)
stray-rsync-dir-auditDONE 23:45 (tg done sent)HTML summary sent (artifact 6bc1b07d)
scanbot-L1-L4partially started (a first receiver had launched Codex detached at 23:52; the pool launch itself exited 2)RELAUNCHED 23:55 → DONE 00:20 PT. Codex built core+L1+L2 then hit its usage window (reopens 03:53 PT); Claude builders finished L3/L4/CLI/tests. Independently verified by A3: 212 tests OK, compileall clean, 71 files in build/, old ops/scripts/scanbot/ untouched. Two FINAL blocks in the packet — "FINAL v2" supersedes. Cross-model review of L3/L4 NOT done. HTML summary relayed as artifact (see TG #3)
telegram-ingress-proofnever startedRELAUNCHED 23:55 → DONE 00:00 PT. Bridge PASS (26 s synthetic turn); phone ingress UNFED (no poller loaded). Result in comms/handoffs/20260912T062747Z__… (not the inbox card). HTML summary relayed as artifact 8117ae06
video-cutroom-v06never startedRELAUNCHED 23:55 → DONE 23:59 PT. Lane dirs + READY.md; NOT render-ready; 3 installs need GO; npm name is hyperframes. HTML summary relayed as artifact 0cce8335

Receivers deliver via tg-publish = HTML attachment (unopenable on iOS, B65) — coordinator relays each as an Artifact link; 2 TG messages sent so far (links 1-3, then 4-5), 1 reserved for scanbot.

Root cause of the three stalls: the per-handoff runner copy shipped by handoff.sh (stdin < resume-runner.sh/tmp/aios-resume-runner-<TS>-<slug>.sh) arrived syntax-broken at the --disallowedTools line; nano-run reported __NANO_RUN_DONE__ exit=2. The canonical runner on Nano is intact (bash -n OK) — relaunched with it via pool.sh run.

3. HTML-summary rule encoded (Sean 09-12: every finished task sends an HTML summary to Telegram, whoever finishes it): added to ops/scripts/handoff/handoff.sh card template, resume-runner.sh receiver tail, and the three staged cards on Nano. Delivery form = hosted link (Artifact), never an HTML attachment (B65); tg-publish --no-send ledgers each file.

4. One tg --class done sent with the three links + relaunch status.

CLOSED 2026-09-12 00:25 PT — all 5 handoffs DONE on Nano, all delivered as hosted links (3 TG messages)

summarylink
overnight-council-to-nanohttps://claude.ai/code/artifact/797c8fc3-9ba9-46be-b637-2dd9806749d8
stray-rsync-dir-audithttps://claude.ai/code/artifact/6bc1b07d-6214-4604-bd94-37419e39f688
computer-use-skill (A3)https://claude.ai/code/artifact/7802b182-6fd8-4e3e-bd0f-b43431a58d93
video-cutroom-v06https://claude.ai/code/artifact/0cce8335-b416-4978-a80f-e2b1b71fe3c2
telegram-ingress-proofhttps://claude.ai/code/artifact/8117ae06-8242-46b0-a626-3bac4898dfed
scanbot-L1-L4https://claude.ai/code/artifact/eece4f23-be25-46b3-bdc1-c2576ac7b78e

Receiver monitor ran to completion and timed out cleanly; no receiver is still running (pool.sh status nano = idle).

PENDING

Sean decisions open

Pointers