一键导入
troubleshoot
Runbook for diagnosing and resolving production issues with the Site Scanning Engine on Cloud.gov.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Runbook for diagnosing and resolving production issues with the Site Scanning Engine on Cloud.gov.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Guide for deploying the Site Scanning Engine to Cloud.gov via GitHub Actions, and using the Cloud Foundry CLI to monitor and troubleshoot deployed environments.
Guide for adding new fields/columns to scan results, including entity decorators, snapshot integration, and API exposure.
Guide for implementing a new scan in the core-scanner library, including page evaluation, entity integration, and testing.
Common development commands and workflows — linting, testing, building, Docker management, and troubleshooting.
Commands for loading and managing scan data — ingesting federal domains, enqueueing scans, running single-site tests, and exporting snapshots.
Guide through initial development setup of the Site Scanning Engine (prerequisites, Docker, building, first data load).
| name | troubleshoot |
| description | Runbook for diagnosing and resolving production issues with the Site Scanning Engine on Cloud.gov. |
Runbook for diagnosing and resolving production issues in the Site Scanning Engine deployed on Cloud.gov (Cloud Foundry).
cf) installedcf login -a api.fr.cloud.gov --ssocf target -o gsatts-sitescan -s prod~/bin/download-current-queue.sh)The production system runs three automated workflows:
Ingest (.github/workflows/ingest.yml)
website tablewebsiteEnqueue Scans (.github/workflows/enqueue-scans.yml)
Scan Workers (7 instances, always running)
core_result, scan_statusCreate Snapshots (.github/workflows/create-daily-snapshots.yml)
Q: The database shows 29k rows but scan_date is yesterday. Why?
A: The website table is updated nightly by ingest, but the core_result table (scan results) is only updated when scans run (Mon/Wed/Fri).
Example timeline:
Tuesday 22:15 UTC: Ingest runs → 29k websites in database (updated timestamps)
Wednesday 00:00 UTC: Enqueue runs → 29k jobs added to queue
Wednesday 00:00-14:00 UTC: Workers process queue → core_result table updated
Thursday: No new scans (only ingest runs)
Friday 00:00 UTC: Enqueue runs again → new scan cycle
Key insight:
If it's Thursday and scan dates show Wednesday, that's normal — the next scan cycle won't start until Friday midnight UTC.
Is a scan cycle in progress right now?
Check if enqueue ran recently:
gh run list --workflow=enqueue-scans.yml --limit 1
Look for a run from today at 00:00 UTC.
Check if workers are actively scanning:
cf logs site-scanner-consumer | grep "Scan completed"
If you see regular messages, scans are in progress.
Check queue depth:
~/bin/download-current-queue.sh
wait > 0 → Scans still queuedwait = 0, active > 0 → Final scans finishingwait = 0, active = 0 → Scan cycle completeWhen will the next scan cycle start?
Check the schedule: Monday, Wednesday, Friday at 00:00 UTC. The enqueue workflow will automatically trigger at that time.
Use this flowchart to diagnose common issues:
Step 1: Check app status
cf apps
Outcomes:
cf restart site-scanner-apiStep 2: Check recent logs
cf logs site-scanner-api --recent
Common patterns:
cf scale site-scanner-api -m 2GStep 3: Check database health
See Database Health section below.
Outcomes:
Step 1: Check consumer status
cf apps
Look for site-scanner-consumer — should show 7 instances, all "running".
Outcomes:
cf restart site-scanner-consumerStep 2: Check Redis queue depth
See Queue Inspection section below.
Outcomes:
Step 3: Check consumer logs
cf logs site-scanner-consumer --recent
Common patterns:
cf scale site-scanner-consumer -m 4G)cf services)Step 1: Verify ingest ran
cf tasks site-scanner-consumer | head -20
Look for recent tasks named github-action-ingest-* or similar.
Outcomes:
Step 2: Check ingest logs
cf logs site-scanner-consumer --recent | grep -i "ingest\|total number of websites"
Indicators of success:
total number of websites following ingest: ~31500Indicators of failure:
column header mismatchUnexpected ErrorStep 3: Check website count in database
See Database Health section. If count is low despite SUCCEEDED task status, this is a silent failure (see Common Failure Modes).
cf apps
Expected output:
site-scanner-api — started, 1 instancesite-scanner-consumer — started, 7 instances (production)cf tasks site-scanner-consumer
Look for:
github-action-ingest-*, github-action-enqueue-scans-*, github-action-snapshot-*SUCCEEDED, FAILED, RUNNINGInterpreting task states:
SUCCEEDED — Task process exited with status 0 (but may have internal errors — always check logs!)FAILED — Task crashed or was killed by Cloud Foundry (OOM, timeout, or exit code != 0)RUNNING — Task is currently executingCritical: SUCCEEDED status only means the task submission and execution completed. It does NOT verify:
Always verify success by:
cf logs site-scanner-api --recent
cf logs site-scanner-consumer --recent
Useful filters:
# Show only errors
cf logs site-scanner-api --recent | grep -i error
# Show ingest activity
cf logs site-scanner-consumer --recent | grep -i ingest
# Show task output
cf logs site-scanner-consumer --recent | grep "APP/TASK"
cf logs site-scanner-consumer
Use case: Monitor a task in real-time while it runs.
Stop streaming: Press Ctrl+C
cf logs site-scanner-consumer | grep "Scan completed"
Use case: Watch scans complete in real-time to verify workers are actively processing the queue.
What you'll see:
2026-06-26T10:03:51.61-0400 [APP/PROC/WEB/4] OUT {"level":30,...,"msg":"Scan completed for site 'example.gov' in [3508ms]"}
2026-06-26T10:03:52.34-0400 [APP/PROC/WEB/2] OUT {"level":30,...,"msg":"Scan completed for site 'another.gov' in [2891ms]"}
Interpreting results:
Alternative filter (more specific):
cf logs site-scanner-consumer | grep '"msg":"Scan completed for site'
Tip: This is the best way to confirm workers are still actively scanning. Use this when you're unsure if the queue is empty or if scans are stuck.
cf services
Expected services:
scanner-postgres-02 (PostgreSQL database)scanner-message-queue (Redis)Service status:
cf service <name>cf env site-scanner-api
Use case: Verify database credentials, Redis connection string, S3 bucket config.
Look for:
VCAP_SERVICES — contains bound service credentialsNODE_ENV — should be productioncf restart site-scanner-api
cf restart site-scanner-consumer
Use case: Apply environment variable changes, recover from hung state.
cf restage site-scanner-api
Use case: Apply buildpack updates or service binding changes.
Warning: This is slower than restart (rebuilds the app).
~/bin/download-current-queue.sh
Output format:
wait: 0
active: 7
delayed: 0
failed: 12
Interpreting results:
| Metric | Normal State | Problem Indicators |
|---|---|---|
wait | 0 or decreasing | High (>1000) and not decreasing = queue backup |
active | 0-7 (one per worker) | Stuck at same number for >1 hour = workers hung |
delayed | 0 | High number = jobs scheduled for future (usually normal) |
failed | Low (<100) | High (>1000) = recurring scan errors |
Common scenarios:
wait=0, active=0, delayed=0, failed=lowwait=decreasing, active=7wait=high and not decreasing → Workers may be stuck or too slowIf failed count is very high and jobs are not retryable:
cf ssh site-scanner-consumer
Then inside the container:
# Connect to Redis (get credentials from VCAP_SERVICES)
redis-cli -h <host> -p <port> -a <password>
# Clear failed jobs
DEL bull:site-scans:failed
Warning: This deletes all failed job data. Only do this if you've investigated the failure cause and determined the jobs are not recoverable.
Step 1: Get database credentials
cf env site-scanner-api | grep -A 50 postgres
Look for: uri, host, port, username, password, dbname
Step 2: Create SSH tunnel
cf ssh site-scanner-api -L 65432:<HOST>:<PORT>
Replace <HOST> and <PORT> with values from credentials.
Step 3: Connect with psql (in a new terminal)
psql -h 127.0.0.1 -p 65432 -U <USERNAME> -d <DBNAME>
Enter password when prompted.
Website count (should be ~29,000-31,500)
SELECT COUNT(*) FROM website;
Outcomes:
~29,000-31,500 — Normal (varies as federal domain list grows)0 — Complete ingest failure (see Recovery Procedure)< 100 — Partial ingest failure or silent failure> 50,000 — Possible duplicate ingestion (investigate)Note: The website table stores ALL ingested domains (including those marked as filter=true). The actual scan count may differ from the website count because:
filter=true but are still scanned (filter is metadata, not a skip flag)Most recent ingest timestamp
SELECT MAX(updated) FROM website;
Expected: Within the last 24 hours (ingest runs daily at 22:15 UTC).
Core results count and freshness
SELECT COUNT(*) FROM core_result;
SELECT MAX(updated) FROM core_result;
Expected: Similar to website count; updated within the last few days (scans run Mon/Wed/Fri).
Check for specific site
SELECT * FROM website WHERE url = 'cms.gov';
Outcomes:
core_result for scan data)Check recent ingest activity
SELECT
DATE(updated) as date,
COUNT(*) as count
FROM website
GROUP BY DATE(updated)
ORDER BY date DESC
LIMIT 7;
Use case: Identify when ingest last ran successfully by checking which dates have bulk updates.
cf tasks site-scanner-consumer | grep ingest | head -5
Look for:
SUCCEEDEDWarning: SUCCEEDED only means the task completed, not that it processed data successfully (see silent failure below).
cf logs site-scanner-consumer --recent | grep -i "total number of websites"
Expected output:
total number of websites following ingest: 31522
If missing: Ingest failed before reaching the completion log (parsing error or crash).
cf logs site-scanner-consumer --recent | grep -iE "error|column.*mismatch|parsing"
Common error patterns:
column header mismatch expected: N columns got: M — CSV schema changed upstreamUnexpected Error: — Generic parsing failureECONNREFUSED — Cannot reach upstream CSV URL404 — Upstream CSV moved or deletedUse this procedure when ingest has failed and the database is empty or corrupted.
Why: Ensure deployed code matches current upstream CSV schema.
gh workflow run deploy.yml
Verify deployment:
gh run list --workflow=deploy.yml --limit 1
Wait for status: ✓ (completed).
Alternative: Trigger from GitHub Actions UI
Option A: Via GitHub Actions
gh workflow run ingest.yml
Option B: Directly via cf run-task
cf run-task site-scanner-consumer \
--command "node dist/apps/cli/main.js ingest" \
-k 2G -m 2G \
--name manual-ingest-$(date +%Y%m%d-%H%M%S)
Monitor task:
# Check task status
cf tasks site-scanner-consumer | head -5
# Stream logs
cf logs site-scanner-consumer
Check logs for success message:
cf logs site-scanner-consumer --recent | grep "total number of websites"
Expected: total number of websites following ingest: ~31500
Verify in database:
# Using SSH tunnel (see Database Health section)
psql -h 127.0.0.1 -p 65432 -U <USERNAME> -d <DBNAME> -c "SELECT COUNT(*) FROM website;"
Expected: ~31,500
If count is wrong:
curl -I https://raw.githubusercontent.com/GSA/federal-website-index/main/data/site-scanning-target-url-list.csvOption A: Via GitHub Actions
gh workflow run enqueue-scans.yml
Option B: Directly via cf run-task
cf run-task site-scanner-consumer \
--command "node dist/apps/cli/main.js enqueue-scans" \
-k 2G -m 2G \
--name manual-enqueue-$(date +%Y%m%d-%H%M%S)
Monitor queue:
~/bin/download-current-queue.sh
Expected: wait count increases to ~31,500, then decreases as workers process jobs.
Check queue periodically:
watch -n 60 ~/bin/download-current-queue.sh
Expected timeline: ~6-12 hours for 31,500 sites with 7 workers (depends on scan complexity and site responsiveness).
Indicators of completion:
wait: 0active: 0failed: < 100 (some failures are normal — unreachable sites, timeouts)Once scans complete:
gh workflow run create-daily-snapshots.yml
Verify snapshot generation:
curl https://api.gsa.gov/technology/site-scanning/data/site-scanning-latest.csv | wc -l
Check federal-website-index repository:
data/ directorydata/hyperlink_domains.csvdata/final_url_websites.csvIf not rebuilt: Trigger manual rebuild in federal-website-index repo (check their Actions workflows).
Signature:
cf logs shows: "column header mismatch expected: N columns got: M"SUCCEEDED despite failure (silent failure)Cause:
headers array in libs/ingest/src/ingest.service.tsFix:
Identify the mismatch:
# Download current CSV header
curl -s https://raw.githubusercontent.com/GSA/federal-website-index/main/data/site-scanning-target-url-list.csv | head -1
# Count columns
curl -s https://raw.githubusercontent.com/GSA/federal-website-index/main/data/site-scanning-target-url-list.csv | head -1 | awk -F',' '{print NF}'
Check deployed code:
git log --oneline -10 # Find latest deployed commit
git show <commit>:libs/ingest/src/ingest.service.ts | grep -A 20 "headers:"
If main branch has the fix, deploy:
gh workflow run deploy.yml
If main branch doesn't have the fix, update the code, merge, then deploy
Re-run ingest (see Recovery Procedure)
Signature:
cf tasks shows task SUCCEEDEDCause:
cf run-task is fire-and-forget (reports submission success, not task result)Fix:
Check logs for actual error:
cf logs site-scanner-consumer --recent | grep -i error
Address the root cause (usually column mismatch or unreachable CSV)
Re-run ingest
Prevention:
Signature:
~/bin/download-current-queue.sh shows high wait count that's not decreasingactive count stuck at same number for >1 hourCause:
Fix:
Check consumer logs for stuck workers:
cf logs site-scanner-consumer --recent | grep -i timeout
Restart consumers:
cf restart site-scanner-consumer
If a specific site is causing issues, consider adding it to a skip list (if pattern exists in code)
If queue backup persists, scale workers temporarily:
cf scale site-scanner-consumer -i 10
(Note: This will be reset on next deploy; update vars-prod.yml for permanent change)
Signature:
Cause:
Fix:
Verify database has data (see Database Health)
If database is empty, fix ingest first (see Recovery Procedure)
If database has data, re-run snapshot:
gh workflow run create-daily-snapshots.yml
Check snapshot task status:
cf tasks site-scanner-consumer | grep snapshot
If task failed, check logs:
cf logs site-scanner-consumer --recent | grep -i snapshot
Verify S3 credentials:
cf env site-scanner-consumer | grep -i s3
Signature:
cf apps shows instances in "crashed" stateCause:
vars-prod.ymlFix (temporary):
cf scale site-scanner-api -m 2G
cf scale site-scanner-consumer -m 4G
Fix (permanent):
vars-prod.yml or vars-dev.ymlgh workflow run deploy.ymlInvestigation:
Always check logs first: Most issues leave clear indicators in cf logs --recent
Verify task completion: SUCCEEDED task status doesn't mean the task succeeded internally — always check logs for error patterns
Monitor the queue: ~/bin/download-current-queue.sh is the fastest way to check scan progress
Use database queries to verify: Trust the database count, not just task status
Coordinate with federal-website-index: Most ingest issues stem from upstream schema changes
Deploy after code changes: Never let updated code sit undeployed if it affects ingest
Scale carefully: Ad-hoc cf scale commands are reverted on next deploy — update vars-*.yml for permanent changes
Check schedules: Ingest runs daily at 22:15 UTC; scans run Mon/Wed/Fri at 00:00 UTC; snapshots run daily at 15:00 UTC
Keep this skill updated: Add new failure modes as they're discovered