---
name: "runtime-guard"
description: "Any task that may run longer than 10 seconds, produce more than one screen of output, or need several rounds to push forward must pass this guard first: put timeouts and on-disk logs on commands, give output a quantified budget, write checkpoint files for progress, and provide stall detection and an escape path. Use when the task involves build/test/install/render/scrape, starting a service or GUI, large files or big logs or big diffs, a session that got slow or tool output that is being truncated, the same error repeating, or a long task that may be interrupted or needs to be handed to another session."
---

> Original skill · commissioned by the user · English edition, 2026-11-12

# Runtime guard (runtime-guard)

**One line: turn "stuck" into "observable, interruptible, resumable".**
There are only four root causes of hangs and stalls — commands with no boundary (never return), output with no budget (blows up the context), state with no persistence (lost, then redone), failures with no stop-loss (the same action retried over and over). This skill closes each one.

## When it applies

- The task is expected to take more than 10 minutes, or involves build, test, install, render, scrape, bulk conversion.
- You need to start a service, a GUI, a daemon, or a command that is interactive by nature.
- You need to read a large file, a large directory, a long log, a big diff, or tens of thousands of query results.
- The session got slow, replies are truncated, 413 Payload Too Large, a tool call has produced no echo for a long time.
- The same error, or the same assumption, has already failed more than twice.
- The task spans turns or sessions and may be interrupted midway.

Not applicable: a single question and answer, reading one small file, running a read-only command that finishes in under a second. The guard has a cost of its own — do not wrap five layers of process around a 3-second job.

## Three hard rules

1. **Every long command has a boundary**: a timeout, a log on disk, and an explicit liveness criterion (port / file / exit code). Missing any one of the three, do not start.
2. **Every large payload is tiered**: measure the size first, then take summaries and key lines; the full text only ever goes to a file on disk, never into the conversation context.
3. **State always lands on disk**: write a checkpoint at every stage boundary; the first thing after a restart is to read the checkpoint, not to explore again.

## The 30 seconds before you start (three commands)

```powershell
python <SKILL_DIR>\scripts\guard.py "<the command you plan to run>"
python <SKILL_DIR>\scripts\budget.py <the file or directory you plan to read>
python <SKILL_DIR>\scripts\checkpoint.py init --goal "<the goal in one line>"
```

If `guard.py` says BLOCK you must rewrite the command; if it says WARN, rewrite it as suggested; if `budget.py` says BLOCK you must switch to `Select-String` / `-TotalCount` / a line range; the checkpoint file defaults to `work\STATE.md` in the current working directory.

## 1. Long commands: always go through pulse

Do not call long commands directly. Run them through the wrapper, turning "waiting" into "a wait with evidence".

```powershell
# Basic: 120-second total timeout, echo the last 40 lines, full log on disk
python ...\pulse.py run --timeout 120 --tail 40 -- npm install

# Idle watchdog: no new output for 60 seconds means hung (made for waiting on stdin / stuck at a key prompt)
python ...\pulse.py run --idle 60 --timeout 600 -- pytest -x

# Service liveness criterion: wait for the port instead of sleeping
python ...\pulse.py wait-port 3000 --timeout 30

# HTTP liveness criterion
python ...\pulse.py probe --url http://127.0.0.1:3000/health --expect 200 --timeout 30
```

Exit codes: `0` success; `124` total timeout; `125` idle timeout (eight times out of ten it is waiting for input); anything else is the child process's exit code. On 124/125, read the tail of the log first, then decide whether it is "slow" or "dead" — **do not rerun immediately**.

Rules:

- Set `--timeout` to the estimated duration ×2; do not guess 30 seconds.
- Anything that "installs an environment, runs the full test suite, renders, downloads" must have `--timeout`; default 120 seconds.
- Log paths go under `work\logs\`, one file per task, so you can compare afterwards.
- Child processes always get `stdin=DEVNULL` (pulse already does this): a script that needs human input must be rewritten to take arguments or environment variables.
- pulse launches through CreateProcess, so it can only run executables; PowerShell built-in commands need a wrapper: `pulse.py run --timeout 60 -- powershell -NoProfile -Command "..."`.

## 2. Long-running and interactive commands: recognise and rewrite

| Symptom | Real cause | Correct form |
| --- | --- | --- |
| The command goes quiet and only reacts to Ctrl+C | It entered a pager (`git log`, `less`) | `git --no-pager log --oneline -20` |
| The dev server is "stuck" | A foreground long-running process that was never going to exit | Start it in the background + redirect the log + probe with `wait-port` |
| The command looks like it is waiting for something | Waiting on stdin (confirmation, password, REPL) | Add `--yes` / `--no-input` / an environment variable |
| Output floods the screen and then nothing responds | Terminal flow control (Ctrl+S) or output buffering | Redirect to a file, only read `tail` |
| A GUI program makes the call never return | The process is attached to a window | Headless mode (`--headless`), or `Start-Process -WindowStyle Hidden` and return immediately |
| A network request hangs forever | No timeout | `curl -m 15`, `Invoke-WebRequest -TimeoutSec 15` |

## 3. Output budget: L0 / L1 / L2

| Level | Form | Cap | Applies to |
| --- | --- | --- | --- |
| L0 summary | Only the conclusion, line numbers, counts, paths | ≤ 20 lines | Default, most situations |
| L1 slice | The matching lines + ±5 lines of context | ≤ 120 lines | Locating a bug, reading a key fragment |
| L2 full text | Written to a file on disk, only the path left in the conversation | No limit | Big diffs, long logs, generated artefacts |

Hard thresholds (cross one and you change level — "let me just take a look first" is not allowed):

- A single tool echo ≤ 8 KB / 200 lines.
- Direct read of a single file ≤ 300 lines; anything larger goes through `budget.py` first, then a line range.
- Search results ≤ 50 entries; more means adding filters, not raising `head`.
- At most 2 images at a time; immediately turn the judgement into text or a file, do not leave screenshots in the context.

The matching way to fetch data:

```powershell
# Full text to a file, the conversation only sees head and tail
npm run build *> work\logs\build.log; Get-Content work\logs\build.log -Tail 40

