lightfast-clerk
Lightfast Clerk Skill
Clerk auth primitives for local development. Strict scripts for operations with stable contracts (Clerk Backend API, filesystem). A playbook for the browser-driven sign-in flow, because Lightfast's auth UI changes and a hardcoded script would silently rot.
The split:
- Scripts talk to Clerk's Backend API and the local filesystem. These are stable contracts — a script is the right tool.
- Playbook (
references/sign-in-playbook.md) drives the browser viaagent-browser. The calling agent (Claude, a human, another skill) reads the playbook and executes it. Failures become observations instead of opaqueexit 1— which is exactly what you want when debugging auth.
Decision tree
What do you need?
├── A JWT to call /api/trpc/... -> command/token.sh <profile> [template]
├── Curl a tRPC procedure with auth -> command/curl.sh <profile> <procedure>
├── Inspect a profile's state -> command/status.sh <profile> [--json]
├── A live browser session (cookie persisted) -> drive references/sign-in-playbook.md
├── Sign out -> drive the sign-out section of that playbook
├── Wipe local profile state -> command/reset.sh <profile>
└── Delete the Clerk user entirely -> command/delete-user.sh <profile>
Most common workflow (testing a tRPC procedure):
.agents/skills/lightfast-clerk/command/curl.sh -t lightfast-desktop claude-default account.get
This single call handles user provisioning, token minting, and the curl in one step. No browser needed — pure Clerk Backend API.
Commands
| Command | Purpose | Browser? | Side effects |
|---|---|---|---|
token.sh <profile> [template] |
Mint a JWT (stdout = JWT) | No | Provisions user on cold start (no dir + no meta); refuses if profile dir exists but meta missing |
curl.sh [-t tpl] <profile> <proc> [body] |
Mint + curl convenience | No | Same as token.sh |
status.sh [--json] <profile> |
Report profile state via Clerk Backend API | No | None |
reset.sh <profile> |
Wipe profile dir + meta | No | rm -rf profile |
delete-user.sh <profile> |
Delete Clerk user + reset | No | Clerk user permanently removed |
References
| File | Purpose |
|---|---|
references/sign-in-playbook.md |
Goal-driven recipe for browser sign-in / sign-out via agent-browser. Read + execute from your own prompt — do not shell out to a one-shot script. |
references/safety.md |
Layered guardrails |
references/test-mode.md |
Clerk test-mode primer (+clerk_test@, OTP 424242) |
references/jwt-templates.md |
Template names and claims |
Mental model
A profile = <repo>/.agent-browser/profiles/<name>/ (Playwright user-data-dir)
<name>.meta.jsonsidecar (email,userId,signedInAt).
Profiles are per-repo, gitignored, scoped to one Clerk test user each.
States reported by status.sh
| State | Meaning | Next step |
|---|---|---|
UNKNOWN |
No meta sidecar | token.sh to cold-start, or drive the playbook |
GHOST |
Meta has userId, but Clerk 404s on it (user deleted out-of-band) |
reset.sh then re-provision |
PROVISIONED |
Valid Clerk user, no browser profile dir (token-only use so far) | Drive the playbook if you need a cookie |
SIGNED_IN_LOCAL |
Valid user + profile dir + signedInAt written |
Proceed; cookies are presumed live |
status.sh does not verify the browser cookie is still valid. That would
require a browser probe, which is expensive and almost never what the caller
needs. If you must know, drive the playbook and observe what happens.
Transitions
token.shon cold start (no dir, no meta) →UNKNOWNbecomesPROVISIONED- Driving the sign-in playbook +
meta_write→PROVISIONED(orUNKNOWN) becomesSIGNED_IN_LOCAL reset.sh→ any state becomesUNKNOWNdelete-user.sh→ any state becomesUNKNOWN+ Clerk user gone- Someone deletes the user out-of-band →
SIGNED_IN_LOCAL/PROVISIONEDbecomesGHOSTnext time you check
Key conventions
- Profile name:
[a-zA-Z0-9_-]+. Default profile in examples:claude-default. - Email: derived from
git config user.email. GitHub noreply → just the username. Example:jp@jeevanpillay.com→debug-jp-jeevanpillay-com+clerk_test@lightfast.ai. Override withLIGHTFAST_CLERK_EMAIL=.... - JWT template: pass
lightfast-desktopfor desktop-shape JWTs (1h expiry,org_idclaim). Omit for the default Clerk session token. - Base URL:
http://localhost:3024(mesh origin). Override viaLIGHTFAST_CLERK_URL. Non-localhost URLs requireLIGHTFAST_CLERK_I_KNOW_WHAT_IM_DOING=1.
Safety guardrails
Every script aborts immediately if any of these fire:
- Clerk publishable key in
apps/app/.vercel/.env.development.localis notpk_test_* - Target URL is not localhost AND override flag is unset
- Profile name contains characters outside
[a-zA-Z0-9_-] token.shis called on a profile with a browser dir but no meta (would silently cross-contaminate profiles viaderive_test_email)
Backend operations (clerk-backend.mjs) refuse non-test secret keys. The
sign-in playbook expects callers to pass a +clerk_test@-suffixed email —
that's enforced by Clerk test mode, not by a script.
Prerequisites
- Dev server running:
pnpm dev:app(orpnpm dev:desktop-stackonce Phase 6 lands). Skill targetshttp://localhost:3024(microfrontends mesh). agent-browserCLI installed:which agent-browsershould resolve..vercel/.env.development.localpulled:cd apps/app && vercel env pull.- JWT template in Clerk dashboard (only needed if you want template-shaped tokens):
- Name:
lightfast-desktop - Expiry: 3600s
- Claims:
{ "org_id": "{{org.id}}" }
- Name:
Background — Clerk test mode
The pk_test_ / sk_test_ Clerk keys enable test mode:
- Emails matching
<anything>+clerk_test@<anydomain>skip real delivery - OTP code
424242always verifies in those flows - Backend-created users skip waitlist gating
See references/test-mode.md.
See also
references/sign-in-playbook.md— browser sign-in / sign-out waypointsreferences/safety.md— guardrails in detailreferences/test-mode.md— Clerk test-mode primerreferences/jwt-templates.md— template names + claimslib/common.sh— shared bash helpers (sourced by all scripts; bash-only)lib/clerk-backend.mjs— Backend API wrapper (ensure-user, get-user, delete-user, mint-session-token)