messaging-queue — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited messaging-queue (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when touching any code that delivers events to an AI agent — PTY stdin injection, MCP Log() notifications, channel pings, overlay toasts, hold buffers, alert hubs, or auto-forward paths. Every "spamming the agent" bug we have shipped traces back to violating one of the rules below.
There is exactly one ordered pipeline from "raw event" to "agent's input". Anything that writes to the agent surface MUST flow through it. Parallel writes are bugs, even when they "work" most of the time — they bypass dedup, activity-deferral, outage-holds, and ordering, and they are the canonical cause of repeated-message spam.
Currently in this project the canonical queue is internal/overlay/alerts.go::AlertScanner (process-output side) plus the daemon-side gate chain in internal/daemon/hub_helpers.go::wireProxyLogger (proxy-side). The browser-error / auto-forward path in cmd/agnt/overlay.go::processAutoForwardEvent is a parallel write that does NOT honor the queue. That is a bug, not an architecture choice.
Any new delivery path must compose these layers in this order. Skipping a layer is a bug; reordering a layer is a bug.
(source, proxyID/scriptID, canonical-message). Existing helper: FingerprintForEntry in internal/daemon/hub_helpers.go. New paths reuse it; do not invent a parallel hashing scheme.DedupeWindow collapses to one. Existing impl: AlertScanner.dedupe map + pruneDedup. No global single-timestamp debounce — o.lastForwardNs in cmd/agnt/overlay.go is the anti-pattern: it makes "any event in last 10s" suppress "any other event", so different errors mask each other and identical errors retrigger forever once the window slides.AlertScanner.batchTimer + pending slice. The pending slice IS the wait-for-ready buffer — see § Layering Hold Buffers below.internal/daemon/hold_buffer.go::HoldBuffer. Must layer on top of the batch buffer (§ below), not run in parallel.HoldBuffer.MatchesJSCascade + DefaultJSCascadePatterns in internal/config/agnt.go. Pattern list MUST cover dev-server reconnect noise (vite: send was called before connect, @vite/client, ViteHotContext; webpack: [HMR] Waiting for update signal).AlertScanner checks ActivityState() == ActivityActive and defers up to maxRetries × retryInterval. Every emit-to-PTY caller must check this — processAutoForwardEvent does not, which is why it can land mid-response.AlertHub.BroadcastLogEntry, fireGatedFanOut in hub_helpers.go. Sinks themselves are dumb — all gating happens upstream.The hold buffer (outage gate) and the batch buffer (AlertScanner.pending) are the same kind of object — a per-fingerprint pending table with a deferred flush. They must compose, not duplicate.
The intended structure:
event → fingerprint → batch buffer (coalesce N ms)
│
▼
activity gate (defer if agent active)
│
▼
hold buffer (defer if proxy in outage,
drop on cascade+recovery)
│
▼
sinksConcretely: HoldBuffer should not own a separate channel + map + goroutine + fingerprint scheme. It should be a predicate and cancel-token layered on the existing pending buffer:
OutageClassifier.SuppressionModeProxy before emitting.ModeFull or ModeDiagnosticOnly-and-error: keep the entry in pending, restart the batch timer with HoldWindowMs, increment retry counter.pending, drop entries whose fingerprint matches a cascade pattern, emit the rest immediately.This collapses two independent goroutines + two fingerprint hashes + two retry counters into one pipeline. It also makes the activity gate and the outage gate compose correctly today they're both "hold and retry" but they don't see each other, so a message can pass the activity gate, get held by the outage gate, and the activity state at emission time is unchecked.
| Anti-pattern | Why it's wrong | Where we did it |
|---|---|---|
| Parallel write to delivery surface | Bypasses every gate | ws_handler.go:246 → NotifyBrowserError → overlay HTTP, parallel to Logger().SetOnLogEntry |
| Global single-timestamp debounce | Different events mask each other; identical events re-fire each window | cmd/agnt/overlay.go::lastForwardNs |
| Per-feature fingerprinting | Same event hashes differently in different gates → dedup breaks across layers | HoldBuffer reinvented fingerprint separate from AlertScanner.fingerprint |
| Sink-side dedup | Each sink dedupes independently → consumers see different streams | None today; do not add |
| Skipping activity gate on "high priority" | "Important" errors interrupt the agent mid-response, derail responses | processAutoForwardEvent |
| Cascade pattern list with only generic tokens | Vite/webpack/turbo HMR errors slip through | DefaultJSCascadePatterns missing vite-specific tokens |
Same logical event → same fingerprint, regardless of which layer produced it. Different events with similar text MUST hash differently. The canonical key:
sha1(source_tag + "\x00" + scope_id + "\x00" + canonical(message))source_tag: browser_js, http_5xx, transport_err, process_output, process_alert, etc.scope_id: proxyID for proxy-scoped events, scriptID for process-scoped, "" for daemon-global.canonical(message): lowercase, collapse whitespace, strip trailing line numbers / column numbers / timestamp prefixes.Existing FingerprintForEntry is the reference implementation. Extend it, do not fork it.
Checklist before merging:
FingerprintForEntry — extend if needed, do not parallel-implement.IdleTimeout.Every gate decision is observable. When debugging "why is X spamming":
debug.Log("alerts", ...)) — gate decision per entry.agnt monitor --types hook,error,diagnostic --format json — what reached the sinks.HoldBuffer.entries length — how many in flight.AlertScanner.pending length — pre-emit batch.OutageClassifier.SuppressionModeProxy — current mode for the proxy.If event count from source > delivery count to sink and the spam continues, the gate is being bypassed — look for a parallel write path.
| Concern | File |
|---|---|
| Canonical pending+dedup+batch+activity-defer | internal/overlay/alerts.go |
| Outage hold buffer (currently parallel; needs layering) | internal/daemon/hold_buffer.go |
| Daemon-side proxy gate chain | internal/daemon/hub_helpers.go::wireProxyLogger |
| Outage classification | internal/daemon/outage_classifier.go |
| Transport-signal tracking | internal/daemon/health_tracker.go |
| Cascade pattern list | internal/config/agnt.go::DefaultJSCascadePatterns |
| Sink interfaces | internal/daemon/alert_hub.go |
| Forbidden parallel write | cmd/agnt/overlay.go::processAutoForwardEvent (do not extend; refactor onto pipeline) |
| Fingerprint helper | internal/daemon/hub_helpers.go::FingerprintForEntry |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.