Add chrome-error-scan.sh and box chrome-errors action

Self-contained per-profile chrome log scanner with watermark-based
new-match detection. Box CLI action returns JSON per-profile counts.

Session: sidechat/chromebox-ops
This commit is contained in:
operator-main
2026-10-04 18:34:09 +00:00
parent 31ed4e2f99
commit 2db0d92d5a
2 changed files with 125 additions and 0 deletions
+44
View File
@@ -650,6 +650,25 @@ def act_notify(agent, message, sidechat=None, allow_main_chat=False):
out(True, agent=agent, target=target, sent=True)
def act_watchdog_alerts():
"""Check for new failed browser relaunches since the watermark.
Runs bin/watchdog-alert-check.sh, which exits 0 (no new failures)
or 1 (new failures printed to stdout) and maintains its own
watermark at NETVM_ROOT/watchdog-alert-watermark.txt.
"""
audit("watchdog-alerts")
script = BIN / "watchdog-alert-check.sh"
r = subprocess.run([str(script)], capture_output=True, text=True, timeout=30)
failures = [l for l in (r.stdout or "").splitlines() if l.strip()]
# Exit 0 = no new failures, exit 1 = new failures found. Any other
# exit code is a script error.
if r.returncode not in (0, 1):
fail("WATCHDOG_CHECK_ERROR", "watchdog-alert-check.sh failed",
{"stderr": (r.stderr or "").strip()[-500:]})
out(True, new_failures=len(failures), failures=failures)
def act_fleet_status():
audit("fleet-status")
cmd = [sys.executable, str(BIN / "super-cli.py"), "fleet", "status", "--json"]
@@ -678,6 +697,21 @@ def act_relay_health():
out(all_healthy, relays=results, healthy=all_healthy)
def act_chrome_errors():
"""Run chrome-error-scan.sh and return per-profile error counts as JSON."""
audit("chrome-errors")
cmd = ["/bin/bash", str(BIN / "chrome-error-scan.sh"), "--json"]
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode == 0:
try:
data = json.loads(r.stdout)
out(True, profiles=data)
return
except Exception:
pass
fail("SCAN_ERROR", "chrome-error-scan.sh failed", {"stderr": r.stderr})
def act_dm_log(limit=50):
audit("dm-log", str(limit))
cmd = [sys.executable, str(BIN / "super-cli.py"), "dm", "log", "--json", "-n", str(limit)]
@@ -1064,6 +1098,8 @@ USAGE = """usage: box-ctl.py <action> [args]
fleet:
fleet-status
chrome-errors per-profile chrome FATAL/crash counts (new since watermark)
watchdog-alerts
relay-health
timer actions:
@@ -1291,8 +1327,16 @@ def main(argv):
fail("BAD_NAME", "usage: policy [check <agent>|show]")
elif action == "fleet-status":
act_fleet_status()
elif action == "watchdog-alerts":
if rest:
fail("BAD_NAME", "usage: watchdog-alerts")
act_watchdog_alerts()
elif action == "relay-health":
act_relay_health()
elif action == "chrome-errors":
if rest:
fail("BAD_ARGS", "usage: chrome-errors")
act_chrome_errors()
elif action == "dm-log":
limit = 50
if rest:
+81
View File
@@ -0,0 +1,81 @@
#!/bin/bash
# chrome-error-scan.sh - scan per-profile chrome logs for concerning patterns.
# Self-contained: scans, compares against watermark, reports only NEW matches.
#
# Usage: chrome-error-scan.sh [--json]
# Default output: "profile:new_count" lines for profiles with new matches,
# or "OK: no new errors" if clean.
# --json: output JSON {"profile": {"total": N, "new": M}, ...}
#
# Watermark: /home/super/Projects/NetVM/chrome-error-watermark.json
# Patterns: FATAL, crash, segfault, out of memory (case-insensitive)
LOGDIR="/home/super/Projects/NetVM"
WATERMARK="$LOGDIR/chrome-error-watermark.json"
# Gather current counts per profile (grep -c prints 0 with exit 1 on no match;
# the || true masks the exit code while preserving the "0" on stdout)
get_count() {
local f="$LOGDIR/chromebox-$1.log"
if [ -f "$f" ]; then
grep -ciE "FATAL|crash|segfault|out of memory" "$f" 2>/dev/null || true
else
echo 0
fi
}
MUSE_C=$(get_count muse)
PIP_C=$(get_count pip)
N646_C=$(get_count 646)
OPM_C=$(get_count opm)
python3 - "$WATERMARK" "$MUSE_C" "$PIP_C" "$N646_C" "$OPM_C" "$1" <<'PYEOF'
import json, sys, os
watermark_path = sys.argv[1]
current = {
"muse": int(sys.argv[2]),
"pip": int(sys.argv[3]),
"646": int(sys.argv[4]),
"opm": int(sys.argv[5]),
}
as_json = len(sys.argv) > 6 and sys.argv[6] == "--json"
# Load watermark (tolerate missing/corrupt file -> treat as all-zero)
watermark = {}
if os.path.exists(watermark_path):
try:
with open(watermark_path) as f:
data = json.load(f)
watermark = data.get("counts", data) if isinstance(data, dict) else {}
except (ValueError, IOError, OSError):
watermark = {}
new_counts = {}
for prof in ("muse", "pip", "646", "opm"):
old = watermark.get(prof, 0)
try:
old = int(old)
except (TypeError, ValueError):
old = 0
delta = current[prof] - old
new_counts[prof] = max(delta, 0)
if as_json:
out = {p: {"total": current[p], "new": new_counts[p]} for p in current}
print(json.dumps(out))
else:
any_new = False
for prof in ("muse", "pip", "646", "opm"):
if new_counts[prof] > 0:
print("%s:%d" % (prof, new_counts[prof]))
any_new = True
if not any_new:
print("OK: no new errors")
# Update watermark atomically
tmp = watermark_path + ".tmp"
with open(tmp, "w") as f:
json.dump({"counts": current}, f, indent=2)
os.replace(tmp, watermark_path)
PYEOF