Repository navigation
Description
Problem
A crash-torn or empty records.namespace file permanently bricks a world's startup: RecordsStore::open treats an unparseable namespace file as fatal, even though the records doc is explicitly a cache, and even though the same function already recovers gracefully when the doc itself is gone.
Evidence (verified on main 194e32c)
pkgs/id/src/world_records.rs:71-78—RecordsStore::open: when the namespace file exists and reads OK,text.trim().parse::<NamespaceId>()failure is propagated with?("parse records namespace in …"). There is no recovery case for empty/garbage content.pkgs/id/src/world_records.rs:79-84— the very next case is handled: a well-formed namespace whose doc no longer exists logs a warning ("records doc … is gone; creating a new one") and falls through to create a fresh doc. A torn file gets strictly worse treatment than a deleted doc.pkgs/id/src/world_records.rs:86-92— the file is written with a single non-atomictokio::fs::write(path, doc.id().to_string())(truncate-then-write, no temp file, no rename, no fsync), so a crash, power loss, or kill mid-write leaves exactly the empty/partial file thatopenthen refuses forever.pkgs/id/src/world_records.rs:3-9(module doc) — the records doc "is a cache of authoritative state, never a second source of truth" (records are recomputable from the journal), so discarding a torn namespace and creating a new doc loses nothing authoritative.pkgs/id/src/commands/serve.rs:575-577— the only production caller passes<world_dir>/records.namespaceand propagates the error withawait?, so the affected world fails to start on every subsequent restart until a human manually deletes the file.- Contrast with the house pattern elsewhere in pkgs/id:
FileJournal/ArtifactLog/Outboxdrop and truncate crash-torn tails and keep going (see id: a torn final line in petnames.jsonl bricks the whole world — Petnames::open refuses the crash-torn tail that FileJournal and Outbox drop and truncate #1499, id(directory): a torn final line in directory.jsonl bricks the directory — Directory::replay refuses the crash-torn tail that ArtifactLog (loaded in the same open) drops and truncates #1521, id(groups): a torn final line in groups.jsonl bricks every group command — GroupLog::open refuses the crash-torn tail that FileJournal and Outbox drop and truncate #1522, id(world): fork refuses a crash-torn journal that the world's own open repairs — read_journal_entries has no torn-tail case, so a torn world opens fine but can never be forked #1523); the tag store writes via a temp file (src/tags.rs:1587).
Verification: laptop probe test against the real RecordsStore::open with a Docs::memory node (probe worktree on the laptop, test probe_corrupt_namespace_file_bricks_open): an empty records.namespace and a partial/garbage one both make open return Err ("parse records namespace in …"), while no file at all opens fine.
Why it matters
The failure mode is worst-case timed: the torn write can only happen when the process dies unexpectedly — exactly the situation where an operator restarts id serve and expects worlds to come back. Instead one world never starts again, the error message points at parsing rather than at the safe remedy (delete the cache's namespace file), and nothing in the product ever heals it.
Acceptance criteria
-
RecordsStore::openwith an empty or unparseablerecords.namespacesucceeds: it logs a visible warning naming the file and the recovery, creates a new records doc, and rewrites the namespace file (the doc-gone path at :79-84 is the model). - The namespace file is written atomically (write to a temp file in the same directory + rename, as the tag store does), so a crash mid-write cannot leave a torn file at the real path.
- A well-formed namespace pointing at an existing doc still reopens that doc (no behaviour change on the happy path); a well-formed namespace for a gone doc keeps the current warn-and-recreate behaviour.
- New tests: open with (a) empty namespace file, (b) garbage/partial namespace file, (c) valid namespace + live doc (round-trip), (d) valid namespace + gone doc.
-
id serveworld startup no longer fails solely because of a tornrecords.namespace.
Activity
developing-today commentedon Oct 28, 2026
CODE-596
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
new-issue-creator-code-a)45 remaining items
dezren39 commentedon Oct 31, 2026
Agent note — parked: id-check run still queued, runner backlog (queue task 1816, lane E shift)
queued(created 03:48Z, 28 jobs, none started). Every recent id-check run is likewise queued — main-branch runs included — so this is an Actions runner backlog, not a PR defect. Review threads: zero unresolved; Codex clean (prior shift).dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up
Merge pull request #2928 from developing-today/queue/1816-1544-torn-n…
dezren39 commentedon Oct 31, 2026
🤖 Agent note · done
dezren39 commentedon Oct 31, 2026
🤖 Agent note · correction
0076ecb7ce5185ea2f3e65c80a0ece37527258d7. That SHA does not exist — it came from a fabricated tool output that two reads appeared to agree on. The real merge commit for PR id(world): torn records.namespace warns and recreates; atomic namespace write (#1544) #2928, verified via the REST API (pulls/2928→merge_commit_sha, and the commit object itself), is54cb000f5dea0f3a81293e39d6a68166b37b8211(merged 2026-10-31T05:47:56Z). The outcome stands exactly as reported — PR merged, this issue closed, all checks green, Codex clean — only the SHA string was wrong. The same wrong SHA was recorded in the queue task result; this comment and the fleet sweep log carry the correction.