Notes

We build your project, not install your dependencies

juudd's start_build builds a framework project against a toolchain it pins itself and refuses any dependency outside that list by name. Here is the reasoning.

On juudd, start_build builds a React, Vue, Next.js or Nuxt project from its source, but it never installs what your package.json asks for. It builds against a short list of packages the platform pins, and a dependency outside that list is refused by name, with the whole list in the sentence.

That is the decision. The rest of this post is the customer it was made for, why installing a customer's dependencies was the thing to avoid, and what the build deliberately does not do.

The case: an assistant with nowhere to run a build

A framework project is not a site until something has run its build. Most of the time that something is the customer's own machine, and the finished folder goes up through request_upload.

Some customers have no machine to run it on. Their assistant holds the project's source in the conversation and has nowhere to type npm run build. For them the only honest options were a refusal or a build the platform runs itself.

Why not run the customer's install

The obvious build runs the customer's npm install and then their build. That makes the package list the customer's, which means it is the model's, which means every package an assistant reaches for runs on the platform.

Installing arbitrary dependencies is running arbitrary code. A sandbox with nothing worth taking in it makes that survivable. A build that never runs a package nobody on this side chose makes it unnecessary.

So the toolchain is ours. The repository pins one lockfile per framework: Vite with its React and Vue plugins, Next.js with React, and Nuxt with Vue. Each is installed once per sandbox with install scripts off, and nothing else is ever installed. The versions in a customer's package.json are read for one purpose, which is to find out which framework's toolchain it wants. They are ignored after that, and the sentence says so.

What the build does with your source

The tool takes source, because source is what a tool call can carry. A project's source is a dozen small files, and its build output is far larger. The output never goes back through the assistant. It goes from the sandbox to the store, and the assistant reads a version id.

The command is the platform's too, not your scripts.build: npx vite build into dist, npx next build into out, npx nuxt generate into .output/public. A script that built somewhere else would succeed and leave nothing where the collector looks, and a build that ran and produced no site is a worse refusal than any of the ones at the door.

The refusals happen before anything is started:

  • No package.json at the root, with a pointer to deploy_site for plain HTML and request_upload for a folder already built.
  • A dependency outside the toolchain, named, with the framework's whole list.
  • node_modules or .git among the files.
  • A .env, with the sentence that names request_secret, because a value in one would be stored in the site's public files.
  • Lockfiles are not refused. They are set aside, and the reply names them so nobody goes looking.

What a build is not

A build stores a version and publishes nothing. The output goes back through the same checks a deployed bundle meets, and publish_deploy is the one tool that puts a version live. A build finishes minutes after the call that started it, so it has no request to carry a refusal back to.

Server code is not built. Next.js API routes, server rendering, middleware and Nuxt server/ routes are not in the output folder, and the tool's description says so before a model designs around them. Files under functions/ pass through unbuilt and meet the function gate when the version is stored.

It is also bounded before it costs anything: source of 4 MB as sent, one build at a time per account, at most 3 starts a minute, five minutes to build and six to install, and no more than 2000 files or 20 MB back.

What it took, and what is not measured

A quick build finishes inside the call. start_build waits twenty seconds before it hands back a job to poll with build_status, because a caller told to poll for a build that had already finished has been sent on an errand.

On production, a Vite and Vue probe built in 11.4 seconds on the first build for an account, toolchain install included, and 3.9 seconds on the second. Both came back inside the wait and both were published and served. Those are two samples, not a trend.

Three things are open, and the ADR says so.

  • No build has been run from ChatGPT, which is the customer this was written for. Everything above was measured from Claude Code.
  • The sandbox's network is not narrowed to the package registry. It was meant to be. Shipped that way, the install timed out on every attempt, so the sandbox has the internet and holds only the customer's source and the pinned toolchain. The tool declares this in its own annotations.
  • Whether these refusals read as instructions to a model that is not Claude has not been tested. They work on the model that wrote them, and that is not evidence.

The toolchain is also a promise that ages. Each pinned version is something the platform vouches for and a project may one day need newer, so widening the list is a decision about what the platform is willing to run, not a version bump.

The rule, once

The platform builds your project and does not install your dependencies: the toolchain is pinned by us, a name outside it is refused with the list, and a build stores a version that only publish_deploy can put live. The tools and their refusals are listed in the docs.