Files
PaperClipAI/tests
DottaandClaude 5c9641f141 test(restart-chaos): add repeated restarts (RR1-RR3): a restart inside the previous restart's recovery (#20480)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The restart chaos harness (`tests/restart-chaos/`) tests if agent
tasks survive control-plane restarts.
> - Every existing fault restarts the server once per task.
> - Real incidents on 2026-11-08 and 2026-11-09 were deploy loops. The
server restarted again 5–60 s after it came back, while recovery of
interrupted runs was still in progress.
> - No wave puts a second restart inside the first restart's recovery
window.
> - This pull request adds a fault family, repeated restarts (RR1–RR3).
It does not change product code.
> - The benefit is that waves now test recovery that is itself
interrupted.

## Linked Issues or Issue Description

**What is the problem?**
The chaos harness restarts the server only once per fault. Production
deploy loops restart it again while it recovers, and no wave tests that.

**What is the solution?**
Add three repeated-restart faults. Each one starts as a K1 and then
stops the replacement again after a seeded gap from its health.

**Why is it needed?**
Recovery of interrupted runs can itself be interrupted. A second restart
in that window can leave a lease, a claim or a wake half done.

Refs #20458 (the TM fault family this one follows).

## What Changed

- Add `RR1`, `RR2` and `RR3` to `--faults` (`repeated-restarts.ts`):
- **RR1 double crash**: K1, then one more `SIGKILL` 3–20 s after the
replacement reports healthy.
- **RR2 crash, then graceful**: K1, then one `SIGTERM` (as K2) 3–20 s
after health.
- **RR3 crash loop**: 3–4 `SIGKILL`s in all, 10–30 s after each health.
The last replacement stays up.
- Add `--rr-gap-seconds <n>` or `<min>-<max>` (1–300). It replaces every
RR gap. RR faults do not combine with `--storm-minutes`.
- The gaps are seeded per task (`restartSteps` in `plan.json`, also in
`--matrix` plans). They are drawn only for RR tasks, so the plans of
existing seeds do not change. `--replay` restarts again after the
recorded gaps.
- The supervisor runs the whole sequence as one fault receipt. The
grader therefore sees one fault window, from the first kill to the last
replacement's ready. Crossing, R1's outage allowance, R5, the T-oracle,
task deadlines and the recovery speed budget treat it as one fault. No
check is relaxed.
- Each stop and replacement is recorded with its signal, gap, PID, the
`/api/ready` status just before the stop, and its stopped, restarted and
healthy times. The record goes to
`<wave>/repeated-restarts/<faultId>.json` (also written when the
sequence fails part way), to the receipt (`repeatedRestart` in
`faults.json` and in each failure's `startup-phases.json`), and to the
timeline (`repeated_restart_stop`, `repeated_restart_healthy`).
- Small registry additions: `types.ts`, `config.ts`, `scheduler.ts`,
`journey-matrix.ts`, `main.ts`, `evidence.ts`, `supervisor.ts`, and the
README section next to TM.

## Verification

- `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts`:
33 files, 402 tests pass. The new `repeated-restarts.test.ts` covers
option parsing, seeded planning for each kind, unchanged plans for other
faults, steps in the task schedule, the matrix and replay, the
stop-and-replace loop on a fake clock (gaps measured from health,
`SIGTERM` for RR2, an exit during a gap, shutdown), and one crossing
window for the sequence.
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos` passes.
- `pnpm verify` asks for broader coverage (the README is a global CI
input), so required CI supplies it.
- Live waves run from master after merge, as the merge rule for
test-only harness PRs allows. Their results go to a follow-up comment.

## Risks

- Harness-only change; no product code changes. Waves without RR faults
are unchanged: the plan draws, the fault receipt and the grader paths
are the same.
- During an RR sequence the server stays "not ready" for the harness, so
no other fault fires and the observer does not click Retry until the
last replacement is ready. This is the same rule as for K6.
- A replacement that exits on its own during a gap fails the fault as a
harness error, with the stops so far recorded. The wave then recovers
the server as after any failed fault.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Anthropic Claude, model ID `claude-opus-5-5`, used through Claude Code
with tool use (shell, file edits) and extended thinking.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 01:44:51 -06:00
..