pithy devOne command runs the whole backend: every Worker in apps/, plus each composed capability’s host Worker and any front end, under one supervising process.
What is real
pithy dev is the command, seeding test data is what fills it, and worktree development is how two branches run at once.
More than you would expect.
| Thing | Locally |
|---|---|
| The Worker runtime | Real. The same runtime Cloudflare runs |
| D1 | Real SQLite, under local state, through Miniflare |
| KV | Real |
| R2 | Real |
| Durable Objects | Real. Sessions, queues and presence sockets all work |
| Workflows | Real, in the capability host Workers pithy dev also starts |
| Really sent, by default. See below |
That is the kit’s whole testing argument in one table: a test that mocks D1 proves the mock works.
What is not
Vectorize. Cloudflare ships no local emulation, so a dev search reaches a real remote index — which is why that capability’s bindings are declared remote and why its provision command needs an account even for dev.
Anything that needs a deployed Worker. A Workflow binding to a capability host that has never been provisioned fails on the first request, naming the binding. That is fail-fast validation working, and it is the most common first-run surprise.
Mail is really sent, unless it cannot be
The email host runs with real delivery on by default, so a magic link you trigger from localhost goes out through Cloudflare Email Service with the same DKIM and the same delivery logs as production.
That needs a Cloudflare login and an onboarded sending domain. With neither, pithy dev checks what it cheaply can before spawning anything, falls back to the local simulator, says so in the ready banner with the command that fixes it, and starts the session anyway — logging the sender, recipient and subject, and writing the rendered bodies to disk.
The verdict is said once, where you look. And the preflight is not the guarantee: a failure that only appears at the first send is caught in the host’s own output and the host is re-resolved for its simulator on the spot, so the sends that follow are written to disk rather than lost.
Ports are assigned, never probed
Each Worker’s port is pinned in this checkout’s dev config, out of a block reserved for this branch in a registry held once per machine.
Probing at startup is a race: two pithy dev processes in two worktrees can both observe the same port free and both try to bind it. Pre-assigning removes it by construction.
A port is used only if free on both loopback families — one tool binds IPv6-only and another binds both. If a pinned port is taken, pithy dev reports the conflict and stops rather than drifting: a Worker that quietly moves breaks every sibling that was told its address at creation.
Resolved ports are exported as environment values and the cross-Worker URLs baked in as origin variables, so Workers call each other directly.
Signing in without a magic link every time
pithy seedThen press l at the ready banner. It opens your default browser already signed in as a seeded user.
No session cookie is ever printed, to the terminal or the log. It used to be, as a line to paste into a browser console — and a working session token rendered as text is a working session token at rest in a scrollback, a log and a screenshot.
The route exists only in a dev composition and never under CI, gated on both independently at registration, because it mints an authenticated session with no credential presented.
You opt in per machine by naming a seeded user in a file in the config directory. No file, no session.
When something never comes up
wrangler dev does not exit when a build fails — it prints the error and keeps running. So the ready banner would wait forever while the real error scrolled forty lines up.
So: 90 seconds after the last Worker is spawned, pithy dev names every Worker still missing, and repeats every 30 seconds while it stays true.
It is still waiting, not failed — the first build of a session pays for a cold bundle. And a Worker that never arrives is reported, never killed: one child exiting tears the session down, and stopping four healthy Workers over one typo is a worse trade than a line naming it.
The report names a restart, because a build that fails on its first attempt never rebuilds — fixing the file and waiting is the one thing that cannot work.
Your local variables are generated
Each Worker’s local variables file is written by pithy dev from sources that never leave your machine — your dev secrets, plus machine-local values no registry declares, overridden by repository-level and Worker-level local files.
A file Pithy did not write is never overwritten and never merged. It is named, an override file is offered, and that Worker starts without one rather than with your file replaced underneath it.
Writing is idempotent by content rather than mtime, so a second run writes no bytes and nothing watching it reloads.
Cold start is slow
The first build of a session takes tens of seconds — for a front end, around half a minute before the first request is served.
Nothing is wrong. Subsequent reloads are fast.
Stopping
Ctrl + C tears down the whole subtree. A re-run stops the previous session first, then reaps orphaned processes still holding the ports, so a crashed session cannot block startup.
Check it worked
- Every Worker reports ready, and the banner fires
/healthanswers, or names the binding it is missing- Mail either sends or writes to disk, and the banner says which
- Two worktrees run at once on different ports