For agents
Local dashboard auth
An autonomous agent (Claude Code, gstack /browse, a custom QA bot) often
needs to inspect or interact with /dashboard. The dashboard is Clerk-protected,
so the agent has to authenticate first. This page documents the supported way.
Quickstart — one HTTP call
With the dev server running (npm run dev):
curl -s http://localhost:3000/api/dev/auth | jq -r .urlNavigate to the returned URL in a browser. Clerk creates an authenticated
session for the deterministic test user and redirects to /dashboard.
The full response shape:
{
"url": "http://localhost:3000/clerk/agent-task/...",
"expiresAt": "2026-05-27T18:42:11Z",
"expiresIn": 1800,
"_meta": {
"usage": "Navigate to `url` in a browser to authenticate as the dev test user. You will land on the redirect target.",
"redirectTarget": "/dashboard",
"docs": "http://localhost:3000/docs/agents/local-auth"
}
}Custom redirect:
curl -s 'http://localhost:3000/api/dev/auth?target=/dashboard/sites' | jq -r .urlAlternative — CLI
npm run dev:auth # prints URL on stdout, metadata on stderr
npm run dev:auth -- --open # also opens the URL in your browser
npm run dev:auth -- --json # full JSON on stdout (machine-parseable)
npm run dev:auth -- --target /dashboard/api-keysThe CLI hits Clerk directly — it doesn't need npm run dev to be running
to mint the URL, but you'll need the dev server up to actually use it.
Test user
By default the test user is agent+clerk_test@dropfast.dev. The +clerk_test
local-part is Clerk's convention that suppresses email delivery and bypasses
verification challenges. The user is auto-provisioned on first call.
Per-workspace isolation (parallel Conductor workspaces):
# .env.local in each workspace
TEST_USER_EMAIL=agent+honolulu@dropfast.devThis avoids races on the single-use sign-in token when two workspaces try to authenticate the same user simultaneously.
How it's gated
The route returns 404 unless all three independent gates pass:
CLERK_SECRET_KEYstarts withsk_test_process.env.NODE_ENV !== 'production'- Request host is
localhostor127.0.0.1andVERCEL_ENVis unset or'development'
A 404 on gate failure means the route is invisible in production and
preview deploys. Don't change this without reading
lib/clerk-test-auth.ts carefully — the assertDevOnly function has a
load-bearing comment block.
If you need to test against a preview deploy, point a local Playwright suite at the preview URL — the route doesn't need to be deployed.
Error shapes
When a gate fails, the route returns 404 with no body. When a gate
passes but something else goes wrong, the route returns structured JSON:
{
"error": {
"code": "clerk_secret_missing",
"problem": "Cannot mint local dashboard auth URL.",
"cause": "CLERK_SECRET_KEY is unset in .env.local.",
"fix": "Add a Clerk test secret (sk_test_*) to .env.local and restart npm run dev.",
"docs": "/docs/agents/local-auth"
}
}Every dev-mode error carries code, problem, cause, fix, and docs.
Playwright
Playwright doesn't need this route. The e2e suite uses
@clerk/testing/playwright
directly via tests-e2e/global.setup.ts, which calls clerk.signIn() with
the same test user. Same identity, different transport.
Headless browser caveat (gstack /browse, raw Playwright)
The magic-link URL works in real browsers — Chrome, Safari, Arc, Firefox.
Open it via npm run dev:auth -- --open and you land on /dashboard
authenticated.
In headless Chromium on http://localhost (no HTTPS), the Clerk
handshake sets the __session_* cookie with SameSite=None, which
Chromium then rejects because the connection isn't Secure. The other
handshake cookies (__client_uat, __clerk_db_jwt) set with explicit
Domain=localhost and pass, but without __session the middleware redirects
to /sign-in.
Real browsers don't hit this — Clerk's JS bundle runs to completion and
re-sets the cookie with SameSite=Lax, which works on plain-HTTP localhost.
Workarounds for headless agents (when you can't open a real browser):
- Use the Playwright e2e suite directly — it uses
@clerk/testing/playwright'sclerk.signIn()which injects the session viawindow.Clerkinstead of redirect handshake. The session sticks. - Run
next dev --experimental-https— with HTTPS on localhost, theSameSite=None; Securecookie sets correctly even in headless. - Hand the URL to a real browser via
--open— best for ad-hoc QA.
This is a Clerk dev-instance + HTTP-localhost + headless-Chromium interaction, not a flaw in the route itself.
Edit this page on GitHub