When Docs Lie, Read the Source
Spent about 30 minutes on a bug that turned out to be the documented example being wrong.
The setup: wiring up Cloudflare R2 for audio recordings. User records a Chinese lyric, voice goes to the server, server saves the file to R2, later it can be played back. Standard. Using a Python library to talk to R2.
Deployed. First upload failed with:
ClientResponse.read() takes 1 positional argument but 2 were given
Specific. Names a method (read()), names a thing the method is on (ClientResponse), says I passed one argument too many.
I looked at my code. I wasn't calling read() with any arguments. I wasn't calling read() at all. I was using a completely different method — a pattern I'd copied directly from the library's documentation.
The documented example was broken
Documentation said (paraphrasing):
async with (await client.get_object(...))["Body"] as stream:
async for chunk in stream.iter_chunks():
process(chunk)
The pattern is elegant: open the response body with async with, read in chunks, close automatically. I used it. It didn't work.
The version of the library I was using had a bug where async with response["Body"] returned a different kind of object than the rest of the code expected — a raw HTTP response whose read() method took zero arguments, while the library's internal iteration code called it with one argument.
The error mentioned read() and ClientResponse — both internal details of the library, not things I'd written. My code matched the documented example. The example was broken. The error was pointing at the library's internals, not at me.
What didn't work
Claude's first instinct, when I pasted the error, was to look at my code. Reasonable — the error sounded like my code. Checked whether I was calling read() somewhere, whether I was passing extra arguments, whether I was mixing library versions. Nothing matched.
Searched the internet. A couple of forum posts mentioned the error without resolution. Neither was actionable.
About 10 minutes in: "The error is naming internal methods. Let me look at the actual library source to see what's happening."
Reading the source
The library was installed as a Python package, so its source was on my laptop in site-packages. About five minutes of reading revealed:
get_objectreturns a dict. The"Body"key holds a streaming object.async with response["Body"]unwraps the stream and returns the raw underlying HTTP response.- The raw HTTP response's
read()method takes zero arguments. - But when you iterate the unwrapped stream with
async for, the library's internal iteration code callsread(size)with a size argument. That's the error.
The documented pattern was written for one version of the library; actual behavior had drifted from docs. A different pattern — using iter_chunks(size) directly on "Body" without the async with — worked correctly. The source made that obvious; the docs didn't mention it.
Two-line fix.
The general rule
Docs are secondary source. Source code is primary source. When they disagree, source wins.
Cuts against an instinct a lot of people (including me) have: trust the docs, distrust our own reading of messy library internals. The docs are written. They have headings, examples. Surely they reflect reality.
Not always. Libraries evolve faster than their docs, especially popular libraries where maintainers are fixing bugs while documentation lags. Examples become stale when the underlying API changes. Error messages get pointed at the wrong place because the code throwing the error doesn't know the caller's context.
The phrasing that helps
When my code looks right and the error names things I didn't write, the explicit ask:
"Read the source of [library name] and trace through what happens when I call [function]. I want to know if my usage is right, or if the docs are stale."
That phrasing unlocks something. Without it, Claude mostly tries to reason about my code. With it, Claude reads the library. Different modes of debugging, different results.
The lesson applies beyond library internals. Config files that claim to support an option that was removed. Cloud platform docs that describe a feature the product team has quietly deprecated. Error messages that describe the shape of the error rather than the cause.
The thing that's true is whatever is actually running.
Takeaways
- Docs are secondary source. Source code is primary source. When they disagree, source wins.
- When errors name things you didn't write, the bug is probably in the library, not in your code.
- Explicit phrasing unlocks source-reading mode: "Read the source of [library] and trace through what happens when I call [function]."
- The pattern generalizes beyond library internals — config docs, deprecated cloud features, misleading error messages all benefit from "read the running thing, not the written one."
Terms
Cloudflare R2 — Object storage from Cloudflare. A place in the cloud to upload files to. Compatible with AWS S3 but cheaper.
Library — A reusable package of code, written by someone else, that I install and call from my own code. The Python community calls these "packages." Most projects depend on dozens of them.
site-packages — The folder on disk where Python keeps installed library code. Because the source is plain text, you can open and read it directly when something is misbehaving.
async with — Python syntax for working with a resource (a file, a network connection, a database transaction) that needs to be opened and then automatically closed when you're done with it. The "async" version handles cases where opening or closing involves waiting (network round-trips, etc).
async for — Python syntax for iterating over a stream of items that arrive over time, like the chunks of a file being downloaded. Each iteration may have to wait for the next chunk to arrive.
API — Application Programming Interface. Loosely: the set of functions, classes, and arguments a library exposes for callers to use. When library "evolves," what's usually changing is the API surface — names, arguments, return types.
Stack trace / error message — When a program fails, it prints information about what went wrong: the error type, where in the code it happened, and often the chain of function calls that led there. Reading these is the first step in debugging.