priorities register status sync — Operations Runbook

Service: nightly-devops-triage Phase 3.5 (.claude/commands/nightly-devops-triage.md) Resolution engine: scripts/priorities.py (list-candidates, resolve --check-refs) + the shared safety gate in scripts/priorities_sync.py (default_gh_lookup / is_reference_resolved) Host: bms-4, via scripts/run-nightly-triage.sh cron (20:00 UTC daily) Introduced: issue #4559, plan docs/plans/plan-4559-priorities-status-sync.md Rewired for the DB cutover: issue #5391 (phase 2 of #5364) — see history below


History — why this looks different from the original #4559 design

Phase 3.5 originally scanned the git-committed docs/priorities.md for well-formed 3-column table rows, checked each linked issue/PR via gh, and rewrote the file through a throwaway clone + a minted GitHub App token + a dedicated branch + a PR + a diff-shape hard gate — all of that machinery existed only because the sync target was a git file. #5391 (phase 2 of #5364) moved the priorities register to the dev_r_priorities Supabase table (docs/priorities.md is now generated read-only output of scripts/priorities.py render), so Phase 3.5 now resolves rows with a direct DB call instead. The resolution safety gate itself did not change — same criteria, same fail-safe rule, reused via scripts/priorities_sync.py’s default_gh_lookup / is_reference_resolved rather than reimplemented.

What it does

  1. python3 scripts/priorities.py list-candidates — SELECTs every dev_r_priorities row with status != 'resolved' and a non-empty issue_refs.
  2. For each candidate id: python3 scripts/priorities.py resolve <id> --check-refs [--dry-run]. resolve --check-refs extracts every {kind, number} ref, checks its live state via gh, and applies the SAME criteria #4559 shipped:
    • a pr ref resolves only when state == "MERGED"
    • an issue ref resolves only when state == "CLOSED" and milestone.title == "Main" (closed-alone is never enough — issue #2416 in this repo was closed with milestone Review while its actual deliverable was never executed; that shape must stay untouched)
    • a non-gh-resolvable ref (kind other than issue/pr — a GitLab MR, a bare doc/prose ref) makes the whole row fail-safe skip, same as the old markdown parser’s mixed-link rule
    • any gh lookup failure fails the row safe (never treated as resolved)
  3. Confirmed-resolved rows get status='resolved', resolved_at=now() via a plain UPDATE — no git clone, no branch, no PR, no diff-shape gate. scripts/priorities.py render’s query already filters WHERE status != 'resolved', so a resolved row simply stops appearing the next time docs/priorities.md is regenerated.

Dry-run mode (default — read this before flipping it off)

Ships with $PRIORITIES_SYNC_DRYRUN = $true in the skill’s Constants block. In dry-run, Phase 3.5 still calls list-candidates and, per candidate, resolve <id> --check-refs --dry-run — the safety gate runs in full (every gh lookup happens for real) but --dry-run stops scripts/priorities.py before the final UPDATE, returning {"id": ..., "status": "would_resolve"} instead. Nothing in dev_r_priorities changes. Report lands in the log (/var/log/nightly-devops-triage.log) and as a Discord embed, one line per candidate:

[3.5] id 3f2a...  tier=P2  refs=#4018            -> WOULD_RESOLVE (closed, shipped to main)
[3.5] id 9c11...  tier=P0  refs=#2620,#2055      -> SKIP  (still open/not-shipped: issues#2620)
[3.5] summary: 40 candidates / 1 resolvable / 39 skipped / cap 5

0 resolvable is the expected healthy steady state — most nights nothing has shipped since the previous run.

Run a manual dry-run check (no cron wait)

export SUPABASE_ACCESS_TOKEN=...   # or ROLE_SECRET_MANAGER_SUPABASE_ACCESS_TOKEN — name only, never print
python3 scripts/priorities.py list-candidates
python3 scripts/priorities.py resolve <id> --check-refs --dry-run

Makes real gh calls and a real (read-only) Management API SELECT, but the --dry-run flag guarantees no UPDATE reaches dev_r_priorities.

Enabling writes

A separate, one-line PR flips $PRIORITIES_SYNC_DRYRUN to $false in the skill’s Constants block, after at least one real nightly dry-run has produced the expected report (see Verification below).

Verification before trusting a live run

  • python -m pytest scripts/tests/test_priorities.py scripts/tests/test_priorities_sync.py -v — covers the #2416 negative case, merged-PR/shipped-issue positive cases, the multi-ref AND rule, non-gh refs, resolve --check-refs/--dry-run behaviour, and the shared gate functions.
  • ruff check scripts/priorities.py scripts/priorities_sync.py scripts/tests/ — must be clean.
  • One real nightly dry-run reviewed in Discord/log before flipping $PRIORITIES_SYNC_DRYRUN.

Diagnosing a failed run

  • Prometheus alert NightlyTriagePhaseFailed{phase="priorities-status-sync"} fires on any failure path — check /var/log/nightly-devops-triage.log on bms-4 for the phase’s WARN/ERROR line and the Discord red embed for the same message.
  • Common causes:
    • SUPABASE_ACCESS_TOKEN / ROLE_SECRET_MANAGER_SUPABASE_ACCESS_TOKEN missing or stale in the bms-4 SOPS-deployed env — scripts/priorities.py exits 2 with “No Management API token in environment” (key name only, never a value). See docs/playbooks/supabase-management-api-sql-windows.md.
    • gh lookup failures for a candidate’s refs — the row fail-safe skips (not a phase failure); only a list-candidates/resolve process error (non-zero exit) fails the phase itself.
    • Constraint or schema drift on dev_r_priorities/dev_r_agent_sessions — check supabase/migrations/ for the latest applied migration and apply-supabase-migrations.yml’s run log.

Disabling in an emergency

Flip $PRIORITIES_SYNC_DRYRUN back to $true in .claude/commands/nightly-devops-triage.md (one-line PR, same as enabling). Safe at any time — dry-run still runs the full gate but writes nothing, so disabling never leaves a row half-resolved.

  • Issue #4559 (original design) · #5364 / #5391 (DB cutover)
  • docs/plans/plan-4559-priorities-status-sync.md · docs/plans/plan-5364-priorities-supabase-table.md
  • docs/playbooks/supabase-management-api-sql-windows.md — Management API access pattern
  • docs/eu-ai-act-compliance.md §4 — nightly-devops-triage risk registration.