Claude Code First, specification version 0.1.0. The plain-language guide
Version 0.1.0. Grade R4/S3.
F404 Text crossing a Windows shell, codec or line-ending boundary changed bytes silently, and the remedies were byte-level checks
| Field | Value |
|---|---|
| Status | active |
| Topic | Environment, permissions, and agent-facing products |
| Grade | R4/S3 |
| Cases | roadkeep, Shio, polyweave, winwright, freewilly |
| Contrary | none |
| Harness facts | none |
Claim
On the Windows machines these projects ran on, text passing through a shell heredoc, a PowerShell pipe, a locale code page or a mixed line-ending tree changed bytes without an error, the damage surfaced later and elsewhere, and each project answered with a mechanism that works on bytes (a declared terminator with a test, a stripped or refused byte-order mark, an explicit decode, a scan for double encoding) rather than with an instruction alone.
Mechanism
Each boundary applies a default the writer does not see: Windows PowerShell 5.1 prepends a byte-order mark when it pipes to a native command, Python's text=True decodes with the locale's code page (cp1252), a heredoc passes source through the shell's quoting and newline handling, and a checkout mixing CRLF and LF makes an edit anchored on one terminator match nothing. The writing process sees nothing wrong, so the defect is found by a parser, a downstream reader, or a count, often after it was committed.
Observations
- roadkeep. "three heredoc edits corrupted a test file's string literals and only the parser noticed" [roadkeep@91754240:docs/CHANGELOG.md#L655],
answered with "Never edit source through a shell heredoc." [roadkeep@91754240:.claude/skills/roadkeep-dev/SKILL.md#L30] Two days later a count: "45 modules end CRLF and 11 end LF, so an edit anchored on one terminator silently matches nothing" [roadkeep@91754240:docs/CHANGELOG.md#L664], answered by a committed .gitattributes and a test, "declare one line terminator and hold it against a mixed file" [roadkeep@05cc307d]. The PowerShell mark broke a stdin path, fixed in "the escape hatch survives the shell that opens the pipe with a mark (RK1023)" [roadkeep@816b0fc7], and the same session filed "a BOM landing in a governed file from --body-file with lint clean" [roadkeep@30d1ebb0].
- shio. "restore LF in diff.mjs after a scripted edit flipped it to CRLF" [shio@237cec023]
on 2026-08-08; a CRLF file hid documentation from an extractor, "the docs extractor reads a CRLF fence, so seven pages stop being invisible (SH674)" [shio@42621b136], raising the extracted blocks "from 55 to 82" [shio@42621b136]; and SH863 declared line endings with a test, "reducing the number of files with incorrect line endings from 308 to 11" [shio@76ffef13e]. A code-page defect corrupted posted content: subprocess.run(text=True) "decodes with the locale encoding, cp1252 here" [shio@c17ed995a] (see F405).
- polyweave. A refusal printed through a runner arrived with a lozenge where an em dash
was: the byte written was correct cp1252, and "What reads the pipe decodes UTF-8" [polyweave@f203b0a]; "the process that wrote the line saw nothing wrong and had nothing to report" [polyweave@f203b0a]. The fix sets the stream encoding in one call rather than editing the strings.
- winwright. "two committed source files carry a double-encoded em-dash, one of them in shipped documentation" [winwright@861b82e:docs/CHANGELOG.md#L503],
answered by a case that walks every text file for UTF-8 read as Windows-1252 [winwright@ddb7289], later widened because damage beside a correct character was invisible to it [winwright@861b82e:docs/CHANGELOG.md#L513]. Line endings were normalised "so a real warning is not buried in CRLF noise" [winwright@8156a60].
- freewilly. "stdout and stderr share one buffer, so a journal line decodes half of what wsl.exe said as noise" [freewilly@c1c2eaf:docs/CHANGELOG.md#L57],
fixed by decoding the two streams apart [freewilly@d9bbfc4]. This one is in the product's own reading of a Windows tool, not in the agent's edit path.
Independence
Five origins. Every observation above is a failure recorded in its own project, on its own files, on its own date. polyweave's no-clobber.py also strips the PowerShell mark [polyweave@6d1c136:.claude/hooks/no-clobber.py#L59-L62], but that hook was written in another repository and imported, so it is not counted for polyweave.
Counter-evidence
None found at the pins. The freewilly observation concerns the product's handling of wsl.exe output rather than text the agent wrote, so it supports the boundary claim and not the narrower claim about agent edits; without it the recurrence is still four projects. The byte-level tests (roadkeep's mixed-terminator test, Shio's line-ending test, winwright's double-encoding walk) are not recorded as having failed after they were added.
Grade
R4: an independent failure in each of the five projects. S3: the sources count what was damaged: 45 and 11 modules in roadkeep, 308 files and seven pages in Shio, two files in winwright. No check encoding the claim is recorded as having failed after it was written, so S4 is not reached.