Yuri Korolev
New York
iInfo
Yuri Korolev
New York
09

Headless

Developer Tools2026
Headless — Developer Tools

Context

Almost everything I build is a web page, and the only way to know a web page works is to look at it. When the thing doing the building is an agent rather than me, that means the agent needs a browser. The normal answer to that is Playwright or Puppeteer, and I used them for a while, and they are good at what they were made for.

What they were not made for is the constraint I actually had. An agent has to read everything it runs. Every line a tool prints comes back into a context window that the rest of the job still needs, and a framework written for a person answers the way you would answer a person: a banner on the way in, and a report at the end of everything that went right. Bun's WebSocket already speaks the DevTools Protocol, which means none of the framework is required to drive the browser at all. So I wrote the small version.

Headless is about thirteen hundred lines of TypeScript across six files, with no runtime dependencies. It gives me a CLI for one-off questions about a page, a library to write a throwaway script against when the job has more than one step, and a way to run a lot of browsers at once. It is packaged as a Claude Code plugin so the whole install is one line, and the plugin carries a skill that teaches the agent when to use which.

Details

Role
Engineer
Team
Solo
Timeline
Jul 2026 — present
Built with
TypeScript, Bun, Chrome DevTools Protocol
Deliverables
CLI, scripting library, Claude Code plugin and skill
Status
In daily use, release paused

Tech stack

Runtime
Bun·TypeScript·Native WebSocket
Browser
chrome-headless-shell·Chrome DevTools Protocol
Runtime dependencies
None
Surface
CLI·Scripting library·Claude Code plugin·Skill
Tests
bun test·Bun.serve fixture·GitHub Actions

Final artifacts

Headless — One command, and the one line it answers with
One command, and the one line it answers with
Headless — Its own page at 390 wide, photographed by the thing that checks it
Its own page at 390 wide, photographed by the thing that checks it
Headless — Twelve browsers at once, and what each page cost
Twelve browsers at once, and what each page cost

Key design moments

01

Playwright is written for a person and the reader here is a model

The constraint that shaped every decision in this is that the output is the expensive part. An agent runs a command and then has to read the answer, and a tool that is generous with its output is a tool that costs you twice for the same fact. So the CLI answers with one thing and stops. `eval <url> "document.title"` prints `"Example Domain"` and nothing else. `shot` prints the path it wrote, one line, with no confirmation around it. `console` prints `(no console output)` when there is nothing wrong, rather than a clean bill of health you have to read to the end of to find out it was clean. The install side is the same argument in megabytes. `npx playwright install` pulls a browser per engine and a dependency tree behind it. This pulls one pinned `chrome-headless-shell` into a cache directory, once, and has nothing else in it. That is the whole idea and it is the only genuinely new thing here: a tool designed so that reading its output is cheap is a different tool, not a smaller one.

Headless — Playwright is written for a person and the reader here is a model
Playwright is written for a person and the reader here is a model
02

The loop is the mistake, not the fan-out

Fan-out is the headline, and the first numbers I took for it were flattering because I measured them wrong. One browser launching, loading a page and closing again looked like it cost about the same as twelve doing it at once, which would have been a remarkable thing for it to do. What I had actually measured was a cold first launch against a warm dozen: the first one pays for DNS and the page cache and everything after it does not. Warmed up and run twice, the honest figures on this laptop are about a fifth of a second for one, and between three quarters of a second and a second for twelve at once. Twelve browsers cost roughly three and a half times one browser. What they do not cost is twelve times one, and the same twelve run one after another take about two and a half seconds, so the fan-out is about three times faster than the loop. That is the claim worth making and it is a smaller one than I first wrote down. There are two shapes and the skill makes the agent choose. Many pages inside one browser is the cheapest and shares cookies and storage, which is what you want for several pages of one site. Separate browsers give every job its own process and its own temporary profile, so sessions cannot see each other and one crash does not take the batch with it. What breaks first is not the machine, it is the cleanup. If a worker throws, the runner stops scheduling new work and cannot cancel what is already running, and any browser whose close never fires leaves a Chrome process and a directory in /tmp behind it. Every example in the repository closes in a finally, and that is why.

Headless — The loop is the mistake, not the fan-out
The loop is the mistake, not the fan-out
03

Most of the skill is about when not to use the tool

The plugin ships a skill, and the useful part of it is not the command list. It is a decision procedure. Check the environment before anything and do not install a browser unless you were asked to, because that is a hundred megabyte download. One fact about one page goes to the CLI. Anything with a second step gets a small script instead, and the reason is written out rather than assumed: each CLI call is its own launch and its own fresh profile, so logging in with one command and screenshotting with the next screenshots you logged out. There is a paragraph on reading a blank page which says to check the document request before the console, because a failed or 4xx document is a server problem and a document that loaded fine with an error under it is a client one. There is a caveat about phone screenshots coming back 980 pixels wide on a page with no viewport meta tag, labelled a caveat rather than a bug so nobody goes and fixes it. The detail sits in three reference files so the part that is always loaded stays short, which is the same argument about output applied to the documentation.

Headless — Most of the skill is about when not to use the tool
Most of the skill is about when not to use the tool

Impact

There is no row of figures above because I have not collected anything that would survive being called a benchmark. What I have is a laptop and a stopwatch, and one lesson about using them. On a ten core Mac, warmed up first and run twice against example.com, one browser launching, loading and closing takes about 0.22 seconds, twelve at once take between 0.76 and 0.92, and the same twelve one after another take about 2.5. The warm-up is the part that matters: without it the first launch pays for DNS and the cache, one browser looks like it costs 0.77 seconds, and twelve at once look free. They are not free. They are about three and a half times one browser and about three times cheaper than the loop, which is the useful fact and not the exciting one. The number I would defend is the dependency count, because zero is a fact rather than an impression: nothing is installed at runtime, and the one place the package touches Node is a single npx call to download the browser, once, at setup. The measure I actually care about is not a figure at all. My own global instructions name this as the only browser tool I am allowed to use and forbid installing Playwright or Puppeteer, and that has held for months without me once wanting to route around it.

Reflections

It works and it is not released, and I spent a while letting those be the same sentence. The whole packaged version went in on one day at the end of July, twenty-nine commits, tests and CI and a plugin manifest and a skill with three reference files, and it has not been touched since. The repository is still private. The design doc I wrote for it lists a known follow-up, which is that my own instructions still name the old command and the old path and will go stale the moment this ships. They still name them, because it never shipped. Meanwhile the unpackaged copy I actually run got edited again four days after the packaged one was finished. The reason it stalled is boring and true: I have been moving over to Codex, and the thing I had just spent a day packaging was a Claude Code plugin. What I learned from that is that packaging is a separate project from building, with its own reason to exist, and if that reason moves while you are halfway through it, nothing tells you.

The other thing I would do differently is publish it on the day it worked, before the tests and the skill and the marketplace manifest, and let it be small in public. The install line is one line and it is a good one, and right now it is a line nobody can run. I am also aware that this tool won inside my own setup partly because I put it in a config file and told myself to use it, which is not the same as anyone choosing it over Playwright on the merits. Everything I believe about the output shape being the real constraint is still untested by anybody who is not me. That was the interesting claim, and it is the part still sitting in a private repository.