Skip to content

docs: correct the README claims the Python build made wrong - #5

Merged
rdtiv merged 4 commits into
mainfrom
docs/python-build-audit
Aug 15, 2026
Merged

docs: correct the README claims the Python build made wrong#5
rdtiv merged 4 commits into
mainfrom
docs/python-build-audit

Conversation

@rdtiv

@rdtiv rdtiv commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Mission-end documentation gate for kickoff issue #1 (merged as #2, 309f021..a381064).

The mission's deliverable was itself mostly documentation, and docs/python.md, CLAUDE.md, and the four cross-document navigation headers all came out of it accurate. The one document with claims that no gate covers is README.md — so it got the audit, against the merged code and by running the commands rather than reading about them.

What changed, and what it was authored from

README claim Reality in the code
"20.6 is when --env-file arrived, and every script here uses it" package.json gives usage no --env-file — it reads a CSV and calls nothing. pyweather/__init__.py's own docstring already said so: "Thirteen scripts, twelve copies of --env-file=.envusage is the one exception."
"Run those from the repo root, not from inside pyweather/ — that is where usage.csv lives, and where both languages expect to find it" Backwards for Python, and it buries the better lesson. pyweather/usage.py sets LEDGER = Path(__file__).resolve().parent.parent / "usage.csv" specifically because uv run makes no promise about the working directory; pyweather/__init__.py uses find_dotenv()'s upward walk for the same reason. Verified: uv run weather from inside pyweather/ succeeds and reads the root .env, and LEDGER still resolves to the repo root.
the command-parity gate has "the one documented exception" scripts/check-docs.ts has two kinds — RENAMED_ENTRY_POINT (assistant:streamingassistant-streaming) and NPM_ONLY (typecheck, typecheck:py, verify:docs) — and a third check the README didn't mention: every entry point must resolve to a module that defines the def main it names.
"typecheck:py … and verify:docs aren't mirrored" typecheck isn't mirrored either. Also added the Python helpers: pyweather/ has counterparts to all four src/ helpers, plus __init__.py, which has none.
the cost table Omitted agent, injection, and verify:docs entirely, and nothing anywhere said which commands need WEATHER_API_KEY. The five weather-tool scripts are exactly those importing weather.js / .weather.

