fix: decode automation precheck output as one UTF-8 stream - #447
Open
Sma1lboy wants to merge 1 commit into
Open
Conversation
The precheck captured stdout/stderr chunk-by-chunk and decoded each Buffer on its own, so a multi-byte UTF-8 sequence split across two `data` events became a replacement character — a `gh pr list` or `git log` precheck with CJK/emoji came back mojibake in the skip record. Concatenate each contiguous run of Buffers and decode once, and cap the tail by whole code points so the 4000-char limit can't halve a surrogate pair. Adds deterministic unit tests over the decode helper.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Claude finished @Sma1lboy's task in 1m 49s —— View job Reviewing PR #447
|
Sma1lboy
added a commit
that referenced
this pull request
Aug 27, 2026
#447) (#584) * fix(api): reject malformed integer flags instead of silently coercing parseInt stops at the first non-digit, so --id 5abc parsed as 5 and flipped the status of a real, wrong issue while exiting 0, and --count 1e3 quietly spawned one task. A shared parsePositiveInt helper now requires the whole value to be digits (safe integer, > 0) and replaces all three bare parseInt sites: the spec gate, VerbArgs.int, and the --agents count parser. * fix(api): read-output tail keeps its last line over the byte cap boundedTail's byte-budget loop counted the final line first, so a single line larger than 64KB (a minified dump, a base64 blob with no newline) pushed the window start past the end and returned a completely empty tail — a coordinator agent reading the pane got nothing and drew the wrong conclusion. The loop now never trims away the last line, mirroring buildHistoryPage's never-fewer-than-one floor. * fix(daemon): match the dashboard bind host case-insensitively Browsers (and the WHATWG URL parser) lowercase the Origin host they send, but allowedHost was compared with its original case, so binding to an mDNS name like MyMac.local 403'd every LAN browser request. Hostnames are case-insensitive (RFC 4343); the comparison now is too. * fix(daemon): decode automation precheck output as one UTF-8 stream Node splits pipe data events at arbitrary byte offsets, so decoding each chunk on its own turned a multi-byte character straddling the seam into a replacement char — a gh pr list precheck full of CJK/emoji titles came back mojibake. Concatenate the raw bytes and decode once, and cap the tail by whole code points so the slice can't strand a lone surrogate at the cut.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Direction
Bugs / correctness — a self-found defect surfaced while reviewing the daemon's stateful/behavioral modules (the pure-logic helpers reviewed clean).
Problem
runAutomationPrecheckcaptures a precheck command's stdout/stderr as rawdata-event chunks, thentail()decoded eachBufferon its own before joining:Node splits a pipe's
dataevents at arbitrary byte offsets (nosetEncodingis set on the spawn), so a multi-byte UTF-8 sequence can straddle two chunks. Per-chunk.toString()decodes each half independently and emits a�at the seam. A precheck likegh pr listorgit logwhose output carries CJK or emoji therefore came back mojibake in the recorded skip result the user reads to tell a healthy "nothing to do" from a broken command. Separately,text.slice(-MAX_OUTPUT_CHARS)capped by UTF-16 code unit, which can halve a surrogate pair at the cut and strand a lone surrogate.The pass/skip verdict itself is driven by exit code, so the feature's decision is unaffected — this is a fidelity bug in the captured output shown to the user.
Fix
tail()now concatenates each contiguous run ofBuffers and decodes it as one UTF-8 stream (Buffer.concat(...).toString("utf8")), interleaving the pre-decoded error strings we push ourselves in order, and caps the tail by whole code points so the 4000-char limit can't split a surrogate pair. The helper is exported so the decode is unit-testable directly.Verification
bun run lint— clean.bun run typecheck— clean.tail()(constructBuffers split mid-character to reproduce the seam without depending on real spawn chunking): the split-🚀/CJK case now decodes without�, error strings stay ordered, and the over-cap case keeps 4000 whole rockets.KOBE_INCLUDE_SOCKET=1 vitest run test/daemon/automation-precheck.test.ts→ 11 passed (7 existing + 4 new).Follow-ups (deliberately deferred — separate slice)
While verifying, I noticed the test suite is already red on
main, unrelated to this change:test/architecture/package-distribution.test.ts > pending changesets version the canonical packagefails because.changeset/live-sidebar-tab-title.md(added by the most recent merge, fix: keep sidebar tab titles live for unselected tasks #445) targets@sma1lboy/kobeinstead of the canonical@sma1lboy/rove. One-line fix to that changeset's frontmatter, but it belongs to fix: keep sidebar tab titles live for unselected tasks #445's slice — flagging rather than folding it in. (My changeset here correctly targets@sma1lboy/rove.)test/state/layout-migration.test.ts > … after a partial failure and retriesfails only when the suite runs as root (it simulates a write failure viachmod, which root bypasses); it passes under a normal CI user.Neither is caused by this PR.
Generated by Claude Code