Infrastructure specialist for SpawnForge. Use when working with Vercel, Cloudflare R2, Neon DB, Upstash Redis, Clerk, Stripe, Sentry, PostHog, GitHub Actions CI/CD, or monitoring CI check status on PRs.
Resources
2Install
npx skillscat add tristan578/project-forge/claude-skills-infra-services Install via the SkillsCat registry.
Infrastructure & Services Guide
SpawnForge's infrastructure stack and how each service connects.
Architecture Overview
User Browser
├─ Vercel Edge (CDN, routing, middleware)
│ ├─ Next.js App (SSR + API routes)
│ │ ├─ Neon Postgres (users, projects, tokens, billing)
│ │ ├─ Upstash Redis (rate limiting, caching)
│ │ ├─ Clerk (authentication, webhooks)
│ │ ├─ Stripe (payments, webhooks)
│ │ ├─ Sentry (error tracking, AI monitoring)
│ │ └─ PostHog (analytics, feature flags)
│ └─ Static Assets
└─ Cloudflare R2 CDN (WASM engine binaries at engine.spawnforge.ai)Estimated monthly cost: ~$130-150
Service Reference
Account IDs, project slugs and DO-NOT-TOUCH rules are canonical in
references/service-accounts.md. If this file and that one
ever disagree, that one is right — fix this one.
Vercel (Hosting + Compute)
- Project:
spawnforge(production, spawnforge.ai) — NOTspawnforge-ai, which is the
Sentry project slug. Passingspawnforge-aito a Vercel command returns a not-found that
is indistinguishable from a permissions failure. - Sibling projects:
spawnforge-staging,spawnforge-docs,spawnforge-design - Scope:
--scope tnolanon EVERY Vercel CLI command - CLI commands:
vercel ls --scope tnolan,vercel logs --scope tnolan,vercel env ls|pull --scope tnolan - Config:
web/vercel.ts(typedVercelConfig: install/build commands, region,git.deploymentEnabled: false, the health-monitor cron; replacedvercel.json, PF-1060/#9097). Response headers live inweb/next.config.ts, not here - Key env vars:
VERCEL_URL,NEXT_PUBLIC_SITE_URL - Gotchas:
vercel devruns all services locally- Preview deployments get unique URLs
force-dynamicscoped to auth routes only (not root layout)
Cloudflare R2 (Engine CDN)
- Buckets:
spawnforge-engine(WASM binaries),spawnforge-assets(server-side uploads) - Worker:
engine-cdnatengine.spawnforge.ai/*— adds CORS headers - Source:
infra/engine-cdn/worker.js+wrangler.toml - Account ID:
0b949ff499d179e24dde841f71d6134f - Upload:
wrangler r2 object put <bucket>/<key> --file <path> --remote - Deploy skill:
/deploy-enginehandles upload and verification - Gotchas:
- R2 CORS rules only apply to S3 API, NOT custom domain — Worker is required
spawnforge-assetsis server-side only (signed URLs, no CORS Worker)- Content-hash filenames for cache busting (PF-888)
Neon Postgres (Database)
- Provisioned via: Vercel Marketplace (auto env vars)
- ORM: Drizzle (
web/src/lib/db/schema.ts) - Key env var:
DATABASE_URL - Commands:
cd web && npm run db:generate # Generate migration cd web && npm run db:migrate # Apply migrations cd web && npm run db:push # Push schema (dev only) cd web && npm run db:studio # Visual browser - Gotchas:
- Use
neon-httpdriver for serverless (not neon-serverless WebSocket) - Transactions via
sql.transaction([...statements]) - No connection pooling needed — neon-http is stateless
- Use
Upstash Redis (Rate Limiting + Cache)
- Provisioned via: Vercel Marketplace
- Key env vars:
UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKEN - Usage:
distributedRateLimit()inweb/src/lib/rateLimit/distributed.ts - Gotchas:
- REST-based (HTTP), not TCP — works everywhere including edge
- Rate limiter uses sorted sets with sliding window
Clerk (Authentication)
- Key env vars:
CLERK_SECRET_KEY,NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - Webhooks:
POST /api/webhooks/clerk— handlesuser.created,user.updated,user.deleted - Proxy:
proxy.tscallsclerkMiddleware()— required forauth()in Server Components - Gotchas:
vercel integration add clerkneeds terminal interaction (blocked for AI agents)- Must manually set
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-inandSIGN_UP_URL - CI/E2E: publishable key check gates ClerkProvider — missing key = no auth wrapper
user.deletedwebhook must cascade-delete user data (PF-840)
Stripe (Payments)
- Key env vars:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET(no publishable key — checkout is a server-side redirect to a Stripe-hosted page, soweb/never loads Stripe.js) - Webhooks:
POST /api/webhooks/stripe— handles checkout, subscription, invoice, charge events - Local testing:
stripe listen --forward-to http://spawnforge.localhost:1355/api/webhooks/stripe - Tiers: starter (free), hobbyist, creator, pro
- Gotchas:
- Webhook signatures MUST be verified with
constructEvent() - Idempotency keys stored in DB to prevent duplicate processing
- Refund webhooks must reverse addon token credits
- Webhook signatures MUST be verified with
Sentry (Error Tracking)
- Org:
tristan-nolan(NOTember-l0) - Project:
spawnforge-ai - Config:
sentry.server.config.ts,sentry.edge.config.ts(NEVER client config for AI monitoring) - Key env vars:
SENTRY_AUTH_TOKEN,NEXT_PUBLIC_SENTRY_DSN - MCP tools:
search_issues,get_issue_details,search_events,get_trace_details - Gotchas:
captureExceptionimport from@/lib/monitoring/sentry-server(not@sentry/nextjsdirectly)- Fingerprinting configured in server + edge configs
- PR code review via Sentry comments — must reply with commit SHA or false-positive explanation
PostHog (Analytics + Feature Flags)
- Key env vars:
NEXT_PUBLIC_POSTHOG_KEY,POSTHOG_PERSONAL_API_KEY,POSTHOG_LLM_CAPTURE
(there is noNEXT_PUBLIC_POSTHOG_HOST— nothing in the repo reads one) - Tracking: gated behind cookie consent (GDPR compliance, PF-668)
- Client provider:
web/src/components/providers/PostHogProvider.tsx - Server capture:
web/src/lib/analytics/posthog-server.ts— content-free$ai_generation
LLM observability (PF-907). Dependency-freefetchto PostHog's capture endpoint; dormant
unlessPOSTHOG_LLM_CAPTURE === 'true'ANDNEXT_PUBLIC_POSTHOG_KEYis set, and a no-op
without per-call consent. - Feature flags:
web/src/lib/flags/posthogFlags.ts— a local evaluator (PF-971 / #8952),
not the PostHog SDK. Requires BOTHPOSTHOG_PERSONAL_API_KEYandNEXT_PUBLIC_POSTHOG_KEY;
omit either andgetBooleanFlag()returns the caller's default with zero network I/O.
It supplies:deep-generation-tier— overridesNEXT_PUBLIC_USE_DEEP_GENERATIONprovider-kill-switch-<provider>— checked bycreateGenerationHandlerbefore token
deduction, so these gate real spend
- Gotchas:
- Only a safe subset of PostHog targeting is evaluated locally (full rollout, 0% rollout, or
a singletierexact-match filter); anything else falls back to the default with a one-time
warn log - Server capture never carries
$ai_input/$ai_output_choices— do not add them
- Only a safe subset of PostHog targeting is evaluated locally (full rollout, 0% rollout, or
GitHub Actions (CI/CD)
- Workflows:
.github/workflows/ci.yml(PR checks),.github/workflows/cd.yml(deploy) - CI gate job: required check that depends on all other jobs
- WASM caching: artifacts shared between CI and CD for same SHA
- Gotchas:
- Path filters can cause gate job to never run (shows "Expected" forever)
- CodeQL runs on push-to-main, on every PR targeting main, and weekly (
.github/workflows/codeql.yml) - Action versions must be pinned to SHA (not floating tags)
CRON_SECRETheader required for cron job verification
Health Check
# Quick health check of all services
curl -s http://spawnforge.localhost:1355/api/health | python3 -m json.tool/api/health checks ten services, not four (web/src/lib/monitoring/healthChecks.ts):
Database (Neon), Clerk, Payments (Stripe), Rate Limiting (Upstash), Engine CDN, Cloudflare R2,
AI Providers, Generation Factory, Chat Backend, Sentry.
Only Database and Clerk failures return HTTP 503; everything else degrades to HTTP 200 with
a per-service degraded/down entry. So a green status code is not a green stack — readservices[].
The AI Providers check grades per capability (#9719), not per key:
| Verdict | When |
|---|---|
down |
no chat backend resolves at all — nothing AI-shaped can be served |
degraded |
a chat backend resolves but some generation capability has neither a platform key nor a gateway route. The unconfigured capabilities are named in error, in details.unconfiguredCapabilities, and in the public-safe summary |
healthy |
chat resolves and every offered capability is configured |
An unset PLATFORM_* key therefore yields degraded, never "outage" — and that is
production's expected steady state today (docs/guides/platform-keys.md: no PLATFORM_* key
is set, deliberately). Remove a capability from the declaration when provisioning it;
missing or invalid declarations do not suppress faults. Only when every missing capability is explicitly listed in the deployment settingHEALTH_EXPECTED_UNCONFIGURED_CAPABILITIES does the entry carry configurationOnly: true,
which keeps it out of the top-level overall and out of the 15-minute synthetic monitor's
Sentry pages (#9727) — the entry itself still reads degraded on the status page. So: an
amber AI Providers card with no /health banner and no Sentry page is the documented
baseline; the same card WITH a page is a real regression.
Those key names are read through an indirection table (PLATFORM_KEY_ENV inweb/src/lib/config/providers.ts), so grepping for them finds nothing — seeweb/.env.example before concluding a provider is actually down.
Scripts
bash "${CLAUDE_SKILL_DIR}/scripts/check-services.sh"— Health check all SpawnForge services: Vercel status, GitHub CI recent runs, git remote connectivity, dev server, and WASM binariesbash "${CLAUDE_SKILL_DIR}/scripts/check-vercel-project.sh" [project-name]— Show deployment details for a specific Vercel project (default: spawnforge)
References
- See service-accounts.md — Canonical account IDs, project names, DO-NOT-TOUCH rules, and required GitHub Actions secrets
- See runbook.md — Quick runbook: Vercel logs, rollback, Sentry checks, R2 CDN verification, Stripe webhook testing, CI diagnostics
CI Status Monitoring
Monitor all CI checks on a PR and report pass/fail with failure logs.
Check current status
gh pr checks <PR_NUMBER> 2>&1Get failure logs
# Get the run ID from the check URL
gh run view <RUN_ID> --log-failed 2>&1 | tail -30Output format
PR #NNNN CI Status
━━━━━━━━━━━━━━━━━
✓ 15/18 passing
✗ 1 failing: Lighthouse Effects Delta Gate
⏳ 2 pending: WASM Build, E2E UI Tests
FAILURE: Lighthouse Effects Delta Gate
Error: ENOENT: no such file or directory, scandir '.lhci-baseline'Tips
- WASM Build takes ~12 min, E2E depends on it — these are always last to finish
- Seer Code Review is external (Sentry) — may take 3-5 min
- If
npm cifails in multiple jobs simultaneously, it's a lockfile issue — check the lockfile section in/build