Lesson: report wrappers must parse their source, not guess its wording
Date: 2026-09-16 Lane: hermes Confidence: high
Context
~/.hermes/scripts/state_of_union_report.py (cron job 4fa833abcfab, daily
5 20 * * * Australia/Sydney) aggregates five child scripts and prints a card.
Two independent defects made its output unusable and mislabelled the fleet as
degraded.
What was actually wrong
-
Noise, not signal. The Nexus leg printed the child monitor's raw progress lines verbatim. The 20:05 AEST card carried
[crypto] Fetching health...| [crypto] Fetching pnl...| [crypto] Fetching engines...and the HA leg carried the literal stringNO_REPLYfrom a wrapper that had nothing to say. The card was built fromfirst_lines(child_output), which takes whatever the child happens to print, including progress chatter. -
Missing script misread as a healthy check.
run_scriptran/Users/dwayne/clawd/scripts/matter_health_report.py, deleted in commitdaa641d(2026-08-06, "Claw Automated Sync") along withmatter_healer.py,fleet-healer.pyandfleet_healer_report.py. The probe therefore returned a non-zero exit, and the caller only tested for"ACTION_REQUIRED"or"Nodes: 0". Afailed 2 - can't open fileline fell through to theelsebranch and was reported as "Matter server check is healthy." HA has held zero Matter config entries since the retirement; the Pro Matter server atws://100.87.212.111:5580/wsnow refuses connections outright (ConnectionRefusedError, verified live this session). The check had been vacuous for six weeks while reporting green. -
Substring matching on health keys. The Nexus fault test used
any(word in nexus.lower() for word in ("blackout","failed","error","unreachable")). The wrapper's own healthy evidence line endschanged=api_error unknown to False, so a clean run tripped the "error" keyword and was escalated as CRITICAL. -
Wrong schema assumptions in two places.
portfolio_summary()readtotal_value_aud/portfolio_value_aud/total; the real file (clawd/memory/portfolio-live-prices.json) nests one level deeper undertotals.combined_est_aud, so the portfolio line never rendered. The research and Gmail metric lines usedmemory_tail(), which prints the last N lines of a file and so emitted closing boilerplate (- None from this bounded scan.,}) instead of a headline. -
Cron status checked against an old format. The guard tested
"ERRORS: None" in cronand"DELIVERY FAILURES: None" in cron.cron-hourly-report.pynow prints theErrors:andDelivery:lines only when there is a problem, and printsOK: n/n enabledotherwise. The old string is never present, so a clean fleet was raised as a critical error on every run. -
Section bleed in the HA count. Counting
-bullets afterCritical candidates:without stopping at the next header swallowed theTop actionable non-noise:section too, producing "50 critical candidates" where the true figure is 15.
Rule
A summary wrapper must parse a child's structured output, or re-read the
child's own inputs. first_lines() over human-facing stdout is not parsing: it
inherits progress chatter and sentinels. When a probe script is missing, that is
an unknown, never a pass, and never a silently green "healthy" branch.
Also: never keyword-match on a health report's raw text. Emit and compare
explicit status keys (api_error=False, Nodes: 0), because evidence lines
routinely contain the words being searched for.
Fix as landed
state_of_union_report.py:
resolve_script()searches~/.hermes/scripts/then~/clawd/scripts/and returns a distinct": not found"marker instead of running a missing path.nexus_fault()matches whole-word signals (swarm sync failed,api_error=true,unreachable,timed out) instead of substrings.matter_entities_present()reads/Users/dwayne/.homeassistant/.storage/core.config_entries; with nomatterdomain entry the retired probe is reported asMatter is not in use: no HA Matter entities, probe retired.When the registry cannot be read it returnsTrue, so an unknown state still pages rather than passing silently.- Cron status is read from the presence of
Errors:/Delivery:/OK:lines. - HA candidates are counted within their own section only.
portfolio_summary()readstotals.combined_est_aud;research_scan_summary()reads the file'sUpdated:header;gmail_scan_summary()readsscanned_atandcurated_findings_count.--self-testguards each of the six behaviours with synthetic fixtures.
nexus_swarm_sync_report.py (both the ~/.hermes/scripts/ copy that cron
actually resolves and the ~/clawd/scripts/ copy): on an API error or a
fingerprint change it prints named status lines (Nexus crypto API unreachable (crypto_api=None)., change_lines()) instead of echoing the monitor's stdout.
crypto.errors stays excluded from the error test: its name is misleading, it
is a routine INFO/WARN log tail.
Verification
python3 -m py_compileclean on both files.python3 state_of_union_report.py --self-testprintsSELF-TEST: PASS, exit 0.- Live run 2026-09-16 20:34 AEST:
0 critical, 1 watch, 4 stable, replacing the pre-fix3 criticalwith no actionable content. HA count now 15, matching the child report. Portfolio line renders asPortfolio approx A$10,142. - Falsification for the Nexus parser: synthetic healthy line containing
api_error None to Falseis not flagged;Nexus swarm sync failed.andcrypto_api=Noneare flagged. - Matter retirement confirmed three independent ways: no
matterentry incore.config_entries(75 entries total), nomatter-named cron job injobs.json(ced6e4b366be matter-seal-healeris gone), andConnectionRefusedErroronws://100.87.212.111:5580/ws.
Files (absolute, for the next agent)
/Users/dwayne/.hermes/scripts/state_of_union_report.py/Users/dwayne/.hermes/scripts/nexus_swarm_sync_report.py/Users/dwayne/clawd/scripts/nexus_swarm_sync_report.py/Users/dwayne/clawd/memory/portfolio-live-prices.json(schema:totals.combined_est_aud)/Users/dwayne/clawd/memory/nexus-engine-status.json/Users/dwayne/.hermes/cron/jobs.json
Residual
~/.hermes/scripts/is gitignored (.gitignore:2:*), so the SoU fix is not version-controlled; the sibling~/clawd/scripts/state_of_union_report.pyis an older unreferenced copy and was deliberately left untouched.matter_self_heal.pystill exists in~/.hermes/scripts/but no cron job references it. Delete or repurpose when Matter is next touched.- 23 prompt-only enabled cron jobs remain (85.2% script-backed).