Playbook — worker dispatch stalled (queue grows, nothing runs)
Symptom: dev_r_worker_queue accumulates rows with status=queued and none move to
running. No claude -p worker processes spawn on bms-4. GitHub issues stay
ai-dev-queued with no PRs.
Trigger / confirm
-
Count queued jobs:
SELECT status, count(*) FROM dev_r_worker_queue GROUP BY status;Many
queued, 0running→ dispatch is stalled. -
Check the leader’s dispatcher log on vps-i1 (primary leader):
ssh -i ~/.ssh/id_ed25519 root@217.154.82.162 \ "grep -E 'Leader|\[loop\]|WARN|AUTO-HEAL' /var/log/p24-infra-dispatcher.log | tail -20"Leader: dispatching (server=vps-i1)with no following[loop]lines → the Python loop is crashing before its firstlog()call (likely aKeyErroron a missing DB column — see root cause below).WARN: bms-4 idle but N job(s) queued (weights=[...], server_max_weight=...)→ weight mismatch; dispatcher will auto-heal within 2 minutes.AUTO-HEAL: bms-4.max_weight_prime bumped light → heavy→ already self-healed.Not leader — skipping dispatch.on both vps-i1 and bms-4 → lease problem (checkdev_r_dispatcher_lease); bms-4’s service-role key 401s (#1329) so bms-4 can never win the lease — that is expected, vps-i1 is primary.
-
Reproduce the crash with the real env on the leader:
ssh -i ~/.ssh/id_ed25519 root@217.154.82.162 'bash -lc " cd /opt/p24-infra; set -a; source monitoring/.env; set +a export SUPABASE_SERVICE_ROLE_KEY=\${SUPABASE_SERVICE_ROLE_KEY:-\$SUPABASE_SERVICE_KEY} export DISPATCHER_SUPA_URL=https://mwkqmgadqnkkihjdeqsi.supabase.co export DISPATCHER_SERVER_NAME=vps-i1 DISPATCHER_IS_NIGHT=false DISPATCHER_LOG=/tmp/d.log export DISPATCHER_SERVERS=\$(curl -sf \"\$DISPATCHER_SUPA_URL/rest/v1/dev_r_server_capacity?enabled=eq.true&select=*\" -H \"apikey: \$SUPABASE_SERVICE_ROLE_KEY\" -H \"Authorization: Bearer \$SUPABASE_SERVICE_ROLE_KEY\") python3 scripts/queue-dispatcher-loop.py"'A
KeyErrorhere names the column the bashSELECTfailed to fetch.
Root causes
1. Missing column in dispatcher SELECT (most common)
scripts/queue-dispatcher-loop.py reads a dev_r_server_capacity column that
scripts/queue-dispatcher.sh’s SELECT (section “3. Load server capacity”) does not
fetch. The missing key never reaches SERVERS, so server["<col>"] throws KeyError
before the first log line, the bash wrapper swallows it via || discord_error, and
every 2-min cycle silently does nothing.
Seen 2026-06-25 (#1398): migration 040 added max_weight_prime/max_weight_night columns
but did not add them to the bash SELECT.
Prevention: whenever queue-dispatcher-loop.py starts reading a new
dev_r_server_capacity column, add it to the SELECT in queue-dispatcher.sh in the
same PR. Keep the loop’s first log() line at the very top of main() (before any
server[...] access) so a crash is visible in the log, not silent.
Fix:
# Confirm current SELECT has all needed columns
grep 'max_weight_prime' /opt/p24-infra/scripts/queue-dispatcher.sh
# If missing, pull latest dev which includes the fix
cd /opt/p24-infra && git pull --ff-only2. Weight mismatch (server max_weight too low for queued jobs)
All queued jobs are heavy but dev_r_server_capacity.max_weight_prime = 'light'.
The dispatcher auto-heals this since 2026-06-25 — it bumps max_weight_prime to match
the heaviest queued weight and posts a Discord warning. No human action needed unless
auto-heal itself fails.
Confirm auto-heal ran:
grep 'AUTO-HEAL' /var/log/p24-infra-dispatcher.log | tail -5Manual fix if auto-heal failed:
UPDATE dev_r_server_capacity
SET max_weight_prime = 'heavy'
WHERE server_label = 'bms-4';Escalation
If the lease is held but the loop still won’t dispatch after column + weight fixes:
- Check
spawn-worker.shexit codes in[loop]lines (spawn failed exit=N) - Check bms-4 RAM:
ssh root@54.36.123.110 "free -g" - MongoDB/quorum/credential items remain human-gated
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="restart",
resource="meta-dispatcher",
result="success", # "success" | "failed" | "skipped"
detail="Dispatcher KeyError stall cleared — stale job fixed and dispatcher restarted",
env="bms-4",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'restart', 'meta-dispatcher', 'success', 'Dispatcher KeyError stall cleared — stale job fixed and dispatcher restarted', 'bms-4')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''