Give the migration's .tmp orphan a home in the operations runbook - #216
Give the migration's .tmp orphan a home in the operations runbook#2167487 wants to merge 1 commit into
Conversation
`migrate_state_root` stages each entry as `<name>.<hex>.tmp` beside its destination and renames it into place. Cleanup sits in a `finally`, so an error or Ctrl-C clears the staging entry; SIGKILL or power loss between the copy and the rename does not, and leaves one in the operator's state root. That orphan is deliberately visible — an orphan is theirs to delete, and they cannot delete what they cannot see — but visible only helps if there is somewhere to look it up. The only place that said so was the 0.21.0/0.22.0 repair paragraph in CHANGELOG.md. That paragraph is addressed to operators upgrading across a specific pair of releases; it is version-scoped history, not a durable reference. An operator on 0.23+ who finds `usage.jsonl.<hex>.tmp` next to their usage log has no reason to go reading a changelog entry about versions they never ran, and `docs/design/operations.md` did not mention the file at all. The new runbook entry is symptom-shaped like its neighbours and stands on its own: what the file is, when it is left behind, that nothing reads it, and that deleting it is safe — including why nothing sweeps it up later, since "why is this not cleaned up automatically" is the next question an operator asks. No 0.21/0.22 framing, so it does not expire. Descriptive only; no behaviour change. Fixes Jason-Vaughan#198 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for this — and for reading Every factual claim in the entry checks out against the code — the staging format, the The audit turned up one thing worth adding, and it is the only change to your entry: "deleting it Per the Clean Room Reconstruction Standard in Closing this one in favour of #217 — not because anything was wrong with it, but because the |
Closes #198. Clean-room reconstruction of PR #216 by @7487 — both the Macro and Micro filters cleared it, so per CONTRIBUTING.md the idea lands here and the bytes are ours. Their diagnosis is the whole basis of this entry. `migrate_state_root` stages each entry as `<name>.<hex>.tmp` beside its destination and `os.replace`s it into position, so a copy killed halfway never leaves a truncated file at the real path — which matters because the re-run guard is "does the destination exist". Cleanup runs in a `finally`, so an error or Ctrl-C takes the staging file with it; SIGKILL and power loss do not. Nothing reclaims the leftover afterwards, deliberately: a name no other process can touch is also a name no later run can find, and sweeping for the suffix would restore the shared-name collision the unique name exists to prevent. It stays visible because an orphan is the operator's to delete. None of that was written anywhere an operator would look. The only mention was the 0.21.0/0.22.0 repair paragraph in the changelog — version-scoped history, useless to someone arriving on 0.23+ who finds the file and never ran either release. The reconstruction adds one thing the contribution did not have. "Deleting it is safe" is true of an orphan and false of a staging file a migration is using right now, and the two are indistinguishable by filename while every console script migrates on startup. Deleting a live one makes `os.replace` raise and the run print `could not move state from …`; nothing is lost and the next startup finishes the move, but an operator acting on an unqualified "safe" would have caused an alarm the runbook told them could not happen. Verified against the failure arm in router.py rather than inferred: the re-raise reaches the outer handler because `destination.exists()` is false. `docs/design/operations.md` is under docs/design/, so the Design Overview Artifact is republished in the same turn per CLAUDE.md. Its #139 entry described the unique-name fix without ever naming the orphan that fix deliberately creates — an accepted cost that goes unstated reads as an oversight. Docs only, no behaviour change. Suite 805 green, lint clean.
What
Adds a runbook entry to
docs/design/operations.mdfor the<name>.<hex>.tmpstaging file thatmigrate_state_rootcan leave behind, plus aCHANGELOG.mdentry under### Internal.Prose only — no behaviour change, no code touched.
Why
migrate_state_rootstages each entry as<name>.<hex>.tmpbeside its destination and renames itinto place. Cleanup is in a
finally, so an error orCtrl-Cclears the staging entry;SIGKILLor power loss between the copy and the rename does not, and leaves one in the operator's state
root. The docstring is explicit that this orphan is deliberately visible — an orphan is the
operator's to delete, and they cannot delete what they cannot see.
Visible only helps if there is somewhere to look it up, and there was not. The only mention was the
0.21.0/0.22.0 repair paragraph in
CHANGELOG.md, which is addressed to operators upgrading acrossthat specific pair of releases — version-scoped history rather than a durable reference. An
operator on 0.23+ who finds
usage.jsonl.<hex>.tmpnext to their usage log has no reason to read achangelog entry about versions they never ran, and
grep -rn '\.tmp' docs/design/confirmedoperations.mddid not mention the file anywhere.The new entry is symptom-shaped like its neighbours in that section and stands on its own: what the
file is, when it is left behind, that nothing reads it, and that deleting it is safe. It also says
why nothing sweeps it up later — a sweep over the destination directory is the shared-name
collision again, one directory wider — because "why isn't this cleaned up automatically" is the
next question an operator asks, and leaving it unanswered invites someone to close it as a gap.
No 0.21/0.22 framing, so the entry does not expire.
Scope is the two files the issue names. The issue's fourth acceptance box — republishing the Design
Overview Artifact to its existing URL — is yours: it is not a repository file and a fork cannot
reach that URL. Everything the Artifact would be rebuilt from is in this diff.
Test plan
make test— 791 passed, 17 skipped, ruff and mypy clean (they gate the target). Nothing here isexecutable, so that is a no-regression check rather than coverage of the change; the substantive
verification was reading
router.py'smigrate_state_rootandatomic.staging_pathagainst theprose, so that the entry describes what the code actually does:
f"{path.name}.{uuid.uuid4().hex}.tmp"— no leading dot, hence visible;_discard(staged)is in afinally, so only a signal that skips it orphans an entry;Also checked the wording does not contradict the § Concurrency work #196 covers, or the
observability.md§ Migrated-log integrity notice above it in the same section.Fixes #198
🤖 Generated with Claude Code