# Silent Op Recovery

> When a long-running command goes silent, checks interim progress first, then kills by recorded PID and restarts with a visible-output reporter plus a bounded tail. Use when install, build, test, deploy, or fetch stops producing output longer than expected and hang vs slow progress is unclear.

- Skill: `jcdavis131/silent-op-recovery` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add jcdavis131/silent-op-recovery`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jcdavis131/silent-op-recovery/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: jcdavis131 (https://skillmd.com/u/jcdavis131)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/jcdavis131/silent-op-recovery

---


# Silent Op Recovery

A long op with no progress signal is a black box. You can't tell if it's working, hung, or waiting on input. Waiting blindly risks a 10-minute hang. The recovery is: kill by PID, restart with visible output.

## The move

1. **Recognize silence.** The op has produced no output for longer than you'd expect for its type (an install silent for 90s+; a build silent for 2m+; a test silent for 30s+). Silence ≠ progress.
2. **Check interim progress directly (lighter first move).** Before killing, poll the op's interim state:
   - List the directory it's writing to (`Get-ChildItem <build-dir>` / `ls <build-dir>`) — are files appearing / growing?
   - Read a partial output file or log it's writing.
   - Query a status endpoint if the op exposes one.
   The background notification fires on *exit*; "is it making progress?" is a *now* question. If interim state shows progress, keep waiting. If interim state is frozen, escalate to kill-and-restart.
3. **Kill by PID, not by name.** Use the PID you recorded at launch:
   - PowerShell: `Stop-Process -Id <pid> -Force -ErrorAction SilentlyContinue`
   - POSIX: `kill -9 <pid>`
   Never use a name-based kill (`Stop-Process -Name node -Force`, `pkill node`) — it nukes unrelated processes.
4. **Restart with a visible-output reporter.** Re-run the same command with a flag that emits progress:
   - pnpm: `--reporter=append-only`
   - npm: `--loglevel=info` or `--foreground-scripts`
   - pip/uv: `-v` or `--verbose`
   - pytest: remove `-q`, or add `--progress`
5. **Tail the output.** `2>&1 | Select-Object -Last 15` (PowerShell) or `2>&1 | tail -n 15` (POSIX) — visible but bounded.
6. **Compare.** If the restart progresses, the original was hung. If the restart also goes silent, the op is genuinely slow or blocked on something external — diagnose that, don't keep killing.

## Track PIDs at launch (the enabler)

You can only kill by PID if you recorded it. When you launch a long-running op, capture its PID:

- PowerShell background job: `$j = Start-Process pnpm -ArgumentList "install" -PassThru; $j.Id`
- POSIX: `pnpm install & echo $!`

Record the PID in your board or a triage note so it's findable when silence hits.

## When NOT to kill-and-restart

- The op is genuinely slow but progressing (you saw output recently). Wait.
- The op is a deploy or migration — killing mid-flight can leave damaged state. Assess before killing (see `background-failure-triage`).
- The op is interactive and waiting on your input — killing loses the input; answer the prompt instead.

## Anti-patterns

- **Waiting blindly.** A silent 10-minute install is not "being patient"; it's gambling.
- **Name-based kill.** `pkill node` ends your dev server, your background build, and the unrelated worker.
- **Restart without a reporter.** Re-running the same silent command reproduces the same silence.
- **No tail.** Visible output without a tail floods context; the tail keeps the signal bounded.
- **Kill loop.** After one silent restart, diagnose root cause.

## Extended patterns

Capture exit + tail idiom, error-grep variant: [reference.md](reference.md)

## Pair with

- `cost-transparency` — the elapsed-time signal is what tells you silence has gone on too long.
- `background-failure-triage` — a killed-and-restarted op that then fails is triaged at the next boundary.
- `fill-the-wait` — while the restarted op runs, fill the wait with an independent in-lane task.

