skills/fullstack-debugger/SKILL.md
Expert debugger for Next.js + Cloudflare Workers + Supabase stacks. Systematic troubleshooting for auth, caching, workers, RLS, CORS, and build issues. Activate on: 'debug', 'not working', 'error', 'broken', '500', '401', '403', 'cache issue', 'RLS', 'CORS'. NOT for: feature development (use language skills), architecture design (use system-architect).
npx skillsauth add curiositech/windags-skills fullstack-debuggerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert debugger for Next.js + Cloudflare Workers + Supabase stacks. Evidence-based troubleshooting with systematic layer isolation.
Is there an error message visible?
├── YES: Browser console error
│ ├── "Failed to fetch" → CORS/Network issue → Test endpoint with curl
│ ├── "Hydration failed" → SSR/Client mismatch → Check for browser APIs
│ ├── "Cannot read properties" → Data loading race → Add optional chaining
│ └── TypeScript error → Type mismatch → Check interface definitions
│
├── YES: Network tab shows red
│ ├── 401/403 status → Auth issue → Check JWT + RLS policies
│ ├── 404 status → Wrong endpoint → Verify worker routes
│ ├── 500 status → Server error → Check worker logs
│ └── CORS preflight fail → Missing headers → Add OPTIONS handler
│
├── YES: Build/Deploy error
│ ├── "Module not found" → Import path wrong → Check relative paths
│ ├── "Type error" → TypeScript strict → Fix type definitions
│ ├── Memory exceeded → Bundle too large → Check webpack config
│ └── Deploy timeout → Worker size → Optimize dependencies
│
└── NO: Silent failure/unexpected behavior
├── Empty data returned → RLS blocking → Test with direct SQL
├── Stale data shown → Cache not invalidating → Check KV cache keys
├── Auth state lost → Session issue → Verify localStorage persistence
└── Worker not updating → Deploy failed → Check wrangler status
Which layer contains the bug?
│
├── Client (Browser/React)
│ ├── Console errors present → Fix JavaScript/React issues
│ ├── Network requests failing → Check endpoint accessibility
│ └── State management broken → Debug React Query/Context
│
├── Next.js Application
│ ├── Build fails → Fix TypeScript/import issues
│ ├── Pages not rendering → Check routing/components
│ └── SSR/SSG issues → Verify static generation setup
│
├── Cloudflare Worker
│ ├── Worker logs show errors → Fix worker code
│ ├── CORS headers missing → Add proper headers
│ └── KV cache issues → Check cache keys/expiration
│
├── Supabase Database
│ ├── Auth failing → Check user session/JWT
│ ├── Queries empty → Test RLS policies
│ └── Realtime broken → Verify subscriptions
│
└── External APIs
├── Rate limited → Check headers/implement backoff
├── Changed response format → Update parsing logic
└── Service unavailable → Add error handling/fallbacks
After applying fix, how to verify?
│
├── Local testing
│ ├── Run `npm run build` → Ensure no build errors
│ ├── Test in browser → Verify UI works correctly
│ ├── Check console → No new errors introduced
│ └── Test edge cases → Boundary conditions work
│
├── Worker testing
│ ├── Deploy to staging → `wrangler deploy --env staging`
│ ├── Test endpoints → Curl all affected routes
│ ├── Check logs → `wrangler tail` shows no errors
│ └── Monitor for 10min → No immediate regressions
│
└── Database testing
├── Test as anon user → RLS policies work correctly
├── Test as auth user → Permissions appropriate
├── Check query performance → No new slow queries
└── Verify data integrity → No data corruption
Symptoms: Applying common fixes without understanding root cause Detection: If you're changing multiple things at once without testing each Fix: Stop. Reproduce issue first, then test ONE hypothesis at a time
Symptoms: Debugging client code when issue is in worker, or vice versa Detection: If you've been debugging for 30+ minutes in wrong layer Fix: Use decision tree above to isolate which layer actually has the bug
Symptoms: "Fixed" code still showing old behavior due to cached responses Detection: If fix looks correct but behavior unchanged Fix: Clear ALL caches - browser, React Query, KV, TypeScript, CDN
Symptoms: Assuming all empty queries are RLS issues Detection: If you immediately jump to RLS without checking other causes Fix: First verify query syntax, then check network, then RLS policies
Symptoms: Making multiple changes rapidly when live site is broken Detection: If you're editing production code without local reproduction Fix: Reproduce locally first, or rollback immediately and debug systematically
Initial symptom: User reports getting logged out randomly, 401 errors in network tab
Step 1: Layer isolation
Step 2: Evidence gathering
# Check current JWT
node -e "
const jwt = localStorage.getItem('sb-project-auth-token');
console.log('JWT payload:', JSON.parse(atob(jwt.split('.')[1])));
console.log('Expires:', new Date(JSON.parse(atob(jwt.split('.')[1])).exp * 1000));
"
Step 3: Root cause analysis JWT shows expiry 1 hour ago. Token refresh failing because:
Step 4: Fix implementation
-- Create policy that allows token refresh
CREATE POLICY "Allow token refresh" ON profiles
FOR SELECT USING (
auth.jwt() IS NOT NULL
OR current_setting('request.jwt.claims', true)::json->>'exp' > extract(epoch from now())::text
);
Step 5: Verification
Lesson: Token expiry can create cascading auth failures when RLS policies block refresh attempts
Initial symptom: "Access to fetch blocked by CORS policy" when calling worker API
Step 1: Reproduction
# Direct curl works
curl https://my-worker.workers.dev/api/meetings
# Returns data successfully
# Browser fetch fails with CORS error
fetch('https://my-worker.workers.dev/api/meetings').catch(console.error)
# CORS error
Step 2: Diagnosis
# Check what headers are returned
curl -i -H "Origin: https://my-site.com" https://my-worker.workers.dev/api/meetings
# Missing Access-Control-Allow-Origin header
Step 3: Trade-off analysis Option A: Allow all origins (*) - Simple but less secure Option B: Whitelist specific domains - Secure but requires maintenance Option C: Dynamic origin checking - Flexible but more complex
Step 4: Fix with security consideration
// worker.js - Option B chosen for security
const ALLOWED_ORIGINS = [
'https://my-site.com',
'https://my-site-staging.pages.dev',
'http://localhost:3000' // dev only
];
function corsHeaders(origin) {
return {
'Access-Control-Allow-Origin': ALLOWED_ORIGINS.includes(origin) ? origin : 'null',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
}
// Handle preflight
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: corsHeaders(request.headers.get('Origin'))
});
}
Step 5: Verification
# Test from allowed origin
curl -H "Origin: https://my-site.com" -i https://my-worker.workers.dev/api/meetings
# Should include Access-Control-Allow-Origin: https://my-site.com
# Test from disallowed origin
curl -H "Origin: https://evil-site.com" -i https://my-worker.workers.dev/api/meetings
# Should include Access-Control-Allow-Origin: null
Lesson: CORS failures often indicate missing preflight handling; security requires thoughtful origin whitelisting
Initial symptom: Meeting search returns empty array, but Supabase dashboard shows data exists
Step 1: Evidence collection
// Test current query
const { data, error, count } = await supabase
.from('meetings')
.select('*', { count: 'exact' })
.limit(5);
console.log({ data, error, count }); // data: [], error: null, count: 0
Step 2: Isolate RLS vs query issue
-- In Supabase SQL Editor, test as anon user
SET ROLE anon;
SELECT COUNT(*) FROM meetings; -- Returns 0
RESET ROLE;
-- Test as admin
SELECT COUNT(*) FROM meetings; -- Returns 1000+
Step 3: Policy analysis
-- Check existing policies
SELECT policyname, permissive, roles, cmd, qual
FROM pg_policies
WHERE tablename = 'meetings';
-- Shows: "authenticated_read" policy with USING (auth.uid() IS NOT NULL)
Step 4: Root cause Anonymous users need read access to public meetings, but policy requires authentication. Query silently fails instead of erroring.
Step 5: Fix with proper scoping
-- Remove overly restrictive policy
DROP POLICY "authenticated_read" ON meetings;
-- Add public read for published meetings
CREATE POLICY "public_read_published" ON meetings
FOR SELECT USING (status = 'published');
-- Add authenticated read for all meetings
CREATE POLICY "authenticated_read_all" ON meetings
FOR SELECT TO authenticated USING (true);
Step 6: Verification
-- Test as anon - should see published only
SET ROLE anon;
SELECT COUNT(*), COUNT(*) FILTER (WHERE status = 'published')
FROM meetings; -- Should show same count for both
-- Test as authenticated - should see all
SET ROLE authenticated;
SELECT COUNT(*), COUNT(*) FILTER (WHERE status = 'published')
FROM meetings; -- Total count > published count
Lesson: RLS policies fail silently; always test with actual user roles, not just admin dashboard
Before marking debug complete, verify:
This skill should NOT be used for:
system-architect for design decisionsperformance-engineer for speed/efficiencysecurity-analyst for vulnerability assessmentdata-engineer for schema/query optimizationfrontend-expert for design/usability problemsDelegate when you see:
system-architectperformance-engineersecurity-analystdata-engineerfrontend-experttools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.