Someone Dumped ChatGPT Work's 232 Tools and the Browser Skill Hides From Itself
A published inventory of ChatGPT Work lists 232 tool interfaces, 44 skills and 615,000 characters of skill source. Its browser skill deliberately withholds its own instructions, and the reason is arithmetic.
The WJS Desk
Sep 1, 2026 · updated 4 hours ago · 5 min read

Simon Willison published a complete inventory of what a ChatGPT Work session can actually call: 232 tool interfaces, 44 skills, and 615,000 characters of skill source, snapshotted on 31 August 2026 with every SKILL.md reproduced verbatim. It reached 200 points and 53 comments on Hacker News.
The scale is the headline. The interesting part is a question Willison asked in his own thread and did not answer, which happens to have a measurable answer.
What is actually in there
The category breakdown says more about the shape of the product than any announcement has:
| Category | Tools |
|---|---|
| GitHub | 89 |
| Sites | 23 |
| Gmail | 21 |
| Google Calendar | 15 |
| Pets | 11 |
| Library | 10 |
| Zillow | 9 |
| Runtime and files | 8 |
| Sub-agents and coordination | 6 |
| Automations | 5 |
GitHub at 89 tools is more than a third of the total, which tells you where the product thinks the work is. But look at Pets, Zillow, and a category literally called "SimonWillison.net database." This is not a default configuration. It is one person's configured session, plugins and all, which is the caveat to carry into every number here.
The other structural detail: every tool is named mcp__codex_apps__*. ChatGPT Work's own first-party tooling is delivered over the Model Context Protocol, the same open protocol Claude Code and everyone else uses. The plumbing has converged even where the products have not.
The question in the thread
Willison flagged the control-browser skill as the most interesting thing in the set. It tells ChatGPT Work how to launch a Playwright instance through the Node.js REPL, and then, rather than explaining how to drive that browser, it instructs the model to run:
nodeRepl.write(await browser.documentation());
That call returns the actual instructions at runtime. His question:
I'd love to know why it's defined like that, as opposed to including all of the instructions directly in the skill Markdown file.
We think it is context budget, and the arithmetic is checkable
Skill systems on both platforms work the same way: a listing of names and descriptions is always in context so the model knows what exists, and the body loads only when the skill is invoked. That gives you two very different price tags for the same text.
On Claude Code the listing budget is documented at 1% of the model's context window, which is 2,000 characters on a 200K model. We measured our own machine this week and found 228 skill entries totalling 50,606 characters, twenty-five times over. When it overflows, descriptions get dropped starting with the least-invoked skills. Nothing errors.
Now apply that to control-browser. Its description alone runs about 480 characters, roughly a quarter of a 200K listing budget for one skill. The SKILL.md body is 5,734 characters. And the browser documentation it defers to is a full API surface: navigation, clicking, typing, screenshots, page state.
Three tiers, three prices:
| Tier | Loaded | Cost |
|---|---|---|
| Skill description | Always, every session | ~480 chars |
| SKILL.md body | On invocation | 5,734 chars |
| Browser API docs | Only when a browser actually launches | Deferred entirely |
Putting the full API into the markdown would move a large block from tier three to tier two. Every session that reads the skill to decide whether to use a browser would pay for the complete API even when the answer is no, and the skill is explicit that the answer is usually no: it says to use web search first and to never fall back to the browser if search fails.
The runtime call is not indirection for its own sake. It is a third tier of lazy loading, below the two the skill format gives you.
There is a second benefit, which is that browser.documentation() returns whatever the current build supports. Instructions embedded in markdown drift from the implementation. Instructions returned by the implementation cannot.
The cost is real, and one commenter said so
We measured the same problem from the other end this week. A single MCP server we had installed with one line of npx config exposes 372 tools and 335,954 bytes of JSON schema, about 84,000 tokens, roughly 42% of a 200K context window. The official reference servers are one to fifteen tools and one to four thousand tokens, so ours is twenty-five times the size of the filesystem server.
Tool search is what makes that survivable: clients defer the definitions and fetch them on demand. Without it, a config line you pasted from a README eats half your context before you type.
darepublic put the user-facing version of this in the Hacker News thread bluntly, saying some of these work tools "can slow things down and waste plentiful tokens." That is the tension. Every tool and skill added is a capability plus a tax, and the tax is paid on turns where the capability is irrelevant. Deferring the browser docs behind a function call is an engineer choosing to pay the tax only when it buys something.
The line worth reading twice
Buried in the plugin management tool's own description is a behavioural instruction:
Proactively search for plugins when an external app, account, or service would materially help, even if the user did not request a plugin. Search before claiming a service is unavailable or suggesting manual workarounds.
There are two honest readings. The generous one is capability discovery: an assistant that says "I can't do that" when a plugin exists is failing, and the instruction fixes a real failure mode. The uncharitable one is that a model told to proactively surface plugins the user did not ask for is doing distribution work for the plugin marketplace.
Both readings are consistent with the text, which is exactly why publishing it matters. That instruction shapes what users see, it has never appeared in a product announcement, and it is visible now only because someone dumped the inventory.
Why this matters beyond one snapshot
Tool and skill definitions are becoming the real interface layer of these products, and they are almost entirely undocumented. You cannot read a changelog to find out that GitHub gets 89 tools and web search gets one, or that the browser skill routes around itself, or that the assistant is told to look for plugins unprompted. Those are product decisions with user-visible consequences, shipped in files nobody publishes.
What we would like to see is the measurement made routine. Both platforms could report what a session's tools and skills cost in context, the way /doctor estimates a skill listing today. Until then, the state of the art is a developer dumping their own session to a static site, and it should not have to be.
The thing that would change our read: if browser.documentation() turns out to return a small blob rather than a large one, our context-budget explanation is wrong and it is plain indirection. That is one call away for anyone with access, and we would like to know.


