pithy deploy --env prodOne Worker, one deployment, one origin. Static assets from the edge, API paths from your code, no preflight anywhere in the picture.
What happens under it
pithy deploy is the command, deploying to staging and production is the environment story, and keeping routes in sync is the gate that runs before it.
For a Worker carrying a UI block: build the client, then ship.
The build emits the client bundle and the built Worker, and writes a redirect that points a subsequent plain deploy at that built config — no flag, no path to remember.
The deploy runs with the Worker’s own directory as its working directory. From a workspace root, the underlying tool aborts with a workspace-detection error; the command already runs it in the right place.
The build carries the environment, and this is the one to know
The environment is chosen at build time, because the plugin resolves each capability’s client-safe values for a named environment.
A build without it inlines dev values into a production bundle, silently.
That has a consequence CI hits: the build writes a redirect for a single already-resolved environment, so a dry run with a different environment flag after a dev build reads dev’s bindings and ignores the flag without saying so.
So do the dry run once per environment, with the environment set at build time.
A failed build fails that Worker
And skips its deploy. Shipping a Worker whose assets are stale is worse than not shipping it.
One Worker’s failure does not abort the batch — every Worker is attempted and reported, and the command exits non-zero if any failed.
The one React rule
Two copies of React is an invalid-hook-call error, and the stack blames the component rather than the resolution.
It is the one failure a linked checkout produces on its own, so both files that resolve modules carry the answer.
The build tool resolves a symlinked package from its real path — so a package linked in from elsewhere imports React out of its tree rather than yours. Nothing goes wrong until the first component from that package is mounted, which in a project that owns its screens can be months.
The two files state the same rule differently, and the difference is not style.
The Worker’s config uses the deduplication option, which re-resolves the named packages from the tool’s root — at the Worker that is a directory where React is a dependency.
The project root’s test config uses an explicit alias, because at the root React is not installed at all: the deduplication option there finds nothing, changes nothing, and says nothing — which is worse than omitting it, because it reads as covered.
The alias names no Worker. It takes the first Worker that resolves React and points everything at that copy, so renaming a Worker or adding a second changes nothing.
Whether you have it depends on when each file was written
Nothing you can re-run adds either half to a file you already have. Both are written once and never rewritten.
| Your front end | You have |
|---|---|
| Scaffolded recently | The Worker’s half. Dev and build resolve one React; your component tests still resolve two |
| Older | Neither, and that is the one that throws in the browser |
For the first: copy the resolution block from the starter test config into your own.
For the second: do that copy and add the deduplication option to the Worker’s build config by hand — a top-level key beside the plugins. No command will put it there for you.
What deploy refuses before it builds
An environment with no origin, an origin nothing routes, and the public subdomain left open beside a custom domain.
That third one matters most here, because on that other origin your base URL names the custom host — so the same-origin gate refuses exactly the requests that establish who somebody is. Sign-in fails there and works on the real domain.
And an environment that does not bind what its Workers declare, which is one comparison of the whole table with one remedy.
It proves what it shipped
After shipping, it probes that Worker’s declared domain for the health route and asserts the version answering matches what was just deployed.
Not the URL the tool printed, which under gradual deployments may be a version-scoped preview. And not a liveness check, because the old version answering happily is precisely the failure worth catching.
A mismatch and an unreachable address both fail the command.
Before you deploy
pithy ui sync --check --worker api
pithy migrate --env prod --jsonThe first catches a route the shell is answering instead of your Worker. The second is a separate, gated step — deploy warns when the schema is behind and never migrates.