Skip to main content

Common Issues

From ENGINEERING.md:
Database connection: Check DATABASE_URL, Neon dashboard status, connection pool limits in server/db.ts Migrations: Run npm run migrate manually to debug. Check drizzle/migrations/ exists. Failures are logged but don’t block startup. Quiz errors: Ensure quiz_answers table exists (migrations). Verify learner has active lesson. Check browser console. Build errors: npx tsc --noEmit to check types. rm -rf client/dist server/dist to clear cache. TS_NODE_TRANSPILE_ONLY=true for deployment. Auth issues: Verify JWT_SECRET is set. Check token expiration. Parent users scoped to own learners only.

Database Connection Problems

ECONNREFUSED

Symptom:
Error: connect ECONNREFUSED 127.0.0.1:5432
Causes:
  1. PostgreSQL not running
  2. Wrong host/port in DATABASE_URL
  3. Firewall blocking connection
Solutions:
# Check if PostgreSQL is running
sudo systemctl status postgresql
# or
brew services list | grep postgresql

# Start PostgreSQL
sudo systemctl start postgresql
# or
brew services start postgresql@15

# Test connection
psql -U postgres -h localhost -c "SELECT version();"

Authentication Failed

Symptom:
error: password authentication failed for user "postgres"
Solution:
# Reset password (local PostgreSQL)
sudo -u postgres psql
postgres=# ALTER USER sunschool WITH PASSWORD 'new_secure_password';
postgres=# \q

# Update .env
DATABASE_URL="postgresql://sunschool:new_secure_password@localhost:5432/sunschool"

# Test
psql "$DATABASE_URL" -c "SELECT 1;"

SSL Required

Symptom:
error: no pg_hba.conf entry for host, SSL off
Solution:
# Add sslmode to connection string
DATABASE_URL="postgresql://...?sslmode=require"

# Or set flag
DATABASE_SSL=true

Connection Pool Exhausted

Symptom:
Error: timeout acquiring connection
Waiting: 5, Idle: 0, Total: 10
Solution: From server/db.ts:
// Increase pool size
export const pool = new Pool({ 
  connectionString: DATABASE_URL,
  max: 20,  // Increase from 10
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000,  // Increase timeout
});
Or close idle connections:
# Find idle connections
psql $DATABASE_URL -c "SELECT pid, usename, state, query FROM pg_stat_activity WHERE state = 'idle';"

# Kill idle connections
psql $DATABASE_URL -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND pid <> pg_backend_pid();"

AI Provider Failures

OpenRouter 401 Unauthorized

Symptom:
Error: OpenRouter API request failed: 401 Unauthorized
Solution:
# Verify API key
curl https://openrouter.ai/api/v1/auth/key \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

# Should return: {"data":{"label":"..."}}

# If invalid, get new key from:
# https://openrouter.ai/keys

# Update .env
OPENROUTER_API_KEY="sk-or-v1-..."

OpenRouter 402 Insufficient Credits

Symptom:
Error: OpenRouter API request failed: 402 Payment Required
Message: Insufficient credits
Solution:
  1. Add credits at openrouter.ai/credits
  2. Fallback model will be tried automatically:
From server/config/env.ts:
export const DEFAULT_SVG_MODEL_FALLBACKS = [
  'google/gemini-3.1-flash-lite-preview',  // Cheaper fallback
  'google/gemini-3-flash-preview',
];

OpenRouter 404 Model Not Found

Symptom:
Error: Model 'google/gemini-4-pro' not found
Solution:
# Check available models
curl https://openrouter.ai/api/v1/models \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  | jq '.data[].id'

# Update to valid model
OPENROUTER_SVG_MODEL="google/gemini-3.1-pro-preview"

Bittensor Connection Timeout

Symptom:
Bittensor chat failed: Error: timeout after 10000ms
Falling back to OpenRouter
Solution: From server/config/flags.ts:
# Ensure fallback is enabled
BITTENSOR_FALLBACK_ENABLED=1

# Or disable Bittensor entirely
ENABLE_BITTENSOR_SUBNET_1=0
LLM_PROVIDER=openrouter
Test Bittensor connection:
curl https://archive.opentensor.ai/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{__typename}"}'

# Should return: {"data":{"__typename":"Query"}}

Authentication Issues

Invalid or Expired Token

Symptom:
{
  "error": "Invalid or expired token"
}
Causes:
  1. Token expired (default 7 days)
  2. JWT_SECRET changed after token issued
  3. Malformed token
