---
name: od-overload
description: Resource overload incidents — CPU/RAM/disk-IO saturation, runaway processes, fork storms, cache thrash, laptop freeze. Triggers on load, OOM, swap, iowait, 100% CPU, slow desktop, confine/harness scopes eating the box, admission-queue backlog, npm/build installs pinning laptop. Invoke BEFORE killing anything.
---

# Resource overload — incident playbook

audience: AI coding agents first. BLUF: measure → identify owning cgroup → kill scope → route compute off laptop. NEVER kill by process name.

## Covers

**Absorbs:** `cpu-load`, `memory-load`, `disk-io-load`, `runaway-process`, `disk-cache-fatigue`.

**Keywords:** cpu, load, oom, memory, swap, ram, runaway, fork bomb, disk io, iowait, thrash, saturation, throttle, 100%, freeze, slow, cache, concurrency.

**Registry coverage:** 59 of 217 complaint records (2026-08-08). Dominant shape: non-interactive agent/build work on laptop; killing visible agent useless — daemon respawns; multiple `confine-*` / `v2-*` scopes concurrent.

## Doctrine

Measure first. Kill by cgroup scope, NEVER by process name. NEVER bypass admission queue.

## Resource no-kill policy — per resource, owner-ruled 2026-08-15

**CPU is a RATE; memory and pids are STOCKS.** You cannot run out of a rate — contention
only slows work. Killing for CPU load is NEVER necessary.

| Resource | Policy | FORBIDDEN |
|---|---|---|
| CPU | `CPUWeight` only — overload degrades to slowness that yields to the owner | `CPUQuota` on any agent slice. 2026-08-15: quota=150% + weight=1 starved 50 sessions' shells to silent rc=1 death (nr_throttled 2.88M). Quota IS a kill under fleet load |
| Memory | stall-based `ManagedOOMMemoryPressure` (current design) — kill rarely, correctly | `MemoryHigh`/`MemoryMax` on agent.slice — a hard level once froze the box (8.7M reclaim events) |
| PIDs | `pids.max` cap — NEW forks fail, existing work lives; pids-guard kills only a scope holding ≥10% of accounted pids (true forkbomb; `0fe056c2d`) | killing the "largest" scope near the cap — 2026-08-15 it serially killed innocent 2-6-pid scopes incl. full checkouts |

Enforced, not advised: tools.json deny rule `cpuquota-on-agent-slice` (runtime commands);
fleet CM policy lint on slice source files (landing path); fleet drift timer (runtime
`set-property` drift surfaces on the board and re-converges). Throttling a fire:
`systemctl --user set-property --runtime agent.slice CPUWeight=10` + per-pid
`renice 19`/`ionice -c3` — reversible, never kills.

## First steps — stop at first rung that explains load

1. **Measure:** `uptime`; `systemctl --user list-units 'confine-*' 'v2-*' --all`; per-scope CPU/mem (`systemctl status` or cgroup files). No numbers → no kill.
2. **Inventory:** `deckctl agents list` — laptop vs buildbox scopes, owning run/seat id. `local > 0` during heavy work = placement failure.
3. **Classify owner:** harness run, factory seat, npm install, browser e2e on laptop, queue backlog. Each has different fix path.
4. **Contain:** `deckctl agents stop --all` OR `systemctl --user stop <scope>` on the hot scope. NEVER `pkill`, NEVER `killall`.
5. **Route:** headless compute → buildbox. Agent build/test/typecheck commands use
   `local-gate --remote-only --key <key> -- <command>` for automatic
   debian1→debian2→debian3 spill, or
   `run-remote launch --host <debian1|debian2|debian3> --repo "$PWD" --wait -- <command>`
   to pin one box. Pass the command directly: an `env PATH=...` wrapper hides command
   identity and MUST refuse rather than fall back locally. Browser pairs use `e2e-remote`;
   dispatched model work uses `seat-remote`. Read
   `docs/specs/2026-08-06-execution-plane-insulation.md` — laptop is terminal, not compute node.
6. **Repro before fix:** same command that caused saturation; artifact = measured before/after + fix sha or config change.

## Never-touch — applies here

From `modules/workstation/claude/incidents/never-touch.md`:

- NEVER kill by process name — cgroup scope only (`cgroup.kill` / `systemctl stop` on scope).
- NEVER kill owner gnome-terminal sessions.
- NEVER bypass admission queue or prevent-band gates.
- NEVER run fork-bomb/exhaustion tests on laptop or buildboxes without containment.
- NEVER write in shared main checkout — worktree only.

## Placement map — read these paths

`modules/workstation/claude/incidents/placement-map.md`:

- `~/.local/state/overdeck` — runtime state, journals, harness logs.
- `~/.claude` — hooks, `bin/`, cpu-guard, local-gate, admission queue.
- `~/.claude/buildbox-hosts.json` — where offload targets live; read before remote dispatch.
- Deploy clone `~/.local/share/overdeck/deploy` — collector + PATH tools source of truth.

## Resolve — exact CLI (never guess syntax)

```
od-incidents list [--type resource-overload] [--state S]
od-incidents show <id>
od-incidents search <query>
od-incidents resolve <id> --artifact <ref> [--summary <line>]
```

`resolve` REFUSES without `--artifact` (exit 2). Artifact = sha on origin/main, named passing test, or live command + expected output reviewer can rerun. No artifact ⇒ stays open.

## Self-update — MANDATORY

This doc is the fleet's memory. You changed the architecture, wiring, commands, or
doctrine this skill describes — or a live incident just proved a rule here wrong or
missing → EDIT THIS FILE in the same landing as the change. Adopted AND rejected
decisions both go in, with the measured why. An owner reminder to record a lesson is
a failure of this rule, not the trigger for it.