# Locate first, then read (do not read the whole file)
Select-String -Path src\*.ts -Pattern "timeout" -List

# Large diff: look at the statistics first
git --no-pager diff --stat; git --no-pager diff -- src\a.ts

# Large directory: count, do not list
(Get-ChildItem -Recurse -File | Measure-Object).Count
```

## 4. Three-strikes rule (anti-infinite-loop)

For the same command, or equivalent attempts under the same assumption, **stop after 3 consecutive failures**. Retrying the same command a 4th time is a violation.

After the third failure you must pick one of three, and write the choice into the checkpoint file:

1. **Change level**: move up from "tweaking parameters" to "reading the log / reading the source / reading the data" and find the real failure point.
2. **Shrink the scope**: build a minimal reproduction (single file, single command, smallest input) and cage the problem.
3. **Ask for help**: state exactly what is missing (which file, which credential, which decision) and stop to ask.

Also record the "assumptions already ruled out", so the next round does not walk the same path again.

## 5. Stall detection and escape

| Symptom | Criterion | Action |
| --- | --- | --- |
| A call has produced no echo for 90 seconds | More than 3× the normal duration | Rerun through pulse with `--idle`, read the log tail to see what it is waiting for |
| Output exists but progress does not move | Sample twice 30 seconds apart, identical content | Treat as stalled: kill the process tree → read the log → change approach, do not keep waiting |
| The session got slow, replies are truncated | A single turn's context has clearly ballooned | `budget.py` health check → write a checkpoint → continue in another session |
| Memory/CPU maxed out | Process usage stays above 90% with no output | Identify the child process, `taskkill /F /T /PID` first, then lower concurrency |
| The same error for the 3rd time | Three-strikes count reached | See §4 |
| A tool reports it is waiting for user input | An explicit approval/input request | Put the question to the user directly; do not spin and poll |

Process tree reaping (Windows):

```powershell
Get-NetTCPConnection -LocalPort 3000 -State Listen | Select-Object OwningProcess
taskkill /F /T /PID <PID>
```

## 6. Checkpoint / resume protocol

The checkpoint file is `work\STATE.md`, with five fixed sections: goal / done / next / blockers / key paths.

```powershell
python ...\checkpoint.py update --done "parser refactored, unit tests pass" --next "run the full suite: pulse.py run --timeout 600 -- pytest"
python ...\checkpoint.py show
```

When to write (do not write at other times, or it turns into a diary):

- When each stage completes (exploration done / edits done / tests done / delivery done).
- Before any irreversible or high-risk operation.
- When you notice the context is about to fill up, or you are about to switch sessions.

The resume order is fixed: read `STATE.md` → verify that "done" actually exists (files / commits / services) → continue from the command in "next". **Do not redo the exploration.**

## 7. Concurrency and subagents

- Parallelism only for **read-only, mutually independent** probing (checking logs, checking docs, checking several directories).
- Writes to the same file, the same port, or the same output directory must be serial; concurrent writes are a common source of hangs and corrupted files.
- Every subtask must have a deliverable and a deadline; when it ends, reap its child processes and confirm no leftover listener on the port.
- Aggregate results on the main thread only; subtasks return "conclusion + evidence path", not long excerpts of the original text.

## 8. Wrap-up check (5 items, missing one means not done)

1. All background processes reaped: no leftover `Listen` on the target port.
2. Output is non-empty and parseable (file size > 0, the JSON/image/video opens).
3. Logs and temporary files are all under `work\`, the delivery directory is not polluted.
4. `STATE.md` is updated to "next", or explicitly marked complete.
5. The conversation holds no leftover long logs, whole-file contents, or piles of screenshots.

## 9. Prohibited list

- Using `sleep 10` instead of a liveness probe.
- Running `npm install` / `pytest` / a render / a download without a timeout.
- Reading a whole file, a whole log or a whole diff into the context "just to take a look first".
- Starting something in the background and treating it as successful without re-verifying the port.
- After a failure, retrying the same action verbatim and hoping the environment fixes itself.
- Pasting hundreds of lines from one grep back into the conversation.
- Running a long task without a checkpoint and resuming from conversational memory.

## Companion files

- `scripts/pulse.py`: executor with a total timeout / idle timeout + port and HTTP liveness probes.
- `scripts/guard.py`: command pre-check that catches interactive, long-running, timeout-less and large-output forms.
- `scripts/budget.py`: pre-read health check that estimates size and token cost and offers alternative commands.
- `scripts/checkpoint.py`: checkpoint file read/write.
- `references/playbook.md`: ready-made command templates per scenario; read when needed.