Solutions:
# Check token expiry
node -e "
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const decoded = jwt.decode(token);
console.log('Expires:', new Date(decoded.exp * 1000));
"

# User must re-login to get new token
curl -X POST http://localhost:5000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password"}'

Forbidden (403)

Symptom:
{
  "error": "Forbidden"
}
Cause: User role doesn’t have permission for the endpoint. From server/routes.ts:
// Admin-only endpoint
app.get("/api/parents", hasRole(["ADMIN"]), ...)

// Parent trying to access admin endpoint = 403
Solution:
# Check user role
curl http://localhost:5000/api/user \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.role'

# If wrong role, promote user (database)
psql $DATABASE_URL -c \
  "UPDATE users SET role = 'ADMIN' WHERE username = 'youruser';"

Parent Can’t Access Learner Data

Symptom:
{
  "error": "Not authorized to view this learner's lessons"
}
Cause: Learner doesn’t belong to parent. Solution:
# Check parent-child relationship
psql $DATABASE_URL -c \
  "SELECT u.id, u.name, u.role, u.parent_id 
   FROM users u 
   WHERE u.role = 'LEARNER' 
   ORDER BY u.parent_id;"

# Fix parent_id if wrong
psql $DATABASE_URL -c \
  "UPDATE users 
   SET parent_id = (SELECT id FROM users WHERE username = 'parent_user') 
   WHERE id = 123;"

Migration Problems

Migration Already Applied

Symptom:
Error: relation "quiz_answers" already exists
Cause: Migration ran but wasn’t recorded in drizzle_migrations. Solution:
# Check what's recorded
psql $DATABASE_URL -c \
  "SELECT * FROM drizzle_migrations ORDER BY created_at DESC;"

# Check if table exists
psql $DATABASE_URL -c "\dt+ quiz_answers"

# Manually mark migration as applied
psql $DATABASE_URL -c \
  "INSERT INTO drizzle_migrations (hash, created_at) 
   VALUES ('0004_quiz_answers', NOW());"

Constraint Violation During Migration

Symptom:
Error: check constraint "score_range" is violated by some row
Cause: Existing data doesn’t meet new constraint. Solution:
# Find violating rows
psql $DATABASE_URL -c \
  "SELECT id, score FROM lessons WHERE score < 0 OR score > 100;"

# Fix data
psql $DATABASE_URL -c \
  "UPDATE lessons SET score = 0 WHERE score < 0;"

psql $DATABASE_URL -c \
  "UPDATE lessons SET score = 100 WHERE score > 100;"

# Re-run migration
npm run migrate

Build Errors

TypeScript Compilation Errors

Symptom:
error TS2339: Property 'role' does not exist on type 'User'
Solution:
# Check types
npx tsc --noEmit

# Clear cache
rm -rf node_modules/.cache
rm -rf client/dist server/dist

# Reinstall dependencies
npm install

# Rebuild
npm run build

Vite Build Fails

Symptom:
Error: Cannot find module '@/components/Header'
Solution:
# Check Vite config (client/vite.config.ts)
# Verify path aliases

# Clear Vite cache
rm -rf client/.vite
rm -rf client/dist

# Rebuild
cd client && npx vite build

Deploy Build Timeout

Symptom:
Railway build timeout after 10 minutes
Solution: From ENGINEERING.md:
TS_NODE_TRANSPILE_ONLY=true for deployment.
# Add to Railway environment variables
TS_NODE_TRANSPILE_ONLY=true
In package.json:
{
  "scripts": {
    "deploy": "TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=tsconfig.deploy.json ts-node server/index.ts"
  }
}

Lesson Generation Issues

Lesson Generation Fails with 503

Symptom:
{
  "error": "Lesson generation failed after multiple attempts. Please try again."
}
Causes:
  1. AI provider API down
  2. Rate limit exceeded
  3. Invalid API key
  4. Network timeout
Debugging:
# Check server logs for specific error
railway logs --tail 50 | grep "Lesson"

# Test OpenRouter directly
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemini-3.1-pro-preview",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

Lesson Contains Placeholder Content

From ENGINEERING.md:
validateLessonSpec() rejects placeholder/stub content — generation failures return 503 (never save stubs)
Symptom: Lesson generation succeeds but contains “Lorem ipsum” or “[Placeholder]”. Cause: Validation not working properly. Solution: Check server/services/lesson-validator.ts:
// Should reject placeholders
const placeholderPatterns = [
  /lorem ipsum/i,
  /\[placeholder\]/i,
  /\[insert.*here\]/i,
  /\[TODO\]/i,
];

for (const pattern of placeholderPatterns) {
  if (pattern.test(content)) {
    throw new Error('Lesson contains placeholder content');
  }
}

Images Not Generating

Symptom: Lessons created without images. Cause: Background image generation failing silently. Debugging:
# Check logs for image generation
railway logs | grep "BG.*images"

# Should see:
# [BG] Images generated for lesson abc-123

# If not, check:
echo $ENABLE_SVG_LLM
echo $IMAGE_PROVIDER
echo $MAX_IMAGES_PER_LESSON
Solution:
# Enable image generation
ENABLE_SVG_LLM=1
IMAGE_PROVIDER=svg-llm
MAX_IMAGES_PER_LESSON=4
IMAGE_GENERATION_TIMEOUT=15000

Quiz Errors

From ENGINEERING.md:
Quiz errors: Ensure quiz_answers table exists (migrations). Verify learner has active lesson. Check browser console.

Quiz Submission Fails

Symptom:
{
  "error": "Lesson not found"
}
Debugging:
# Check if learner has active lesson
psql $DATABASE_URL -c \
  "SELECT id, learner_id, status, subject 
   FROM lessons 
   WHERE learner_id = 123 AND status = 'ACTIVE';"

# Should return one row
# If no results, create new lesson via UI or API

Points Not Awarded

Symptom: Quiz submitted successfully but no points added. Debugging:
# Check points_ledger
psql $DATABASE_URL -c \
  "SELECT * FROM points_ledger 
   WHERE learner_id = 123 
   ORDER BY created_at DESC 
   LIMIT 5;"

# Check learner_points
psql $DATABASE_URL -c \
  "SELECT * FROM learner_points WHERE learner_id = 123;"
Solution: Tables might not exist (migration issue).
# Run migrations
npm run migrate

# Verify tables exist
psql $DATABASE_URL -c "\dt+ points_ledger learner_points"

Performance Issues

Slow Response Times

Symptom: API requests taking > 5 seconds. Debugging:
# Check database query performance
psql $DATABASE_URL -c "
SELECT 
  calls, 
  mean_exec_time, 
  query 
FROM pg_stat_statements 
ORDER BY mean_exec_time DESC 
LIMIT 10;
"

# Enable query logging (PostgreSQL)
psql $DATABASE_URL -c "
ALTER DATABASE sunschool SET log_statement = 'all';
ALTER DATABASE sunschool SET log_duration = on;
"
Solutions:
  1. Add missing indexes:
CREATE INDEX idx_lessons_learner_status ON lessons(learner_id, status);
CREATE INDEX idx_quiz_answers_learner_date ON quiz_answers(learner_id, answered_at DESC);
  1. Optimize queries:
// Bad: N+1 query problem
for (const lesson of lessons) {
  const learner = await storage.getUser(lesson.learnerId);
}

// Good: Join or batch query
const lessons = await db
  .select()
  .from(lessons)
  .leftJoin(users, eq(lessons.learnerId, users.id));

High Memory Usage

Symptom:
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
Solution:
# Increase Node.js memory limit
NODE_OPTIONS="--max-old-space-size=2048"  # 2GB

# Add to Railway environment variables
# or to package.json:
{
  "scripts": {
    "start": "node --max-old-space-size=2048 dist/server/index.js"
  }
}
Check for memory leaks:
// Add to server
setInterval(() => {
  const usage = process.memoryUsage();
  console.log(`Memory: ${Math.round(usage.heapUsed / 1024 / 1024)} MB`);
  if (usage.heapUsed > 1500 * 1024 * 1024) {  // 1.5GB
    console.warn('High memory usage detected');
  }
}, 60000);  // Every minute

Debugging Workflow

1

Check Server Logs

# Railway
railway logs --tail 100

# Local
npm run dev
# Watch console output
2

Test Database Connection

psql "$DATABASE_URL" -c "SELECT current_database();"
3

Verify Environment Variables

# Check required vars are set
echo $DATABASE_URL
echo $JWT_SECRET
echo $OPENROUTER_API_KEY
4

Check Health Endpoint

curl http://localhost:5000/api/healthcheck
5

Review Recent Migrations

psql $DATABASE_URL -c \
  "SELECT * FROM drizzle_migrations 
   ORDER BY created_at DESC LIMIT 5;"
6

Test API Endpoints

# Login
TOKEN=$(curl -X POST http://localhost:5000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password"}' \
  | jq -r '.token')

# Get user
curl http://localhost:5000/api/user \
  -H "Authorization: Bearer $TOKEN"

Getting Help

Next Steps