Dispatch Pipeline Key Sync
When to use: CF Worker p24-meta-dispatcher returns HTTP 500 after a secrets-sync.yml run, or dispatch-to-queue.yml fails with auth errors after a SOPS credential rotation.
Problem
CF Worker Wrangler secrets are an isolated silo NOT covered by secrets-sync.yml.
Every time monitoring.env.sops changes, the CF Worker retains the old value until manually updated.
Keys that MUST stay in sync between SOPS and CF Worker Wrangler:
| Key | SOPS file | Wrangler secret name | CF Worker |
|---|---|---|---|
SUPABASE_SERVICE_KEY | secrets/monitoring.env.sops | SUPABASE_SERVICE_KEY | p24-meta-dispatcher |
QUEUE_API_KEY | secrets/monitoring.env.sops | QUEUE_API_KEY | p24-meta-dispatcher |
GH_APP_PRIVATE_KEY (used by dispatch-to-queue.yml) is stored as a GH Secret and is also NOT covered by secrets-sync.yml — see section below.
PRIORITIES_REPORTER_KEYS (used by the /priorities endpoint, #5573) is a sixth Wrangler
secret in the same isolated silo — see the dedicated section below.
Automation: PR #3082 adds secrets/monitoring.env.sops to the push.paths trigger of deploy-meta-dispatcher.yml, which already contains wrangler secret put steps for all 5 CF Worker secrets. After PR #3082 merges, this sync is automatic.
Symptoms
| Symptom | Likely cause |
|---|---|
dispatch-to-queue.yml fails with “A JSON web token could not be decoded” | GH_APP_PRIVATE_KEY GH Secret stale after SOPS rotation of GITHUB_APP_PRIVATE_KEY_B64 in n8n-bms4.env.sops |
dispatch-to-queue.yml fails with HTTP 401 from CF Worker | QUEUE_API_KEY GH Secret out of sync with CF Worker Wrangler secret |
| CF Worker returns HTTP 500 (“Invalid API key”) | SUPABASE_SERVICE_KEY in Wrangler is revoked or stale |
Confirmation
# 1. Check CF Worker health
$status = (Invoke-WebRequest -Uri "https://p24-meta-dispatcher.radieu.workers.dev/health" -Method GET -UseBasicParsing).StatusCode
Write-Host "CF Worker health: $status" # expect 200 (after PR #3094 merges)
# 2. Check recent dispatch-to-queue.yml runs
gh run list --repo radieu/p24-infra --workflow dispatch-to-queue.yml --limit 5
# 3. Check GH Secret last-updated time
gh secret list --repo radieu/p24-infra | Select-String "QUEUE_API_KEY|GH_APP_PRIVATE_KEY"Fix: Update CF Worker Wrangler secrets (delegate to secret-manager)
Spawn a secret-manager background agent per CLAUDE.md §Role Enforcement.
Agent(
description: "secret-manager — sync SUPABASE_SERVICE_KEY + QUEUE_API_KEY to CF Worker",
prompt: """
Read C:\\Users\\konar\\.claude\\agent-prompts\\roles\\secret-manager.md and adopt that persona.
Working directory: C:\\code_2026\\p24-infra
Task:
1. Extract SUPABASE_SERVICE_KEY from secrets/monitoring.env.sops (safe pattern — never print value)
2. Run: echo $val | npx wrangler secret put SUPABASE_SERVICE_KEY --name p24-meta-dispatcher
3. Verify: GET https://p24-meta-dispatcher.radieu.workers.dev/health returns 200
4. Repeat for QUEUE_API_KEY if also out of sync
IMPORTANT: do not display any secret values in your response.
"""
)
Fix: Update GH_APP_PRIVATE_KEY GH Secret (after n8n-bms4.env.sops rotation)
When GITHUB_APP_PRIVATE_KEY_B64 is rotated in n8n-bms4.env.sops, delegate to secret-manager:
Task: Extract GITHUB_APP_PRIVATE_KEY_B64 from secrets/n8n-bms4.env.sops,
base64-decode to PEM, set as GH Secret:
gh secret set GH_APP_PRIVATE_KEY --repo radieu/p24-infra --body $pem
Fix: Update QUEUE_API_KEY GH Secret (after monitoring.env.sops rotation)
Task: Extract QUEUE_API_KEY from secrets/monitoring.env.sops,
set as GH Secret: gh secret set QUEUE_API_KEY --repo radieu/p24-infra --body $val
PRIORITIES_REPORTER_KEYS — the /priorities endpoint’s per-repo keys (#5573)
The scoped priorities-register endpoint (POST /priorities/entry, POST /priorities/log on
p24-meta-dispatcher) lets the 15-20 parallel coding agents write dev_r_priorities /
dev_r_agent_sessions without each holding the high-privilege SUPABASE_ACCESS_TOKEN
Management API PAT that scripts/priorities.py uses. Its auth is a per-repo reporter key —
deliberately NOT the shared QUEUE_API_KEY.
| Key | Wrangler secret name | CF Worker | Shape |
|---|---|---|---|
PRIORITIES_REPORTER_KEYS | PRIORITIES_REPORTER_KEYS | p24-meta-dispatcher | JSON map {"<owner>/<repo>":"<key>", …} |
How the auth works: the Worker parses the JSON map once per request and matches the presented
Authorization: Bearer <key> (constant-time) against the map’s values. The reporting repo is
derived from which key matched — never from the request body — so a session cannot spoof its
origin. When the secret is unbound or malformed, the endpoint fails closed (401).
Why a JSON map (Option A), not N separate secrets (Option B): adding a new repo is a
secret-only change (edit the one map value + wrangler secret put) with no code edit or redeploy.
Per-repo revoke/rotate works either way — drop or replace one entry in the map.
Adding / rotating a repo’s reporter key (delegate to secret-manager)
This is a credential operation — a dev-coder / sys-admin session must NOT do it inline.
Spawn a secret-manager agent (per CLAUDE.md §Role Enforcement):
Task (secret-manager):
1. Generate a fresh random key for <owner>/<repo> (e.g. `openssl rand -hex 32`) — never print it.
2. Store it in that repo's own secrets/*.env.sops as PRIORITIES_REPORTER_KEY (same delivery
mechanism as QUEUE_API_KEY today — the consuming repo reads it from its env and sends it as the
Bearer token). It is a DIFFERENT value from QUEUE_API_KEY.
3. Merge it into the p24-meta-dispatcher PRIORITIES_REPORTER_KEYS JSON map and push the whole map:
echo '<json-map>' | npx wrangler secret put PRIORITIES_REPORTER_KEYS --name p24-meta-dispatcher
(read the existing map first, add/replace the single `"<owner>/<repo>": "<key>"` entry, keep the
others intact — this is a full-value overwrite, not an append).
4. Verify: a POST /priorities/log with the new key returns 200; a wrong key returns 401.
IMPORTANT: do not display any secret values in your response.
Deploy-workflow wiring (done — #5628):
deploy-meta-dispatcher.ymlnow has a “Bind PRIORITIES_REPORTER_KEYS (aggregate per-repo map)” step that re-pushes this secret on every deploy, so a redeploy no longer risks leaving it clobbered. Because the map has no single SOPS home, the step reconstructs it at deploy time: it decrypts each consumer repo’s own SOPS file (secrets/{et-operational-platform,brandpilot,art-agency,whatsup}.env.sops), reads that repo’sPRIORITIES_REPORTER_KEY, and assembles the{ "<owner>/<repo>": "<key>" }JSON map from an explicit label→file table in the workflow (the label must match #5578’s bound values exactly — e.g.radieu/Art-Agencyis capitalised unlike its lowercase filename). It fails closed: a missing/empty key aborts the deploy rather than pushing a partial map. The four consumer SOPS paths are on the workflow’spush.pathstrigger, so a reporter-key rotation auto-re-pushes the map. Onboarding a new repo to the map is still asecret-managerop (mint key + write its SOPS), plus one new line in that workflow table.
Prevention
PR #3082 addresses this: any push to main touching monitoring.env.sops automatically re-runs the full wrangler secret put sequence via deploy-meta-dispatcher.yml.
Escalation
If CF Worker returns 500 after sync: check wrangler tail --name p24-meta-dispatcher for error details, verify Supabase JWT is valid, or create a [SECRET-REQUEST] issue for a fresh service_role key.
Known outage history
| Date | Duration | Root cause | Fix |
|---|---|---|---|
| 2026-07-06 | ~4h (16:15–20:45 UTC) | SUPABASE_SERVICE_KEY in CF Worker was a revoked sb_secret_* Named API Key; GH_APP_PRIVATE_KEY stale after PR #3017 | Replaced via wrangler + updated GH Secret (PR #3077) |
| Session 37 | ~1h | QUEUE_API_KEY in CF Worker out of sync after SOPS rotation | Manual wrangler secret put |
| Session 44 | ~2h | SUPABASE_SERVICE_KEY in SOPS had 119-char corrupted value (Windows encoding) | PR #2961 fixed SOPS; PR #2956 fixed CF Worker |