Everything else in the README checked out and was left alone: the six gates (the count matches verify:docs's own output — structure, command parity, then compile/ordering/diff/coverage per document), the fifteen usage.csv columns and their five groupings, the four-term cost formula, by model / by session / by caching in both usage reporters, the three model IDs in src/usage.ts and src/bench.ts, the status table, and the two setup documents' links.

Gates

Run in a clean worktree off origin/main, exit codes checked individually:

  • npm run typecheck → 0
  • npm run typecheck:py → 0
  • npm run verify:docs → 0 (structure: 7 Markdown files, command parity: 16 npm scripts and 13 [project.scripts] entries agree, both documents clean)

Also run, to check claims rather than to gate: uv sync --locked, npm run weather, uv run weather (from the root and from inside pyweather/), npm run usage and uv run usage (both with no ledger, and both over one synthetic ledger — byte-identical reports from the same file). No Claude API call was made.

🤖 Generated with Claude Code

Audited every checkable claim in README.md against the code on main, with
the commands run rather than read. Five were wrong or incomplete:

- "every script here uses --env-file" — `usage` does not, and never did;
  it reads a CSV and calls nothing. Narrowed to scripts that need a key.
- "Run those from the repo root ... where both languages expect to find
  usage.csv" — the Python side deliberately does NOT depend on the working
  directory. pyweather/usage.py resolves the ledger from __file__ and
  __init__.py finds .env by walking up, precisely so `uv run` lands on the
  same ledger from anywhere. `uv run weather` from inside pyweather/ works.
- the command-parity gate is described as having one exception; it has two
  kinds (the assistant:streaming rename, and the three npm-only gates), and
  it also checks that each entry point resolves to a real `def main`.
- `typecheck` was missing from the list of npm scripts with no Python
  counterpart, and pyweather/'s own helpers went unmentioned.
- the cost table omitted `agent`, `injection`, and `verify:docs`, and
  nothing said which commands need WEATHER_API_KEY as well.

Gates: typecheck, typecheck:py, verify:docs all exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The merge added an explicit request timeout to both src/weather.ts and
pyweather/weather.py — the one substantive thing Part 7 gained in both
languages. verify:docs kept the listings in sync, but it cannot see that
the prose around them stopped describing the file.

- docs/typescript.md's "Read what you just wrote" still promised "four
  ideas in that file" and enumerated the pre-merge four, skipping the
  most transferable one. fetch having NO default timeout is exactly the
  kind of thing a beginner assumes is handled for them.
- docs/python.md's Part 7 comparison table had no row for it, though the
  defaults genuinely differ in kind: fetch has none, httpx silently has
  5s. That divergence is what the table exists to make visible.

Prose only — no code block touched. typecheck, typecheck:py, verify:docs
all exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rdtiv

rdtiv commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

Follow-up: extended the audit past README.md to CLAUDE.md and the four other docs/ files, and pushed a second commit (57d5ee1).

docs/typescript.md and docs/python.md — Part 7 prose stopped describing Part 7's code. The merge added an explicit request timeout to src/weather.ts:52 (AbortSignal.timeout(10_000)) and pyweather/weather.py:92 (timeout=10.0). verify:docs kept the listings in sync — it cannot see that the prose around them went stale:

  • docs/typescript.md's "Read what you just wrote" section still opened "Four ideas in that file, and all four transfer to every API you'll ever call" and enumerated the pre-merge four. fetch having no default timeout at all is the most transferable of the lot, and it's the thing a beginner is most likely to assume is handled for them.
  • docs/python.md's Part 7 Idea | TypeScript | Python comparison table had no row for it, even though this is a genuine difference in kind rather than spelling: fetch ships no timeout, httpx silently ships 5s. pyweather/weather.py:80-87 says so at length in a comment; the table that exists to surface exactly these divergences didn't mention it.

Prose only — no code block touched, so the doc/code coupling gates are unaffected. All three gates re-run after the edit: npm run typecheck → 0, npm run typecheck:py → 0, npm run verify:docs → 0.

Two findings left deliberately unfixed, for a human to decide:

  1. CLAUDE.md:203-211 still describes scripts/check-docs.ts as "the four gates — compile, ordering, diff, coverage". The merge added two repo-wide gates above those (structure, command parity). The practical cost: nothing in CLAUDE.md warns a future agent that adding an npm script without a matching pyproject.toml entry point fails CI. Not fixed here because changing CLAUDE.md shouldn't ride along in a docs PR.
  2. pyweather/weather.py:3 opens "Four ideas in here, and all four transfer to every API you'll ever call" and lists the same pre-merge four — the mirror of the typescript.md gap fixed in this PR. It's a source docstring rather than documentation (the Python comment stripper drops it, so docs/python.md never reproduces it), and this gate edits documentation only.

Everything else in CLAUDE.md and the four other documents checked out: all four series-navigation headers list document 3 with resolving links, python.md correctly gates itself behind document 2, app.md is a self-declared stub whose references to src/assistant-streaming.ts and the CSV ledger still hold, and CLAUDE.md's Python helper names, ledger contract, and pinned model IDs all match the code.

Comment thread README.md Outdated
The four helpers have counterparts too — `pyweather/text.py`, `config.py`,
`usage.py`, `weather.py` — plus one with no TypeScript equivalent:
`pyweather/__init__.py`, which loads `.env` once for the whole package where
the npm scripts each pass `--env-file`.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] The new helpers sentence says pyweather/__init__.py loads .env once “where the npm scripts each pass --env-file.” That reintroduces the overclaim this PR just corrected on line 74. In package.json, only the twelve lesson scripts that need a key pass --env-file; usage, typecheck, typecheck:py, and verify:docs do not. pyweather/__init__.py already names the usage exception (“Thirteen scripts, twelve copies of --env-file=.env”).

Suggestion: Reuse the wording from line 74: “where the scripts that need a key pass --env-file.

rdtiv and others added 2 commits August 14, 2026 20:39
PR 5 promoted "fetch has no timeout at all" to a Part 7 teaching point. It is
false. undici, the HTTP engine behind Node's fetch, defaults headersTimeout
and bodyTimeout to 300 seconds. Measured on Node v24.14.1 against a server
that accepts the connection and never responds, fetch rejects after 301.0s
with UND_ERR_HEADERS_TIMEOUT.

The claim lived in six places, not the two a first read found:

  docs/typescript.md    the Part 7 prose, and the src/weather.ts listing
  docs/python.md        the comparison-table row, and the weather.py listing
  src/weather.ts        the comment above the fetch call
  pyweather/weather.py  the comment above the httpx.get call

All six now say the same thing: both runtimes ship a default, they differ by
60x (300s against 5s), and they differ in kind as well as size —
AbortSignal.timeout is one deadline for the whole call, where httpx applies
its timeout to each operation separately. That divergence is a better Part 7
lesson than the absolute it replaces, and it is exactly what the Idea |
TypeScript | Python table exists to surface.

Also in this pass:

- docs/python.md still carried the "run every command from the repository
  root" instruction this PR deleted from README.md, 380 lines before the
  LEDGER comment that disproves it. It was the highest-traffic copy.
- The comparison table gained the follow_redirects row that the code comment
  names and the table skipped.
- src/weather.ts and pyweather/weather.py headers said "four ideas" where the
  document now says five. verify:docs strips comments, so no gate sees this.
- README: models moved out of the paid bucket (models.list() spends no tokens
  and logs no row), the --env-file overclaim removed where it had been
  reintroduced 150 lines after being fixed, the typecheck "not mirrored"
  sentence narrowed to what is true, uv's directory independence bounded to
  inside the repo, and the Part 7 summary row updated to match the lesson.

