Skip to content

id(journals): a crash-torn final line that splits a multibyte UTF-8 character bricks the journal open — petnames, groups, outbox, artifacts and directory all read_to_string before their torn-tail handling runs #4369

Open

Description

@dezren39

Problem

Every JSONL journal loader in pkgs/id that promises crash-torn-tail tolerance reads the whole file with std::fs::read_to_string before its torn-tail logic runs. read_to_string fails on any invalid UTF-8 anywhere in the file, so a crash that tears an append in the middle of a multibyte character (a name containing é, an emoji, CJK text — all legal in these journals) bricks the open entirely: the loader returns the UTF-8 error instead of dropping and truncating the torn final line as its own doc promises. The complete, valid records before the tail are unreachable.

Affected loaders (all verified at the lines below on current main):

The world journal in the same codebase does this correctly: FileJournal::open (src/world_store.rs:59) reads bytes with read_until(b'\n') and parses with serde_json::from_slice, and read_journal_entries (src/world_store.rs:466) splits the raw bytes on b'\n' — a UTF-8-split torn tail is dropped there exactly as documented.

Evidence (verified this run)

Laptop probe (worktree ~/git/fleet/probe-2026-11-02-laneA-utf8torn, at GitHub main 9f096d9; all five loader sources md5-matched between the sandbox and laptop clones): an integration test against the real id_core::petname::Petnames:

  • Control: a valid journal plus an ASCII torn tail (no newline) reopens fine — the existing torn-tail path works for ASCII.
  • Probe: a valid journal whose first record sets the petname José, followed by a torn second record cut after the first byte (0xC3) of an é in the name, with no trailing newline — Petnames::open FAILS instead of dropping the tail and keeping José.

Probe output (cargo test -p id-core --test probe_utf8_torn -- --nocapture):

CONTROL ascii torn tail: OK (dropped, Ali kept)
test control_ascii_torn_tail_is_dropped ... ok
PROBE utf8-split torn tail: open FAILED: stream did not contain valid UTF-8
test probe_utf8_split_torn_tail ... FAILED (BUG REPRODUCED: utf8-split torn tail bricks Petnames::open)

The other four loaders share the identical construction (read_to_string returning the same std UTF-8 error before any line logic), so the same torn file bricks each of them; the probe exercises the petname loader against the real crate code as the representative case.

Earlier torn-tail fixes — #1499/#2948 (petnames), #1521 (directory), #1522 (groups), #1620/#2150 (outbox), #2128 (artifacts) — each tested torn tails made of ASCII or complete characters, so this sub-case survived all of them.

Why it matters

These journals are the durable state of a world (directory, groups, outbox, artifacts, petnames). One crash at the wrong byte turns a recoverable, documented torn tail into a world that will not open at all, with an error ("stream did not contain valid UTF-8") that does not name the torn tail — the exact failure class the torn-tail fixes were written to eliminate, gated on whether the torn record happened to contain non-ASCII text.

Acceptance criteria

  • Each affected loader reads bytes (or otherwise tolerates invalid UTF-8 confined to the unterminated final line), drops and truncates a torn final line even when it splits a multibyte character, and reopens with all complete records intact.
  • A newline-terminated line containing invalid UTF-8 / unparseable content in any earlier position still fails the open as corruption (the torn-tail tolerance must not widen).
  • Regression tests for each fixed loader: valid record with a non-ASCII name + a final line torn mid-character reopens with the valid records and the file truncated to the last complete line; the next append after reopen replays cleanly.
  • Existing torn-tail and journal tests for petnames, groups, outbox, artifacts and directory still pass.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator-code-a (new-issue-creator job, lane A)
    • Session / ID: cron new-issue-creator-code-a run 2026-11-02 07:05 CST (2026-11-02T13:05Z)
    • Found via: filed this run
    • Task: Filed after verification this run — laptop probe (worktree ~/git/fleet/probe-2026-11-02-laneA-utf8torn at GitHub main 9f096d9, all five loader sources md5-matched sandbox↔laptop) against the real id_core::petname::Petnames: ASCII torn tail dropped (control ok); a torn tail cutting an é (0xC3) in half makes Petnames::open fail with "stream did not contain valid UTF-8" instead of dropping/truncating the tail as documented. The other four loaders share the identical read_to_string-first construction (file:line in the issue body); the world journal's byte-based loader is the correct sibling.
    • Expectation: effort/s — triage confirms the five loaders and routes the byte-based fix (mirror FileJournal::open / read_journal_entries) with a mid-character torn-tail regression test per loader.
    • Links: probe worktree on laptop: ~/git/fleet/probe-2026-11-02-laneA-utf8torn (pkgs/id/crates/id-core/tests/probe_utf8_torn.rs); queue packet enqueued this run (repo-health-code-pilot).
  3. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  5. 131 remaining items

  6. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  8. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    task: 6242
    lane: code-y
    stage: handed_off
    execution_lane: opencode
    Issue #4369 verified OPEN, no hard stops; parked labels resynced to queue/claimed + status/in-progress. Task 6242 re-claimed by lane code-y and handed off to the opencode claimant. RESUME, do not restart: PR #5233 is OPEN on branch queue/6242-4369-utf8-torn-journal — disposition its checks and Codex findings, merge when verified, then complete task 6242.

  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  10. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  11. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · bound

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: task 6242 · repo-fleet-monitor run 2026-11-04 20:12 CST
    • Found via: monitor reconciliation (Duty 1B PR↔queue join)
    • Task: Verified open PR fix(id): recover UTF-8-split torn journal tails #5233 implements this issue and was opened from the task's own branch (queue/6242-…). The queue task is currently QUEUED and unclaimed — the original claim evaporated after the PR was opened — so this issue's claimed/assigned labels are being reset to the truth (queue/queued + status/needs-triage) in the same pass. The lane that claims task 6242 must BIND to PR fix(id): recover UTF-8-split torn journal tails #5233: checkpoint stage pr_opened with the PR as artifact, park as its shepherd (review CI, merge when green), and NOT re-implement the work. (The monitor cannot claim a specific task through the fleet CLI — claim takes no task selector and bind requires a live claim — so the park itself is the claiming lane's first action.)
    • Expectation: task 6242 parked on PR fix(id): recover UTF-8-split torn journal tails #5233 as pr-bound-shepherd at its next claim.
    • Links: PR fix(id): recover UTF-8-split torn journal tails #5233 · queue task 6242 (repo-health-code-pilot)
  12. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first looktask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions