This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
A pull request that changes something visible should prove it — a before and an after, rendered inline in the description where a reviewer already is. Easy in a browser. Hard when the author is an unattended agent whose only tool is the gh CLI, with no session cookie to drag a PNG onto. Three clean-looking hosting methods each failed for a different reason; the one that works is slightly grubby, and the honest move was to admit that rather than dress it up.
Any pull request that changes something a user can see should carry before/after screenshots inline in the description — not as links a reviewer has to open, but as images that render where they are already looking. Release PRs (develop → main) shouldn't re-capture anything; they should reuse the screenshots from the feature PRs that fed into the release.
The constraint that makes this interesting: the author is the claude-harness agent. It runs unattended and its only lever on GitHub is the gh CLI. There is no browser, no logged-in session, no cursor to drag a file with. Every hosting option has to survive that.
Inline images in GitHub markdown need a URL the image already lives at. The obvious, tidy ways to produce that URL all fell over:
GitHub's native upload — the drag-and-drop that mints user-attachments CDN links — is the canonical answer. It is a browser-session feature. There is no gh command and no documented API that reproduces it, so an unattended agent simply cannot reach it.
gh gist create looks like a free blob host, but it rejects binary files. Feed it a PNG and it returns binary file not supported. It can host the markdown, not the image the markdown needs. Dead end.
Release assets (gh release upload) actually work — the asset gets a stable CDN URL that renders inline. But it is semantically wrong. Every screenshot would need a release to attach to, so the Releases page fills with tags that aren't releases. That trades one kind of clutter for a worse one: it hollows out a feature that means something. Rejected.
The one method that reliably renders inline from the CLI: commit the PNGs onto the PR's own branch, then embed them with branch-pinned raw.githubusercontent.com URLs:

Because the image is a real file on the branch the PR is built from, it exists the instant the description is rendered — no upload step, no external host, nothing that needs a session. This is not theoretical: it is exactly what a real prior PR, paul-explore #237, did.
The files land at a structured path rather than a bare branch folder: docs/pr-screenshots/<version>/<pr-number>/<feature>/before.png — the package version this PR bumps to, then the PR number, then a short feature name. Grouping by version keeps every screenshot for a release together (a release is one version made of several PRs), and the PR number disambiguates within it. The wrinkle: a PR number only exists once the PR is open, so the screenshots land in a commit after the PR is created — which the push-early workflow already does anyway.
Release PRs get this for free. A develop → main PR carries the same commits that already hold the feature PRs' screenshots, so the release description reuses those images rather than shooting anything new.
The obvious follow-up feels responsible: "delete the screenshots from main at release time so the repo stays clean." It is theater. Once the blobs merge through develop → main, they live in git history forever — the objects are in the packfile whether or not a working-tree copy still points at them.
So deleting the file reclaims no space and undoes no commit. It only adds risk: a live PR description embeds a branch-pinned URL, and removing the file underneath it can break that render. We do not prune. The only honest lever on repo weight is keeping the screenshots small in the first place, so that is the one we pull.
A screenshot should show the product, not the tooling sitting on top of it. Before capture, the harness hides the two dev-mode overlays: the Next.js dev indicator in the bottom-left corner and the React Query devtools button in the bottom-right. Otherwise the "after" image documents the dev chrome instead of the UI change under review.
The tidiest-looking designs here were the ones that didn't work (user-attachments, gists) or that worked by quietly corrupting another feature (release assets). The method we shipped — PNGs on the branch, raw URLs pinned to it — is a little grubby, and the temptation was to bolt a cleanup step onto it to feel better. But that step cleans nothing.
"Best engineering" sometimes means picking the pragmatic method that actually works and being honest about its cost, rather than a prettier design that fails or a ritual that only looks like hygiene.