# Upgrade Guide — vNEXT

<!-- assembled-by: assemble-next-md -->
<!-- bump: patch -->

## What Changed

Robustness Net #1: a Slack subsystem error can no longer crash the whole agent
process. Previously, a Slack WebSocket send that happened during a reconnect (the
socket not fully OPEN) threw synchronously inside an un-awaited event listener,
which escaped as an uncaughtException or unhandledRejection and could take the
entire server down — the root cause of the 2026-06-14 outage.

- Every Slack socket send now goes through one funnel (`_safeSend`) that checks the
  socket is OPEN, wraps the send so a throw can never escape, and (for the liveness
  probe only) reconnects on failure — guarded so it can never tear down a freshly
  replaced socket.
- The four send sites are unified: `queueOutbound` now enqueues instead of dropping
  on a lost race, the acknowledgement send no longer reconnects per-message, the
  queue drain retains unsent messages on failure, and the heartbeat probe self-heals.
- A process-level `unhandledRejection` handler is added (the async Slack message
  path surfaces failures as rejections, which the existing handler did not catch),
  sharing the exact same narrow, audited allowlist and fail-toward-crash default as
  the existing uncaughtException handler via one shared decision function.
- The allowlist gains one tightly-anchored "WebSocket is not open" entry so the
  Node 22+ built-in WebSocket message form is recognized — anchored so it cannot
  swallow look-alike fatal errors.

This is defense-in-depth behind the existing in-process respawn (net #2) and the OS
watchdog (net #3). No behavior change on the happy path.

## What to Tell Your User

A short Slack network hiccup can no longer crash your agent. Before this, if Slack's
connection dropped at exactly the wrong moment, the whole agent could go down for a
moment and restart. Now the agent quietly reconnects to Slack and keeps everything
else running — your other chats, scheduled jobs, and memory are unaffected. There is
nothing to turn on and nothing to configure; on a normal day you will not notice any
difference. You only benefit on a bad day, when a transient Slack glitch that used to
ripple outward now stays contained to Slack and heals itself.

## Summary of New Capabilities

- Slack socket errors are contained at the subsystem boundary and can no longer take
  the whole agent process down.
- The agent now also recovers from a wider class of unexpected internal errors
  (unhandled promise rejections) the same careful way it already handled others —
  logging and continuing for a tiny set of known-harmless cases, and restarting
  cleanly for anything genuinely unknown.

## Evidence

- Root cause grounded in code (read 2026-06-14): the unguarded sends were
  `queueOutbound` and the queue drain; the acknowledgement and heartbeat sends were
  already guarded. All four are now funneled.
- Tests across all three tiers, all green: unit (`tests/unit/slack-safesend.test.ts`,
  `tests/unit/process-level-error-handler.test.ts`, plus updated socket reconnect /
  heartbeat / sqlite-close tests), integration
  (`tests/integration/slack-adapter-boundary.test.ts`), and an E2E "feature is alive"
  process-survival test (`tests/e2e/slack-containment-process-survival.test.ts`) that
  spawns a real child process, emits a contained error (both uncaughtException and
  unhandledRejection forms) and asserts the process stays alive, then emits an unknown
  error and asserts it still exits 1.
- A wiring-integrity ratchet asserts every raw socket send lives only inside
  `_safeSend`, so a future un-funneled callsite fails the test.
- Converged spec + report: `docs/specs/SLACK-SUBSYSTEM-ERROR-CONTAINMENT-SPEC.md`,
  `docs/specs/reports/slack-subsystem-error-containment-convergence.md`.
- Side-effects review (second-pass concur): `upgrades/side-effects/slack-subsystem-error-containment.md`.