Gates: npm run typecheck 0, npm run typecheck:py 0, npm run verify:docs 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsAeobZPagwRXXXDzqE6bW
README promised that `verify:docs` catches a `src/` file drifting from its
listing in `docs/typescript.md`, and named the exact line it would report.
That is true of code and false of comments: `stripTypeScript` and
`stripPython` remove comments from BOTH sides before the diff gate compares
them, so a teaching comment can drift out of step with its listing — or say
something flatly untrue — with all six gates green.

This is not incidental. It is how the timeout claim corrected in the previous
commit stayed wrong in six places through a passing CI, and in this repo the
comments ARE the teaching. The promise now carries its exception.

Also: "Every call is logged to usage.csv" had the same defect as the cost
table above it — `models` calls the API and logs no row. Narrowed to calls
that spend tokens, matching the fix at line 134.

Gates: npm run typecheck 0, npm run typecheck:py 0, npm run verify:docs 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsAeobZPagwRXXXDzqE6bW
@rdtiv

rdtiv commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

Review pass over this PR, and the fixes for it. Two commits pushed here (9cc9794, b10fdf8); the findings that fell outside this PR's files went to #7.

The one that mattered

This PR promoted "fetch has no timeout at all" to a Part 7 teaching point. It is false. undici, the HTTP engine behind Node's fetch, defaults headersTimeout and bodyTimeout to 300 seconds. Reproduced against a server that accepts the connection and never answers: on Node v24.14.1, fetch rejects after 301.0s with UND_ERR_HEADERS_TIMEOUT — not never.

That matters beyond the wording, because the second commit added the Idea | TypeScript | Python row on the stated grounds that "the defaults genuinely differ in kind." They don't. Both runtimes ship one; they differ by 60x (300s against 5s) — and in kind, but not the kind claimed: AbortSignal.timeout(10_000) is one deadline for the whole call, where httpx applies timeout=10.0 to each operation separately (connect, read, write, pool). Against a server dripping a byte every 9s, the TypeScript build fails at 10s and the Python build runs indefinitely. The row now says that, which is a better Part 7 lesson than the absolute it replaces.

The claim lived in six places, not the two a first read finds:

  • docs/typescript.md — the Part 7 prose, and the src/weather.ts listing
  • docs/python.md — the comparison-table row, and the weather.py listing
  • src/weather.ts — the comment above the fetch call
  • pyweather/weather.py — the comment above httpx.get, as "fetch's 'wait forever, follow anything'"

Also corrected here

  • docs/python.md:151 still carried the "run every command from the repository root" instruction this PR deleted from the README — 380 lines before the LEDGER comment that disproves it. It was the highest-traffic copy of the claim.
  • README.md:224 reintroduced the --env-file overclaim 150 lines after hunk 1 removed it. (Caught independently by your own inline note.)
  • models was billed in the cost table as "a fraction of a cent". src/models.ts imports only the SDK — models.list() spends no tokens and appends no ledger row. Both "every call is logged to usage.csv" sentences narrowed to match.
  • typecheck "isn't mirrored at all" — narrowed to the true fact (typecheck, typecheck:py, verify:docs are the three NPM_ONLY names), since as written it told the reader there was no Python typecheck.
  • uv run "from whichever directory you happened to be standing in" — bounded to inside the repo; uv walks up for pyproject.toml and fails outright outside the tree.
  • Part 7 summary row and the follow_redirects table row, both of which had fallen behind the lesson.
  • src/weather.ts:3 and pyweather/weather.py:3 said "four ideas" where the document now says five. Renumbering also left a stale back-reference at weather.py:14 ("On the fourth point"), now fifth. This closes the second item you parked for a human; the first, CLAUDE.md's four-gates description, is in docs: correct the same two claims where the README audit didn't reach #7.

The learning

README promised that verify:docs catches a src/ file drifting from its listing, and named the exact line it would report. True of code, false of commentsstripTypeScript and stripPython remove comments from both sides before the diff gate compares them. That is precisely how a false claim sat in six places through a passing CI, and in this repo the comments are the teaching. The promise now carries its exception; #7 records the same thing in CLAUDE.md, where agents read it first.

Second learning, cheaper: this PR's premise — "the one document with claims that no gate covers is README.md" — was too narrow. docs/setup-mac.md, pyproject.toml and CLAUDE.md were equally ungated and carried the same two claims, which is all of #7.

Gates

Both commits, exit codes checked individually, in a clean worktree:

  • npm run typecheck → 0
  • npm run typecheck:py → 0
  • npm run verify:docs → 0

No Claude API call was made.

Left undone, deliberately

  • Closing the blind spot rather than documenting it. Making the diff gate compare comments is a scripts/check-docs.ts design change with a real cost — stripping exists so documents needn't reproduce every comment byte-for-byte. That is its own piece of work.
  • .claude/ is not in .gitignore, so worktrees show as untracked in the main checkout. Unrelated to this PR's content.
  • docs/setup-windows.md was checked and needs nothing; docs/app.md remains a self-declared stub.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsAeobZPagwRXXXDzqE6bW

@rdtiv
rdtiv merged commit 9f50b4f into main Aug 15, 2026
2 checks passed
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