agentleFS
metalogicametalogica/substrate/skills/deploy/SKILL.md

deploy

Skill0 starsChanged 2 months agoMITView on GitHub

Stage 3 production deploy for substrate projects. Covers public *.vercel.app deploy, custom domain + 5 Clerk DNS CNAMEs + SSL, Clerk prod instance with domain-typo confirmation, prod Convex deployment, env sync via .env.prod, and optional GCP OAuth. Five phase markers (A–E) so you can stop or resume without redoing completed work. Invoke after /substrate:migrate once local sign-in is verified.

  • Reads credentials
  • Commits and pushes

155 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use pm-discovery, qa-phase and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • ww-w-aiSkill

    pm-discovery

    PM Agent Team - Automated product discovery, strategy, and PRD generation. Runs PM analysis workflow to produce comprehensive PRD before PDCA Plan phase.

    Also in 3 projects that use this

  • ww-w-aiSkill

    qa-phase

    Run QA tests at various levels (L1-L5) for features under PDCA management. Integrates with MCP tools to execute structured test plans.

    Also in 3 projects that use this

  • +10 more from ww-w-ai

The file

Show its rules and text

We split this skill into the rules it gives an agent

32 rules. 0 are common, 12 are distinctive, and 20 are very specific to this project.

  • 0

    Common

    Most of the 139 edited versions we read keep these.

  • 12

    Distinctive

    This author's own choices. Worth a look.

  • 20

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    If STAGE_MISMATCH, stop and redirect per the table above.

    line 37
  2. Very specific:

    If likely done, ask: *.vercel.app appears already deployed.

    line 65
  3. Very specific:

    GitHub remote: If no origin, run:

    line 69
  4. Very specific:

    Ask user for slug + visibility with default-escape.

    line 75
  5. Very specific:

    Vercel link: If no .vercel/project.json:

    line 77
  6. Very specific:

    Use printf %s "$VALUE" | vercel env add NAME production (stdin form — piped heredoc is unreliable on some shells):

    line 85
  7. Very specific:

    Verify: Ask user to visit the Vercel-generated URL and sign in via dev Clerk.

    line 104
  8. Very specific:

    Custom domain decision: Ask: Register a custom domain?

    line 138
  9. Very specific:

    If skip, jump to Phase C (user can still have a prod Clerk instance attached to the *.vercel.app URL, but it's uncommon).

    line 140
  10. Very specific:

    Get or buy the domain: Ask: Already own the domain?

    line 142
  11. Very specific:

    Goal: create a production Clerk instance bound to the custom domain, write prod keys to .env.prod.

    line 211
  12. Distinctive:

    Domain-typo confirmation: BEFORE the user creates the Clerk prod instance, echo the domain back and require exact re-entry:

    line 285
  13. Distinctive:

    GCP OAuth (optional, prod-only): If Google sign-in is desired on prod, the user needs their own Google OAuth client (Clerk's shared creds …

    line 309
  14. Distinctive:

    Validate the webhook secret format: before writing anything:

    line 326
  15. Very specific:

    Goal: create prod Convex deployment, sync .env.prod values to Convex prod + Vercel prod (overwriting Phase A's dev-env temps).

    line 375
  16. Distinctive:

    If already set to the dev URL, EDIT it rather than creating a new endpoint.

    line 401
  17. Distinctive:

    MUST verify migration is complete at the stage gate.

    line 515
  18. Distinctive:

    MUST run idempotency check at the start of each phase.

    line 516
  19. Distinctive:

    MUST print each phase's checklist before the gate.

    line 517
  20. Very specific:

    MUST pause at each phase gate for explicit y/n/default approval.

    line 518
  21. Distinctive:

    MUST perform domain-typo confirmation before Clerk prod create (Phase C step 1).

    line 519
  22. Very specific:

    MUST perform tenant-drift detection at Phase C start (diff .env.prod against npx convex env get --prod).

    line 520
  23. Very specific:

    MUST write .env.prod in Phase C, read from it in Phase D.

    line 521
  24. Very specific:

    MUST NOT commit .env.prod — gitignored via .env* rule.

    line 522
  25. Distinctive:

    MUST NOT push secret keys (Clerk Secret Key, webhook secret, Convex deploy key) to the client bundle.

    line 523
  26. Very specific:

    MUST NOT push CLERK_JWT_ISSUER_DOMAIN or CLERK_WEBHOOK_SECRET as Vite env vars — these are Convex-server-only, set via npx convex env set …

    line 524
  27. Distinctive:

    MUST NOT force-push to remote main.

    line 525
  28. Very specific:

    MUST validate the webhook secret format (^whsec_) in Phase C step 9 before any write.

    line 526
  29. Very specific:

    MUST NOT run dev Clerk setup — that belongs to /substrate:migrate.

    line 527
  30. Distinctive:

    SHOULD tolerate partial progress: if the user has done some steps manually (e.g.

    line 528
  31. Distinctive:

    SHOULD narrate context clearly when the user switches between terminal, Clerk dashboard, GCP Console, Vercel dashboard, and DNS provider.

    line 529
  32. Very specific:

    SHOULD offer the default-escape suffix [type 'default' to let me decide sensible defaults] on Socratic questions (repo slug, visibility, …

    line 530

“Common” is measured: kept by at least 6 in 10 of the 139 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/substrate:deploy

Take a migrated substrate project from localhost to a fully functional production deployment: public URL → custom domain → prod Clerk → prod Convex → prod env sync.

Heads up: this is a long interactive operation with five phases. Each phase has its own idempotency check, checklist, and approval gate — you can stop after any phase and resume later. The user will switch between terminal, Clerk dashboard, Google Cloud Console, Vercel dashboard, and possibly their DNS provider. Narrate context clearly.

When to run

  • /substrate:migrate has completed — src/App.tsx no longer shows the substrate welcome screen, and dev Clerk sign-in works on localhost.
  • convex/_generated/ is present (Convex codegen has run).
  • The user wants a production URL (*.vercel.app at minimum; custom domain is the goal).

When to REFUSE

SignalRedirect
Migration hasn't run (src/App.tsx still shows welcome screen)Run /substrate:migrate first
Dev Clerk not configured (.env.local missing VITE_CLERK_PUBLISHABLE_KEY)Run /substrate:migrate step 9 to complete dev Clerk setup
convex/_generated/ missingRun npx convex dev once in another terminal

Stage gate

test -f package.json \
  && test -d convex/_generated \
  && test -f .env.local \
  && grep -q "^VITE_CLERK_PUBLISHABLE_KEY=." .env.local \
  && ! grep -q "Substrate project initialized" src/App.tsx \
  || echo "STAGE_MISMATCH"

If STAGE_MISMATCH, stop and redirect per the table above.

Resolve SUBSTRATE_ROOT (same pattern as /substrate:init):

for candidate in \
  "$HOME/.claude/plugins/substrate" \
  "$PWD/.claude/plugins/substrate" \
  "${SUBSTRATE_ROOT:-}"; do
  if [ -n "$candidate" ] && [ -f "$candidate/scripts/connect-vercel.sh" ]; then
    echo "FOUND: $candidate"
    break
  fi
done

Phase A — Public deploy (*.vercel.app on dev backends)

Goal: app reachable at a Vercel-generated URL, using DEV Clerk + DEV Convex. Quick preview deploy for sharing; NOT real production (dev Clerk has shared Google OAuth, dev Convex isn't isolated).

Idempotency check:

test -f .vercel/project.json && git remote get-url origin >/dev/null 2>&1 && vercel whoami >/dev/null 2>&1 && echo "PHASE_A_LIKELY_DONE"

If likely done, ask: *.vercel.app appears already deployed. Skip Phase A? [type 'default' to let me decide sensible defaults]. Default: skip.

Steps

  1. GitHub remote. If no origin, run:

    bash "$SUBSTRATE_ROOT/scripts/init-github.sh" "<repo-slug>" "<public|private>"

    Ask user for slug + visibility with default-escape. Defaults: slug from package.json#name, visibility private.

  2. Vercel link. If no .vercel/project.json:

    bash "$SUBSTRATE_ROOT/scripts/connect-vercel.sh" "<project-slug>"
  3. Push DEV env to Vercel prod (temporary). Phase A uses dev backends so the public URL works immediately. Phase D will overwrite these.

    Use printf %s "$VALUE" | vercel env add NAME production (stdin form — piped heredoc is unreliable on some shells):

    VCU=$(grep -E "^VITE_CONVEX_URL=" .env.local | cut -d= -f2-)
    VCK=$(grep -E "^VITE_CLERK_PUBLISHABLE_KEY=" .env.local | cut -d= -f2-)
    CSK=$(grep -E "^CLERK_SECRET_KEY=" .env.local | cut -d= -f2-)
    printf '%s' "$VCU" | vercel env add VITE_CONVEX_URL production
    printf '%s' "$VCK" | vercel env add VITE_CLERK_PUBLISHABLE_KEY production
    printf '%s' "$CSK" | vercel env add CLERK_SECRET_KEY production
  4. Trigger deploy:

    git push origin main

    Vercel auto-builds. Print the expected Vercel dashboard URL.

  5. Verify. Ask user to visit the Vercel-generated URL and sign in via dev Clerk. Wait for y/n/default.

Phase A checklist (print to terminal before gate)

Phase A — Public Deploy
  ✓ GitHub remote:       <repo-url>
  ✓ Vercel project:      <vercel-url>
  ✓ Env pushed (dev):    VITE_CONVEX_URL, VITE_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY
  ✓ Deploy triggered:    git push origin main
  ✓ Live URL reachable:  https://<project>.vercel.app
  ✓ Dev sign-in works:   confirmed

⚠  App is running on DEV backends. Phase B+ add custom domain and prod.

Gate

"Continue to Phase B (custom domain)? (y/n/default)". Default: yes.


Phase B — Custom domain + DNS

Goal: custom domain points at the Vercel deployment, and 5 Clerk DNS CNAMEs are in place ready for Phase C.

Idempotency check:

vercel domains ls 2>/dev/null | grep -q "<domain>" && echo "PHASE_B_DOMAIN_OWNED"

Steps

  1. Custom domain decision. Ask: Register a custom domain? (y/n/default — default: skip, stay on *.vercel.app).

    If skip, jump to Phase C (user can still have a prod Clerk instance attached to the *.vercel.app URL, but it's uncommon).

  2. Get or buy the domain. Ask: Already own the domain? (y/n/default — default: buy).

    Buy:

    vercel domains buy <domain>

    Known bug: CLI has a polling timeout that can falsely report failure after a successful purchase. Verify with vercel domains ls before retrying.

    Add existing:

    vercel domains add <domain>
  3. Domain in team inventory vs. attached to project. These are two separate states. vercel domains buy puts the domain in inventory automatically. If you bought elsewhere, run vercel domains add <domain> to put it in inventory. Only THEN can you:

    vercel alias set <latest-deployment> <domain>

    to attach to the project.

  4. Add 5 Clerk DNS CNAMEs. Open Clerk dashboard → Domains → add <domain>. Clerk shows 5 tenant-specific CNAME targets. Add each via Vercel DNS:

    vercel dns add <domain> clerk           CNAME <tenant>.clerk.services
    vercel dns add <domain> accounts        CNAME <tenant>.accounts.clerk.services
    vercel dns add <domain> clkmail         CNAME mail.<tenant>.clerk.services
    vercel dns add <domain> clk._domainkey  CNAME dkim1.<tenant>.clerk.services
    vercel dns add <domain> clk2._domainkey CNAME dkim2.<tenant>.clerk.services

    CLI quirks:

    • --scope <team> MUST come AFTER the subcommand (vercel dns add ... --scope works; vercel --scope ... dns add ... errors).
    • vercel dns rm <id> --yes is NOT supported. Pipe yes y | vercel dns rm <id> if unattended.
  5. Wait for Clerk verification. In Clerk dashboard, click "Verify configuration". Clerk checks DNS and provisions SSL certs automatically. Can take 2–10 minutes.
  6. Confirm DNS resolves:

    dig <domain> +short

    Expect Vercel IPs. If empty, wait another 2 minutes and retry.

Phase B checklist

Phase B — Custom Domain
  ✓ Domain purchased/added:       <domain>
  ✓ Domain in team inventory:     yes
  ✓ Domain attached to project:   yes
  ✓ 5 Clerk CNAMEs added:         clerk, accounts, clkmail, clk._domainkey, clk2._domainkey
  ✓ Clerk "Verify configuration": clicked
  ✓ DNS resolves:                 <Vercel IPs>
  ⚠  SSL provisioning may still be in progress (Clerk handles automatically)

Gate

"Continue to Phase C (prod Clerk instance)?". Default: yes.


Phase C — Prod Clerk instance

Goal: create a production Clerk instance bound to the custom domain, write prod keys to .env.prod.

Prerequisite: Phase B complete — Clerk has verified DNS.

Idempotency + tenant-drift check:

Phase C has THREE possible states when re-entered. Detect which one before deciding whether to skip:

# State 1: .env.prod lacks pk_live_ → Phase C has never run. Proceed normally.
grep -q "^VITE_CLERK_PUBLISHABLE_KEY=pk_live_" .env.prod 2>/dev/null || echo "PHASE_C_FIRST_RUN"

# State 2: .env.prod has pk_live_ but Convex prod doesn't have matching
# CLERK_JWT_ISSUER_DOMAIN / CLERK_WEBHOOK_SECRET → TENANT DRIFT. User likely
# recreated Clerk prod (different tenant ID) or rotated keys.

LOCAL_ISSUER=$(grep "^CLERK_JWT_ISSUER_DOMAIN=" .env.prod 2>/dev/null | cut -d= -f2-)
LOCAL_WEBHOOK=$(grep "^CLERK_WEBHOOK_SECRET=" .env.prod 2>/dev/null | cut -d= -f2-)
DEPLOYED_ISSUER=$(npx convex env get CLERK_JWT_ISSUER_DOMAIN --prod 2>/dev/null || true)
DEPLOYED_WEBHOOK=$(npx convex env get CLERK_WEBHOOK_SECRET --prod 2>/dev/null || true)

if [ -z "$DEPLOYED_ISSUER" ]; then
  echo "PHASE_C_LOCAL_DONE_PHASE_D_PENDING"
elif [ "$LOCAL_ISSUER" != "$DEPLOYED_ISSUER" ] || [ "$LOCAL_WEBHOOK" != "$DEPLOYED_WEBHOOK" ]; then
  echo "DRIFT_DETECTED"
else
  echo "PHASE_C_FULLY_IN_SYNC"
fi

Handling each state:

  • PHASE_C_FIRST_RUN → proceed to step 1 below.
  • PHASE_C_LOCAL_DONE_PHASE_D_PENDING → user ran Phase C earlier but Convex prod doesn't have env set yet (Phase D hasn't completed). Ask: "Phase C appears done locally but prod env isn't synced. Skip to Phase D?" Default: yes.
  • PHASE_C_FULLY_IN_SYNC → .env.prod matches Convex prod exactly. Phase C is done. Ask: "Clerk prod already configured. Skip Phase C? [type 'default' to let me decide sensible defaults]". Default: skip.
  • DRIFT_DETECTED → Clerk credentials in .env.prod don't match what's deployed. STOP and print this warning verbatim (do NOT prompt to skip Phase C — drift means re-sync is required):

    ⚠  TENANT DRIFT DETECTED
    
    Clerk credentials in .env.prod don't match Convex prod env:
    
      .env.prod CLERK_JWT_ISSUER_DOMAIN: <LOCAL_ISSUER>
      Convex prod CLERK_JWT_ISSUER_DOMAIN: <DEPLOYED_ISSUER>
    
      .env.prod CLERK_WEBHOOK_SECRET:    <whsec_...XXXX>  (last 4 chars)
      Convex prod CLERK_WEBHOOK_SECRET:  <whsec_...YYYY>  (last 4 chars)
    
    Probable cause: you regenerated your Clerk prod instance or rotated keys.
    Tenant ID likely changed, which means FIVE things may need updating:
    
      1. DKIM CNAMEs (dkim1, dkim2, clkmail, clerk, accounts) — tenant-specific
         targets change. Re-run Phase B step 4 with Clerk dashboard's new targets.
      2. Publishable + Secret keys → push to Vercel prod (Phase D step 5).
      3. JWT Issuer Domain → push to Convex prod (Phase D step 4).
      4. Webhook Signing Secret → push to Convex prod (Phase D step 4).
      5. Webhook Endpoint URL in Clerk dashboard — verify it still points to
         https://<prod-convex>.convex.site/clerk-webhook; re-subscribe to user.*
         events if the old endpoint was deleted.
    
    Continue and re-sync Convex prod + Vercel prod? (y / n / default)

    On y or default: skip the "has .env.prod got pk_live_?" re-check and jump to Phase D (which will push the new .env.prod values to both backends). The user is responsible for re-running Phase B step 4 (DNS CNAMEs) if the tenant ID changed — the skill prints the recommendation but can't auto-detect what the NEW tenant's CNAME targets should be (those come from Clerk dashboard).

    On n: halt. User fixes .env.prod manually (typically by pasting back the old values from Clerk dashboard) and re-runs the skill.

Steps

Steps

  1. Domain-typo confirmation. BEFORE the user creates the Clerk prod instance, echo the domain back and require exact re-entry:

    About to create a Clerk production instance for:
    
      <domain>
    
    Typos BAKE INTO the publishable key and force a full recreation
    (losing tenant ID, all DKIM CNAMEs, webhooks). Type your domain
    back EXACTLY to confirm:

    Reject if mismatch. This prevents the 30-minute re-do cycle the first-run reporter hit.

  2. Create Clerk prod instance. User opens Clerk dashboard → toggle to "Production instance" → enter confirmed domain → Create.
  3. Wait for Clerk SSL provisioning (may already be complete from Phase B step 5).
  4. Configure sign-in methods. Email (code / magic link) + optional Google. Same as dev.
  5. Create Convex JWT template on prod Clerk. Configure → JWT Templates → New → Convex preset. Note the new Issuer URL — it's DIFFERENT from the dev issuer.
  6. Create prod webhook endpoint. Webhooks → Add Endpoint → https://<prod-convex-deployment>.convex.site/clerk-webhook (you'll have the prod Convex URL after Phase D step 1 — come back to this). Subscribe to user.created, user.updated, user.deleted. Copy the signing secret (whsec_...).
  7. GCP OAuth (optional, prod-only). If Google sign-in is desired on prod, the user needs their own Google OAuth client (Clerk's shared creds don't apply to prod):

    • Google Cloud Console → APIs & Services → Credentials → Create OAuth 2.0 Client ID → type: Web application
    • Authorized redirect URI (paste exactly): https://clerk.<domain>/v1/oauth_callback
    • Authorized JS origin: https://clerk.<domain>
    • Copy Client ID + Client Secret
    • Clerk dashboard → Social Connections → Google → Custom credentials → paste values

    Skip if user is OK with Email-only prod sign-in (which is fine).

  8. Collect prod Clerk values (all four — ask one at a time with default-escape NOT offered since these are typed-confirmation secrets):

    • Publishable Key (pk_live_...)
    • Secret Key (sk_live_...)
    • JWT Issuer Domain (prod issuer URL from step 5)
    • Webhook Signing Secret (whsec_... from step 6)
  9. Validate the webhook secret format before writing anything:

    if [[ ! "$CLERK_WEBHOOK_PROD" =~ ^whsec_ ]]; then
      echo "ERROR: webhook secret must start with whsec_. You probably pasted the URL."
      exit 1
    fi
  10. Write .env.prod (gitignored via .env* rule). VITE_CONVEX_URL is filled in Phase D step 3:

    cat > .env.prod <<EOF
    # Substrate production env (gitignored).
    # Source of truth for Convex prod + Vercel prod. Updated via /substrate:deploy.
    
    # Client-safe
    VITE_CONVEX_URL=
    VITE_CLERK_PUBLISHABLE_KEY=$CLERK_PK_LIVE
    
    # Server-only
    CLERK_SECRET_KEY=$CLERK_SK_LIVE
    CLERK_JWT_ISSUER_DOMAIN=$CLERK_ISSUER_PROD
    CLERK_WEBHOOK_SECRET=$CLERK_WEBHOOK_PROD
    EOF

Phase C checklist

Phase C — Prod Clerk Instance
  ✓ Domain confirmed (no typo):   <domain>
  ✓ Prod instance created:        Clerk prod for <domain>
  ✓ SSL cert provisioned:         https://clerk.<domain>
  ✓ Sign-in methods enabled:      Email [+ Google]
  ✓ JWT template created:         Convex preset on prod
  ✓ Webhook endpoint (URL pending prod Convex in Phase D)
  ✓ GCP OAuth configured:         yes/no (optional)
  ✓ Keys in .env.prod:            pk_live_, sk_live_, issuer, whsec_

Gate

"Continue to Phase D (prod Convex + env sync)?". Default: yes.


Phase D — Prod Convex + env sync

Goal: create prod Convex deployment, sync .env.prod values to Convex prod + Vercel prod (overwriting Phase A's dev-env temps).

Idempotency check:

npx convex env get CLERK_JWT_ISSUER_DOMAIN --prod 2>/dev/null | grep -q . && echo "PHASE_D_LIKELY_DONE"

Steps

  1. Create prod Convex deployment:

    npx convex deploy

    Note: --prod flag was removed in recent Convex versions — convex deploy targets production by default. CI uses CONVEX_DEPLOY_KEY; interactive prod pushes refuse piped stdin.

    First run: prompts to create a prod deployment (name usually project-name-prod). Convex prints the prod deployment URL.

  2. Finish the Clerk prod webhook endpoint (deferred from Phase C step 6). Update the endpoint URL in Clerk dashboard to:

    https://<prod-convex-deployment>.convex.site/clerk-webhook

    If already set to the dev URL, EDIT it rather than creating a new endpoint.

  3. Update .env.prod with prod Convex URL:

    PROD_CONVEX_URL="<prod-convex-url-from-step-1>"
    # macOS sed
    sed -i '' "s|^VITE_CONVEX_URL=.*|VITE_CONVEX_URL=$PROD_CONVEX_URL|" .env.prod
    # Linux sed
    # sed -i "s|^VITE_CONVEX_URL=.*|VITE_CONVEX_URL=$PROD_CONVEX_URL|" .env.prod
  4. Push .env.prod to Convex prod (Convex-only env vars — these are NOT pushed to Vercel):

    CLERK_ISSUER=$(grep "^CLERK_JWT_ISSUER_DOMAIN=" .env.prod | cut -d= -f2-)
    CLERK_WEBHOOK=$(grep "^CLERK_WEBHOOK_SECRET=" .env.prod | cut -d= -f2-)
    npx convex env set CLERK_JWT_ISSUER_DOMAIN "$CLERK_ISSUER" --prod
    npx convex env set CLERK_WEBHOOK_SECRET "$CLERK_WEBHOOK" --prod
  5. Push .env.prod to Vercel prod (overwrites Phase A's dev-env temps). Vercel doesn't have an update-in-place for env vars — remove + re-add:

    for KEY in VITE_CONVEX_URL VITE_CLERK_PUBLISHABLE_KEY CLERK_SECRET_KEY; do
      VAL=$(grep "^${KEY}=" .env.prod | cut -d= -f2-)
      vercel env rm "$KEY" production --yes >/dev/null 2>&1 || true
      printf '%s' "$VAL" | vercel env add "$KEY" production
    done

    Do NOT push CLERK_JWT_ISSUER_DOMAIN or CLERK_WEBHOOK_SECRET to Vercel — these are Convex-server-only and are already set via step 4.

Phase D checklist

Phase D — Prod Convex + Env Sync
  ✓ Prod Convex deployment:       <prod-convex-url>
  ✓ Webhook endpoint fixed up:    https://<prod-convex>.convex.site/clerk-webhook
  ✓ .env.prod VITE_CONVEX_URL:    updated to prod
  ✓ Convex prod env:              CLERK_JWT_ISSUER_DOMAIN, CLERK_WEBHOOK_SECRET set
  ✓ Vercel prod env (overwrote):  VITE_CONVEX_URL, VITE_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY

Gate

"Continue to Phase E (redeploy + verify)?". Default: yes.


Phase E — Redeploy + verify

Goal: redeploy with prod credentials; user confirms end-to-end prod sign-in on the custom domain.

Steps

  1. Trigger redeploy. Either:

    vercel --prod --yes

    OR push an empty commit to trigger Vercel's git integration:

    git commit --allow-empty -m "chore: redeploy with prod env"
    git push origin main
  2. Wait for build (~1–3 min).
  3. Verify on custom domain. Tell the user:

    Open https://<domain>. Sign in via prod Clerk (Email or Google).
    Confirm you land on an authenticated page with no console errors.
    
    Did prod sign-in work end-to-end? (y/n)
  4. If n, diagnose in this order:

    • Clerk allowlist missing custom domain → Clerk dashboard → Domains → add https://<domain>
    • Prod Clerk webhook URL mismatches .env.prod → Clerk dashboard → Webhooks → verify endpoint URL matches https://<prod-convex>.convex.site/clerk-webhook
    • Vercel prod env not refreshed → vercel env ls production and re-run Phase D step 5 if values look dev-ish
    • Convex prod env not set → npx convex env get CLERK_JWT_ISSUER_DOMAIN --prod should return the prod issuer; if empty, re-run Phase D step 4
    • SPA route 404 → confirm vercel.json with SPA rewrite is committed (shipped in the scaffold; if missing, add before redeploying)
    • Clerk sign-in redirects to /sign-up/verify-email-address and 404s → ensure <SignIn/> / <SignUp/> use routing="virtual" (frontend-doctrine §4.2a)

Final handoff

✔ Substrate production deploy complete.

  Clerk prod:     https://clerk.<domain>
  Convex prod:    <prod-convex-url>
  GitHub:         <repo-url>
  Vercel:         <vercel-url>
  Live URL:       https://<domain>

  Auto-deploy:    enabled — every push to main → rebuild
  Env staging:    .env.prod (gitignored, sync via /substrate:deploy re-run)

Next:
  - git push to redeploy any time
  - /substrate:quick-spec or /substrate:architect-spec to ship features
  - Rotating keys / Clerk tenant changes? Update .env.prod and re-run
    Phase D to sync.

Constraints

  • MUST verify migration is complete at the stage gate. Deploying the welcome screen is worthless.
  • MUST run idempotency check at the start of each phase. If done, offer to skip; don't force re-work.
  • MUST print each phase's checklist before the gate. Gates are user-review checkpoints.
  • MUST pause at each phase gate for explicit y/n/default approval. Non-negotiable.
  • MUST perform domain-typo confirmation before Clerk prod create (Phase C step 1). This is the single biggest footgun in the flow.
  • MUST perform tenant-drift detection at Phase C start (diff .env.prod against npx convex env get --prod). If DRIFT_DETECTED, print the full re-sync guidance and do NOT offer to skip Phase C — drift means Convex prod is stale.
  • MUST write .env.prod in Phase C, read from it in Phase D. .env.prod is the source of truth for production env staging.
  • MUST NOT commit .env.prod — gitignored via .env* rule.
  • MUST NOT push secret keys (Clerk Secret Key, webhook secret, Convex deploy key) to the client bundle.
  • MUST NOT push CLERK_JWT_ISSUER_DOMAIN or CLERK_WEBHOOK_SECRET as Vite env vars — these are Convex-server-only, set via npx convex env set ... --prod.
  • MUST NOT force-push to remote main.
  • MUST validate the webhook secret format (^whsec_) in Phase C step 9 before any write.
  • MUST NOT run dev Clerk setup — that belongs to /substrate:migrate. If .env.local lacks Clerk values, halt and redirect to migrate.
  • SHOULD tolerate partial progress: if the user has done some steps manually (e.g. bought the domain via dashboard), skip those steps rather than re-running and overwriting.
  • SHOULD narrate context clearly when the user switches between terminal, Clerk dashboard, GCP Console, Vercel dashboard, and DNS provider.
  • SHOULD offer the default-escape suffix [type 'default' to let me decide sensible defaults] on Socratic questions (repo slug, visibility, domain decisions). Typed confirmations (Clerk keys, domain-name retype) are NOT defaultable — deliberate manual inputs.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.