Skip to content

fix(history): skip a run-index row torn inside a multibyte character - #8681

Open
Duang777 wants to merge 1 commit into
loopx-project:mainfrom
Duang777:codex/run-index-torn-multibyte-row-20261113
Open

Duang777 wants to merge 1 commit into
loopx-project:mainfrom
Duang777:codex/run-index-torn-multibyte-row-20261113

Conversation

@Duang777

Copy link
Copy Markdown
Collaborator

Goal And Delivered Outcome

  • Outcome basis / optional anchor: self-found core defect. No public issue; the request in this PR is the basis.
  • Goal/source and gap: load_index_snapshot in loopx/history.py decoded each run-index line with a strict bytes.decode("utf-8"), but the index is written with ensure_ascii=False, so an interrupted append can end inside a multibyte character. The append helpers already repair the missing newline after a torn tail, so later rows keep landing behind the damaged one.
  • Observable before → after, with the validation row that proves it: One such row raised UnicodeDecodeError from every reader of that index: history and status collection, the Todo and refresh-state readers and the readback in write_compact_runtime_projection. After: the row is counted in raw_count / invalid_count like an unparseable JSON row and the intact rows are returned; the digest still covers the original bytes. Rows: regression_parity, unit.
  • Issue/task and intended base: none. Base is main at 7bb1b01.

Author Declaration

  • Written by: agent, Claude Sonnet 5.5, for @Duang777

Implemented against

  • Specification and revision: no written specification; the request in this PR is the basis.
  • Criteria:
Criterion (spec clause) Disposition Symbol / path Test or command
A damaged row must not take down every reader of the index implemented load_index_snapshot in loopx/history.py tests/control_plane/test_run_index_jsonl_framing.py::test_history_read_skips_a_row_torn_inside_a_multibyte_character
  • Self-check before submission: reproduced the defect on the unfixed code, then confirmed the new test fails before and passes after. Did not run the full suite or CI-equivalent gates locally. Also tried the same tolerance in slot_accounting._load_goal_run_index_records and dropped it: the real settlement readback already fails closed there.

Scope And Continuation

  • Completed scope and remaining work: complete within this scope. A torn row is skipped and counted, not repaired.
  • Slice boundary / successor: N/A, one independent defect.

Validation

  • Tested revision: e2202a3eef43004fc8aeb2cf06bdfe5d61c5f77f for the diff; local runs used a working tree whose touched files were blob-identical to main 7bb1b01 before the change (the touched history.py hunk was re-applied to the current main file; the replaced line is unchanged upstream).
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
regression_parity passed The new test fails on the unfixed code with UnicodeDecodeError and passes with the fix; write_compact_runtime_projection also stops crashing.
unit passed test_run_index_jsonl_framing.py 18/18; ruff check clean on both touched files.
unit failed The history and quota-settlement test files showed 14 failures both with and without this change on the stale local tree (missing loopx.chat_event_cache); not re-run on main.
  • Coverage and gaps: The 14 local failures are an artifact of the local tree and need confirming against CI on this head.

Frontend / Visual Evidence

  • UI impact: none

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: Core control-plane hardening.

Shared-authority RFC fixture impact

N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • Existing English/Chinese RFC pairs (including ledger entries) both have a diff, and the local premerge check passes, or no paired RFC changed. No RFC changed.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

🤖 Generated with Claude Code

The run index is written with ensure_ascii=False, so an interrupted append
can end in a partial UTF-8 sequence. load_index_snapshot decoded every line
with a strict bytes.decode("utf-8"), so one such row raised UnicodeDecodeError
from every reader of that index: history/status collection, the Todo and
refresh-state readers, and the append-side readback of
write_compact_runtime_projection. The append helpers already repair a missing
row delimiter after a torn tail, so new records kept being written behind the
damaged row while nothing could read the index again.

Count an undecodable row as an invalid row (raw_count and invalid_count), the
same way an unparseable JSON row is already handled, and keep reading the
rest of the index. The digest still covers the original bytes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: duang777 <duangjl007@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant