deployment-validator
Validates application readiness for Render/production deployment. Auto-runs when user mentions deployment. Prevents "works locally, fails in production" issues. Always run this BEFORE any deployment.
$ 설치
git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/devops/deployment-validator ~/.claude/skills/claude-skill-registry// tip: Run this command in your terminal to install the skill
name: deployment-validator description: Validates application readiness for Render/production deployment. Auto-runs when user mentions deployment. Prevents "works locally, fails in production" issues. Always run this BEFORE any deployment. triggers: ["deploy", "render", "production", "staging", "push to render", "ready to deploy", "deployment", "going live"] version: 1.0.0 created: 2025-10-19 auto_invoke: true
Deployment Validator Skill
Purpose: Systematically validate the application is ready for production deployment, catching common "works locally, fails on Render" issues BEFORE they happen.
When to Use
This skill MUST be invoked when:
- User mentions deploying to Render, production, or staging
- User says "ready to deploy" or "push to production"
- Any git push to main/production branches
- After significant routing or static file changes
- Before creating deployment documentation
Common Issues We've Learned (Hard-Won Lessons)
Issue 1: Static File Path Mismatches
Problem: Files served from wrong directory (projectRoot vs publicDir) Symptom: Works locally, 404 in production Root Cause: Express static middleware serves from different base paths
Example:
// BAD - File in /public but served from projectRoot
app.use(express.static(projectRoot));
// Accessing /demo-scenario-picker.html serves from projectRoot/demo-scenario-picker.html
// But file is actually in projectRoot/public/demo-scenario-picker.html
// GOOD - Serve public files from publicDir
app.use(express.static(publicDir));
// Accessing /demo-scenario-picker.html serves from publicDir/demo-scenario-picker.html
Fix Pattern:
- Identify which directory the file is ACTUALLY in
- Ensure static middleware serves from that directory
- Update all references to match the served path
Issue 2: Route Precedence Overriding Static Middleware
Problem: Explicit routes defined AFTER static middleware override file serving Symptom: 404 for HTML files even though they exist Root Cause: Express matches routes in order - explicit routes win over static middleware
Example:
// BAD - Explicit route overrides static middleware
app.use(express.static(publicDir)); // Line 199
app.get('/demo-scenario-picker.html', (req, res) => { // Line 450
res.sendFile(path.join(projectRoot, 'demo-scenario-picker.html')); // WRONG PATH!
});
// GOOD - Remove explicit route, let static middleware handle it
app.use(express.static(publicDir)); // Line 199
// No explicit route needed - static middleware serves it automatically
Fix Pattern:
- Check for duplicate routes (explicit routes + static middleware)
- Remove explicit routes if static middleware already handles the file
- If explicit route is needed, ensure it uses correct path (publicDir, not projectRoot)
Issue 3: Missing Files in Production
Problem: Files exist locally but not committed/pushed Symptom: git status shows untracked files, Render can't find them Root Cause: File moved to new directory but git not updated
Example:
# File moved from projectRoot to /public but not tracked
$ git status
?? public/demo-scenario-picker.html
# File not in repo, so Render can't access it
Fix Pattern:
- Always check
git statusafter moving files git addnewly created/moved files- Verify file is in repo before deploying
Issue 4: Hardcoded Local Paths
Problem: Paths work on developer machine but not in production Symptom: ENOENT errors in production logs Root Cause: Absolute paths specific to local filesystem
Example:
// BAD - Hardcoded local path
const filePath = '/Users/developer/project/public/file.html';
// GOOD - Relative to project structure
const filePath = path.join(__dirname, '..', 'public', 'file.html');
Fix Pattern:
- Search codebase for hardcoded paths (/Users/, C:, etc.)
- Replace with path.join() using __dirname or process.cwd()
- Use environment variables for external paths
Issue 5: Environment Variable Mismatches
Problem: Different env vars locally vs production Symptom: Features work locally, fail in production Root Cause: .env file not synced with Render dashboard
Example:
# Local .env
PORT=3000
NODE_ENV=development
# Render dashboard (missing vars)
PORT=10000
# NODE_ENV not set - defaults incorrectly
Fix Pattern:
- Document all required env vars
- Verify Render dashboard has all vars set
- Use fallback values:
process.env.VAR || 'default'
Validation Checklist
Run this checklist BEFORE every deployment:
Step 1: Static File Validation
# Check which files are in /public
ls -la public/
# Verify files are tracked in git
git status
# Search for explicit routes that might override static middleware
grep -n "app.get.*\.html" src/index.ts
# Check static middleware configuration
grep -A5 "express.static" src/index.ts
Expected Results:
- All HTML files in /public are tracked by git (not in
?? untracked) - No explicit routes for files served by static middleware
- Static middleware serves from correct directory (publicDir for /public files)
Step 2: Route Precedence Check
# Find all app.get() routes in index.ts
grep -n "^app.get" src/index.ts
# Check order: static middleware should be BEFORE explicit routes
# Line numbers should be: static middleware (low) -> explicit routes (high)
Expected Results:
- Static middleware defined early (around line 199-244)
- Explicit routes defined later (after line 275)
- No duplicate routes (same path in static middleware + explicit route)
Step 3: Path Validation
# Search for hardcoded paths
grep -r "\/Users\/" src/
grep -r "C:\\\\" src/
grep -r "projectRoot" src/index.ts | grep -v "const projectRoot"
# Verify path.join usage for all file operations
grep -n "sendFile" src/index.ts
Expected Results:
- No hardcoded user-specific paths
- All sendFile() calls use path.join() with __dirname or projectRoot/publicDir
- Correct base directory (publicDir for /public files, projectRoot for root files)
Step 4: Git Status Validation
# Check for untracked files
git status --porcelain | grep "^??"
# Check for uncommitted changes
git status --porcelain | grep "^ M"
# Verify critical files are tracked
git ls-files public/ | wc -l
Expected Results:
- No untracked files in /public (unless intentionally gitignored)
- All changes committed
- All production files present in git repository
Step 5: Environment Variable Check
# List all env vars used in code
grep -r "process.env" src/ | grep -v node_modules | cut -d: -f2 | grep -o "process.env\['[^']*'\]" | sort -u
# Compare with .env.example (if exists)
cat .env.example
Expected Results:
- All required env vars documented
- Render dashboard configured with all necessary vars
- Fallback values for non-critical vars
Step 6: Build Validation
# Clean build
npm run build
# Check for build errors
echo $? # Should be 0
# Verify dist/ directory created
ls -la dist/
Expected Results:
- Build succeeds without errors
- dist/ directory contains compiled JavaScript
- No TypeScript errors
Step 7: Local Production Simulation
# Run in production mode locally
NODE_ENV=production npm start
# Test critical endpoints
curl http://localhost:3000/health
curl http://localhost:3000/demo-scenario-picker.html
curl http://localhost:3000/api/v1/requirements
Expected Results:
- Server starts successfully
- All endpoints return 200 (not 404)
- HTML files serve correctly
Automated Validation Script
Create this script at /scripts/validate-deployment.sh:
#!/bin/bash
# Deployment Validation Script
# Run this BEFORE every deployment
set -e # Exit on first error
echo "=== Project Conductor Deployment Validator ==="
echo ""
# Step 1: Git Status
echo "1. Checking git status..."
UNTRACKED=$(git status --porcelain | grep "^??" || true)
if [ -n "$UNTRACKED" ]; then
echo "❌ FAIL: Untracked files found:"
echo "$UNTRACKED"
exit 1
fi
echo "✅ PASS: No untracked files"
UNCOMMITTED=$(git status --porcelain | grep "^ M" || true)
if [ -n "$UNCOMMITTED" ]; then
echo "⚠️ WARNING: Uncommitted changes found:"
echo "$UNCOMMITTED"
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Step 2: Check for duplicate routes
echo ""
echo "2. Checking for duplicate routes..."
DUPLICATES=$(grep -n "app.get.*\.html" src/index.ts | wc -l)
if [ "$DUPLICATES" -gt 5 ]; then
echo "⚠️ WARNING: Found $DUPLICATES explicit HTML routes"
echo " Review for conflicts with static middleware"
grep -n "app.get.*\.html" src/index.ts
fi
# Step 3: Check for hardcoded paths
echo ""
echo "3. Checking for hardcoded paths..."
HARDCODED=$(grep -r "\/Users\/" src/ 2>/dev/null | grep -v node_modules || true)
if [ -n "$HARDCODED" ]; then
echo "❌ FAIL: Hardcoded paths found:"
echo "$HARDCODED"
exit 1
fi
echo "✅ PASS: No hardcoded paths"
# Step 4: Validate static file configuration
echo ""
echo "4. Validating static file configuration..."
PUBLIC_FILES=$(ls -1 public/*.html 2>/dev/null | wc -l)
echo " Found $PUBLIC_FILES HTML files in /public"
# Check if publicDir is used correctly
PUBLICDIR_USAGE=$(grep -c "express.static(publicDir)" src/index.ts || true)
if [ "$PUBLICDIR_USAGE" -lt 1 ]; then
echo "⚠️ WARNING: publicDir static middleware not found"
fi
# Step 5: Build test
echo ""
echo "5. Running build test..."
npm run build > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✅ PASS: Build successful"
else
echo "❌ FAIL: Build failed"
exit 1
fi
# Step 6: Check required files
echo ""
echo "6. Checking required files..."
REQUIRED_FILES=(
"src/index.ts"
"package.json"
"tsconfig.json"
)
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "❌ FAIL: Missing required file: $file"
exit 1
fi
done
echo "✅ PASS: All required files present"
# Summary
echo ""
echo "=== Deployment Validation Complete ==="
echo "✅ Ready to deploy to Render"
echo ""
echo "Next steps:"
echo " 1. git add ."
echo " 2. git commit -m 'Ready for deployment'"
echo " 3. git push origin main"
echo " 4. Monitor Render deployment logs"
Make it executable:
chmod +x scripts/validate-deployment.sh
Usage
Automatic Invocation
When user says: "ready to deploy", "push to render", "deploy to production"
Your Response:
🛡️ Running deployment validation first...
[Run validation checklist steps 1-7]
Results:
✅ Static files validated
✅ Route precedence correct
✅ No hardcoded paths
✅ Git status clean
✅ Environment variables documented
✅ Build successful
✅ Local production test passed
All checks passed! Ready to deploy.
Manual Invocation
User can explicitly call:
npm run validate:deploy
# or
./scripts/validate-deployment.sh
Fix Patterns Reference
Pattern 1: File in /public, served from projectRoot
Detection: File exists in /public but 404 in production Fix:
// Before (WRONG)
app.get('/file.html', (req, res) => {
res.sendFile(path.join(projectRoot, 'file.html')); // File not here!
});
// After (CORRECT)
// Remove explicit route, let static middleware handle it
app.use(express.static(publicDir)); // This serves /public files at root
Pattern 2: Explicit route overrides static middleware
Detection: grep shows both static middleware AND explicit route for same file Fix:
// Before (CONFLICT)
app.use(express.static(publicDir)); // Line 199
app.get('/file.html', ...); // Line 450 - OVERRIDES!
// After (RESOLVED)
app.use(express.static(publicDir)); // Line 199
// Removed explicit route - static middleware handles it
Pattern 3: File moved but not tracked
Detection: git status shows ?? public/file.html
Fix:
git add public/file.html
git commit -m "Add file to public directory"
git push origin main
Pattern 4: Wrong path in sendFile
Detection: ENOENT error in production logs Fix:
// Before (WRONG)
res.sendFile(path.join(projectRoot, 'file.html')); // File is in /public!
// After (CORRECT)
res.sendFile(path.join(publicDir, 'file.html')); // Correct base dir
Integration with package.json
Add these scripts:
{
"scripts": {
"validate:deploy": "./scripts/validate-deployment.sh",
"predeploy": "npm run validate:deploy",
"deploy": "git push origin main"
}
}
Now npm run deploy automatically validates before pushing.
Skill Improvement Tracking
Version History:
- 1.0.0 (2025-10-19): Initial creation from deployment debugging session
- Captured static file path mismatch issue
- Captured route precedence issue
- Captured git tracking issue
- Created automated validation checklist
Future Enhancements:
- Add automated fix suggestions (not just detection)
- Integrate with CI/CD pipeline
- Add Render-specific log monitoring
- Database migration validation
- Environment variable auto-sync with Render
Success Metrics
This skill is successful if:
- Zero "works locally, fails on Render" incidents after deployment
- Validation catches issues before git push (not after)
- New developers can deploy confidently using this checklist
- Production deployments succeed on first try (no rollbacks)
Related Skills
validation: General validation workflow (tests, linting)scout: Find deployment best practices from external sources
References
- Express.js static middleware docs: https://expressjs.com/en/starter/static-files.html
- Render deployment guide: https://render.com/docs/deploy-node-express-app
- Path module docs: https://nodejs.org/api/path.html
Remember: This skill was created from REAL debugging pain. Every check in this list prevented an actual production issue. Use it religiously.
Repository
