Skip to content

Shrink the Wattsi scratch footprint, always clean it up, and log where builds die - #219

Merged
tobie merged 2 commits into
mainfrom
claude/jolly-feynman-uka09h
Sep 28, 2026
Merged

tobie merged 2 commits into
mainfrom
claude/jolly-feynman-uka09h

Conversation

@tobie

@tobie tobie commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

The instance (on nano, 512 MiB) was OOM-killed while building whatwg/html#12263. The log ends with a bare Killed from the start script and no shutdown lines. Node's own RSS stayed around 110 MB throughout, and the one-minute memory snapshots arrived later and later in the minutes before the kill. The job died after unzipping the head build, while unzipping the merge-base build. The likely culprit is the Wattsi scratch files in the tmp dir counting against the machine's memory: both zips plus both fully unpacked builds were there at once. This still needs confirming on the instance (df -h /tmp). The logging added here should confirm or rule it out next time.

Changes

Smaller footprint (lib/wattsi-client.js)

  • Unpack only multipage-html/ from each build, plus xrefs.json from the head build, instead of the whole zip. Each zip is deleted once it's unpacked. A missing xrefs.json makes unzip fail with a clear error, which is fine since its upload needs it anyway.

Always clean up (lib/controller.js, lib/models/pr.js)

  • handlePullRequest() now calls a new PR.cleanup() in a finally. Before, the per-PR scratch directory was removed only at the end of a successful cacheAll(), so failed, aborted or skipped builds left their files behind. A failed cleanup only logs a warning and never changes the job's result.

Debugging (lib/logger.js, lib/wattsi-client.js)

  • memory() snapshots (per request, every minute, on exit) now also carry the machine's memTotal, memAvailable and shmem from /proc/meminfo, and tmpUsed for the tmp dir's filesystem, where each can be read.
  • Each Wattsi step (fetch head/merge-base, extract head/merge-base, diff, rewrite, fetch html-dfn.js) logs at debug level when it starts and how it ended, with its duration, a memory snapshot and the size of the PR's scratch dir. If the process dies, the last starting line names the step. The diff's modified/added/removed/unchanged counts are logged too.
  • The unpacked-files listing uses readdir instead of spawning ls, only runs when debug logging is on, and no longer goes through a process.nextTick that did nothing.
  • WattsiClient takes an optional logger, for tests.

DEPLOYMENT.md: the flavor is now nano, the Wattsi scratch-space description matches the new behaviour, and it describes what an OOM kill looks like in the log and which fields to read.

Tests

  • WattsiClient.unzip: extracts only the expected members, deletes each zip after extraction, and keeps the zip and rejects when unzip fails.
  • Controller.handlePullRequest cleanup: cleans up after a successful build, a failed build and a skipped update, and a failing cleanup doesn't change the result.
  • WattsiClient.step / logListing: start/done/failed lines with scratch size, and the listing is skipped when debug is off and never fails the build.
  • memory(): the machine fields are present on Linux.
  • npm test: 163 passing. I also ran getFilenames() end to end on local fixture zips with real unzip/diff and the pretty logger. Unused files were left out of the extraction, and each step logged as expected.

Not in this PR

  • The Wattsi build runs in PR.init(), before the controller checks whether the PR needs an update, so skipped whatwg/html events still build. Moving it is planned as a follow-up, after an end-to-end test of the handlePullRequest() pipeline. getPreviewFiles(), cacheAll() and the body view currently have no coverage.

🤖 Generated with Claude Code

https://claude.ai/code/session_017fa64HAQWfNnsexnq3duiT

An instance on a 512 MiB flavor was OOM-killed while unzipping the second
whatwg/html build, with Node's own RSS around 110 MB. The scratch files are
the likely culprit: each Wattsi zip holds far more than we use, and both
zips plus both unpacked trees sat in the tmp dir at once.

- Extract only multipage-html/ (plus xrefs.json from the head build), and
  delete each zip once it is unpacked.
- Clean up the per-PR scratch directory when the job ends, whatever the
  outcome. It used to be removed only at the end of a successful
  cacheAll(), so failed or skipped builds left their files behind.
- Update DEPLOYMENT.md: the app runs on nano, and describes what an OOM
  kill looks like in the log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fa64HAQWfNnsexnq3duiT
The crash on whatwg/html#12263 could only be located by noticing which
`ls` line was missing, and Node's own memory numbers looked healthy while
the machine ran out.

- memory() snapshots now add the machine's MemTotal, MemAvailable and
  Shmem (tmpfs counts there) from /proc/meminfo, and the space used on
  the tmp dir's filesystem, where each can be read.
- Each Wattsi build step (fetch, extract, diff, rewrite, html-dfn.js) logs
  when it starts and how it ended, with its duration, a memory snapshot
  and the size of the PR's scratch dir. The diff's file counts are logged
  too.
- The unpacked-files listing uses readdir instead of spawning `ls`, only
  runs when debug logging is on, and no longer goes through a
  process.nextTick that did nothing.
- WattsiClient takes an optional logger, for tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fa64HAQWfNnsexnq3duiT
@tobie tobie changed the title Shrink the Wattsi scratch footprint and always clean it up Shrink the Wattsi scratch footprint, always clean it up, and log where builds die Sep 28, 2026
@tobie
tobie merged commit a831cb6 into main Sep 28, 2026
1 check passed
@tobie
tobie deleted the claude/jolly-feynman-uka09h branch September 28, 2026 11:53
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.

2 participants