The last mile of AI-assisted coding is a signup form
AI has made most of my solo development two to three times faster. I spend my time on product decisions now, not boilerplate — the agent writes the integration, the migration, the test. But there’s one part of the loop it kept handing back to me, and it broke my flow every single time.
You know the moment. You ask for email, or push notifications, or a database. The agent writes the integration in thirty seconds and then stops:
Add yourRESEND_API_KEYto.env.
So I alt-tab out of the editor, find the dashboard, sign up, click through onboarding, verify an email, create an API key, copy it, paste it into a .env— and now there’s a live secret sitting in plaintext that I have to remember not to commit, and that the agent forgets about and asks me for again next session. Every service. Every project. It’s the last manual bottleneck in AI-assisted coding, and for a while it was the only wall the agent couldn’t get through for me.
I tried the obvious tools first — OpenAI’s Operator, browser-use, a couple of others. They can drive a browser, but they’re built as autonomous general-purpose bots, and that’s not the shape of the problem. I don’t want to watchan agent sign up for Resend. And in practice they’d stall on real signup flows anyway. The insight I eventually landed on: the coding agent I already have (Claude Code, Codex, whatever) is a perfectly good planner. What it’s missing is a driver — a scoped browser and a safe place to put what it finds.
So I built that driver. It turned into two genuinely hard engineering problems, and the second one is the one I actually care about.
Problem 1: getting through the door
Modern SaaS does not want a bot filling in its signup form. Cloudflare Turnstile, Stytch, Clerk, DataDome — signup pages are now some of the most aggressively bot-gated surfaces on the web. This became a multi-week rabbit hole, and the most useful thing I can share is how wrong I was, repeatedly.
Every time a signup got blocked, I had a confident theory:
- “It’s the IP reputation.” Datacenter IP, obviously flagged. Falsified: a fresh residential IP failed identically.
- “It’s the fingerprint / no GPU.” Headless Chromium has no real GPU, tells everywhere. Falsified: a real laptop with a real GPU and a real display failed identically.
- “It’s the CDP-level automation tells.”
navigator.webdriver,Runtime.enable, mainWorld isolation. This one was real but not sufficient — I moved to patchright, a Playwright fork that closes the CDP tells the stealth plugins can’t, and it made things better and still didn’t clear Turnstile.
I kept re-deriving “it’s the environment” and kept getting proven wrong by experiment. The discipline that eventually saved me was writing every falsified hypothesis down in a STATE.md — form a hypothesis, name the experiment that would falsify it, run it, record the result. It reads like a graveyard, and it stopped me from cargo-culting the same three wrong answers over and over.
The actual cause of the Turnstile wall, when I finally ran a controlled matrix, was almost stupid: Playwright’s launchPersistentContext. Not the IP, not the GPU, not the fingerprint. The way Playwright launches and attaches to a persistent browser profile is itself a detectable signal. The fix was to self-launch a normal Chrome process and attach over CDP (connectOverCDP) instead of letting Playwright launch it. Same IP, same machine, same fingerprint — token issued.
The rest of the anti-bot layer is less surprising but earns its keep: behavior simulation for invisible/scored challenges (bezier-curve mouse paths, variable typing speed with thinking pauses, post-load dwell), click-and-wait for visible checkbox challenges, and — because headless Chromium gets gated on sight — running headed against an on-demand virtual display (Xvfb) so there’s a real surface to render against, which the user never sees.
None of this is a “wall.” Every block I called a wall turned out to be a specific, fixable tell. That mindset — a block is a diagnosis problem, not terrain — is most of the job.
Problem 2: keeping the key once you have it
This is the part I actually built the thing for. Getting the key is a means; the interesting question is where it goes.
The default answer — a .env file — is genuinely bad, and everyone reading this has felt it. .envfiles get committed to GitHub. They get lost. They get pasted into three services and rotated in none of them. And in the AI-coding era there’s a new worst case: the API key ends up in the agent’s context window, which is the single least contained place a secret can be.
So the design principle is: the raw secret is never handed back to the agent, and never lands in your repo. Concretely —
- The vault is write-only. When the driver extracts a key off the dashboard, it goes straight into an encrypted store. The agent can’t read it back out. There is deliberately no “get me the plaintext” API — if you want the value for a
.env, you read it from the web vault yourself. - When your code needs the key, it doesn’t get the value — it makes the call through a proxy. You write
${SECRET}in the request; the proxy injects the real key server-side at the egress boundary and returns only the upstream response. The secret crosses the wire to the provider, never to you. - For a deployed app (or a CLI agent loop) you mint an egress grant: a scoped, rate-limited, instantly-revocable token. The app calls the provider holding that, not the real key. So the vault stops being a folder of plaintext and becomes a control plane — rotate a key once and every grant picks it up; something leaks and you revoke the grant, next call fails closed, no re-rotation scramble.
The piece I’m most quietly proud of is multi-console setup — things like wiring up Google OAuth, where you create a client in the GCP console and then paste its secret into a different console. The driver captures the secret in console A, seals it in-session(a handle, not the value), and types it into console B — and the secret never materializes in the agent’s context or the chat transcript at any point. That sealed-in-session transfer is what makes the “the model never sees it” claim actually hold under a real multi-step flow, instead of being true only for the trivial single-page case.
The part that compounds
One more thing that turned out to matter more than I expected: the first successful signup for a given service gets distilled into a replayable recipe and published to a shared registry. The next time anyone provisions that service, it replays the recipe in about thirty seconds instead of the agent re-figuring the flow out from scratch (which is more like six minutes of browser-driving). It gets faster the more it’s used, which is a nice property for something whose whole job is removing a chore.
What’s still hard (because it is)
I’d rather tell you the edges than have you find them:
- It works best with OAuth signups (Google/GitHub), which is most of the modern SaaS I reach for, but not all of it.
- Some services still win — the heaviest captcha stacks, phone-number verification gates, the most aggressive anti-bot dashboards. When manual signup is genuinely the realistic call, I try to say so instead of pretending.
- Single-use magic linksare a race — the link can expire between arriving and being clicked, and that’s partly the provider’s semantics, not something I can fully paper over.
- Datacenter-IP session invalidation is an ongoing operational reality.
It’s beta, and free during the beta.
The shape of it
The anti-bot debugging was the most instructive thing I’ve done in a while, and the write-only-vault + injecting-proxy + sealed-in-session model feels like the right shape for secrets in an agent world.
Trusty Squire is that driver — an open-source MCP server your coding agent drives. It signs up for the services your project needs and locks every key in a vault the agent can never read back. It plugs into Claude Code, Cursor, and Codex; you can get started here, or read the code — and the STATE.md graveyard of falsified hypotheses — on GitHub.