Nine harnesses read files. What actually differs?
two harnesses emit no line numbers at all
an invariant, not a preference
Qwen Code; everyone else starts at one
Nine harnesses were read from source. The thing worth knowing first is that almost none of the differences are the ones people argue about. Line caps and output formatting are visible, discussed, and mostly harmless. The differences that will actually cost you something are invisible until they bite.
The one that loses data
Six of the nine will let an agent overwrite a file that changed after it was read. The agent reads a file, thinks for a while, writes it back, and whatever happened in between is gone — silently, with no error and nothing in the transcript to suggest anything went wrong.
Three implementations guard against it, in three different places: Claude Code requires a prior read before an edit, Hermes compares mtime at write time, and MiMo Code tracks read state per file. That is three separate people arriving at the same problem independently, which is usually a sign the problem is real.
This is listed as an invariant rather than a design choice, which means the comparison table below shows it as pass/fail. That is a judgement, and it is the one place on this page where we are willing to make one: there is no coherent argument for silently discarding a concurrent edit.
The one that is nobody’s fault
Qwen Code’s read_file is 0-indexed. Every other implementation that answers
the question starts at one. Neither is wrong — but a builder moving code between
harnesses has no way to notice until every read is off by exactly one line, and
nothing in either tool’s description mentions it.
This is a design choice, so the table records it and does not score it. Marking it as a failure would be the easy, wrong thing to do, and it is worth being explicit that we are not doing it.
Four ways to number a line
N + TAB, N: , N|, or nothing at all. Interesting, but it changes nobody’s
decision, which is why it sits in the long tail rather than at the top.
There is one consequence worth noting. Two harnesses have written code
specifically to stop the model writing the line-number prefix back into the
source file — Claude Code instructs the model to strip it, Hermes runs a
detector on the write path that rejects content matching N|. Same pothole,
two patches, and neither team has any data on how often a model actually falls
in. That is precisely the kind of question this site exists to answer, and it
has not been answered yet.
Codex does not have a read tool
It reads files by running cat and sed through exec_command. That is a
legitimate design — the shell is already there, and it composes — but it means
raw bytes from a binary file land in the context window with nothing to mark
them as binary, no truncation notice, and no line numbers to refer back to.
It is the only implementation surveyed where reading a file can quietly spend
the context window on bytes the model cannot use.
What is not here
Four harnesses register a read tool that could not be verified from source: OpenHands, OpenClaw, Grok Build and ZCode. They are named below the candidate table rather than shown as empty rows, because a blank cell reads as “does not do this” when the truth is “we have not checked”.
Candidates
13 tools · 9 read from source| Owner | Tool | Offset basechoice | Announces truncationinvariant | Binary not silentinvariant | Staleness checkinvariant | Source |
|---|---|---|---|---|---|---|
| Claude Code | Read | 1-indexedsrc | holdssrc | holdssrc | holdssrc | repo |
| Codex | exec_command | — | — | failssrc | failssrc | repo |
| dsh | read | 1-indexedsrc | ? | holdssrc | failssrc | repo |
| Hermes | read_file | 1-indexedsrc | holdssrc | ? | holdssrc | repo |
| Kimi Code | read | 1-indexedsrc | holdssrc | ? | failssrc | repo |
| MiMo Code | read | 1-indexedsrc | holdssrc | holdssrc | holdssrc | repo |
| opencode | read | 1-indexedsrc | holdssrc | holdssrc | failssrc | repo |
| pi | read | 1-indexedsrc | holdssrc | ? | failssrc | repo |
| Qwen Code | read_file | 0-indexedsrc | holdssrc | ? | failssrc | repo |
Present but not verified. 4 more harnesses register a read tool that has not been read from source: Grok Build read_file, OpenClaw read, OpenHands file_editor, ZCode Read. They are left out of the table below rather than shown as empty rows — a blank cell would claim the behaviour is absent, which is a different thing from not having checked.
Full comparison
every cell links to where it was read| Dimension | Claude CodeRead | Codexexec_command | dshread | Hermesread_file | Kimi Coderead | MiMo Coderead | opencoderead | piread | Qwen Coderead_file |
|---|---|---|---|---|---|---|---|---|---|
| Output shape | |||||||||
| Dedicated read tool | Readsrc | none — cat/sed via exec_commandsrc | readsrc | read_filesrc | readsrc | readsrc | readsrc | readsrc | read_filesrc |
| Line numbering | N + TABsrc | nonesrc | ? | N|src | N + TABsrc | N: src | N: src | nonesrc | ? |
| Output wrapper | plainsrc | raw stdoutsrc | plainsrc | plainsrc | plainsrc | <path>/<type>/<content>src | <path>/<type>/<content>src | plainsrc | plainsrc |
| Limits | |||||||||
| Default line cap | 2000src | nonesrc | 2000src | 2000src | 1000src | 2000src | 2000src | 2000src | ? |
| Byte / char cap | — | nonesrc | ? | 100k chars (not lines)src | 100 KBsrc | 50 KBsrc | 50 KBsrc | 50 KBsrc | ? |
| Per-line cap | 2000 charssrc | nonesrc | ? | — | 2000 charssrc | 2000 charssrc | 2000 charssrc | — | ? |
| Whichever hits first | linessrc | — | ? | charssrc | lines or bytessrc | lines or bytessrc | lines or bytessrc | lines or bytessrc | ? |
| Pagination | |||||||||
| Offset base | 1-indexedsrc | — | 1-indexedsrc | 1-indexedsrc | 1-indexedsrc | 1-indexedsrc | 1-indexedsrc | 1-indexedsrc | 0-indexedsrc |
| Negative offset reads the tail | — | — | — | — | yes (−1..−1000)src | — | — | — | — |
| Announces truncationinvariant | holdssrc | — | ? | holdssrc | holdssrc | holdssrc | holdssrc | holdssrc | holdssrc |
| Continuation hint | on truncationsrc | — | ? | next_offsetsrc | wasTruncated + truncatedLineNumberssrc | (Showing X-Y of N. Use offset=Z)src | (Showing X-Y of N. Use offset=Z)src | [Showing X-Y of N. Use offset=Z]src | (truncated)src |
| Reports total lines | ? | — | ? | ? | ? | yessrc | yes (End of file - total N)src | yessrc | linesShown[]src |
| Out-of-range offset | ? | — | ? | ? | ? | explicit error w/ line countsrc | explicit error w/ line countsrc | ? | ? |
| Non-text | |||||||||
| Binary content never enters context silentlyinvariant | holdssrc | failssrc | holdssrc | ? | ? | holdssrc | holdssrc | ? | ? |
| Binary files | refusesrc | raw bytes into contextsrc | UTF-8 onlysrc | ? | ? | error: Cannot read binary filesrc | error: Cannot read binary filesrc | ? | ? |
| Images | inlinesrc | separate view_imagesrc | separate read_image (auto-downscale)src | separate vision_analyzesrc | separate read-mediasrc | attachment + mime sniffsrc | attachment (jpeg/png/gif/webp)src | attachment, auto-resize 2000²src | vision bridgesrc |
| PDFs | pages param, max 20/reqsrc | — | ? | ? | read-mediasrc | attachmentsrc | attachmentsrc | ? | page ranges + continuationsrc |
| Notebooks | cells + outputssrc | — | ? | ? | ? | (notebook-edit tool)src | ? | ? | ? |
| Giant single line | per-line capsrc | nonesrc | ? | char budget absorbs itsrc | per-line capsrc | per-line capsrc | per-line capsrc | points at bash sed fallbacksrc | ? |
| Errors | |||||||||
| Missing path | errorsrc | shell errorsrc | ? | ? | ? | Did you mean one of these?src | Did you mean one of these?src | ? | ? |
| Path is a directory | errorsrc | listssrc | ? | ? | ? | lists entriessrc | lists entriessrc | ? | ? |
| Empty file | explicit remindersrc | empty stdoutsrc | ? | ? | ? | ? | ? | ? | ? |
| Non-vision model | — | — | requires image-capable modelsrc | ? | ? | ? | ? | explicit note in outputsrc | ? |
| Correctness | |||||||||
| Mixed line endings | ? | rawsrc | ? | ? | renders CR visiblysrc | ? | ? | ? | ? |
| Re-read dedup | — | — | — | (path,offset,limit)→mtime, 'File unchanged since last read'src | — | — | — | — | file read cachesrc |
| Staleness check before writeinvariant | holdssrc | failssrc | failssrc | holdssrc | failssrc | holdssrc | failssrc | failssrc | failssrc |
| Line-number contamination guard | instructs model to strip prefixsrc | — | — | write-side detector rejects N| contentsrc | — | — | — | — | — |
| Multi-file read | — | — | concurrent batchessrc | — | — | — | — | — | read_many_filessrc |
| Side effects | |||||||||
| Warms LSP on read | — | — | — | — | — | yessrc | yessrc | — | — |
| Injects nearby instruction files | — | — | — | — | — | AGENTS.md as system-remindersrc | AGENTS.md as system-remindersrc | compact AGENTS.md/CLAUDE.mdsrc | — |
Rows marked invariant are things every implementation should hold to, so they render as pass/fail. Everything else is a design choice: differing is recorded, never scored. ? means we could not verify it; — means the question does not apply.