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
python3 scripts/priorities.py list-candidates— SELECTs everydev_r_prioritiesrow withstatus != 'resolved'and a non-emptyissue_refs.- For each candidate id:
python3 scripts/priorities.py resolve <id> --check-refs [--dry-run].resolve --check-refsextracts every{kind, number}ref, checks its live state viagh, and applies the SAME criteria #4559 shipped:- a
prref resolves only whenstate == "MERGED" - an
issueref resolves only whenstate == "CLOSED"andmilestone.title == "Main"(closed-alone is never enough — issue #2416 in this repo was closed with milestoneReviewwhile its actual deliverable was never executed; that shape must stay untouched) - a non-
gh-resolvable ref (kindother thanissue/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
ghlookup failure fails the row safe (never treated as resolved)
- a
- Confirmed-resolved rows get
status='resolved', resolved_at=now()via a plainUPDATE— no git clone, no branch, no PR, no diff-shape gate.scripts/priorities.py render’s query already filtersWHERE status != 'resolved', so a resolved row simply stops appearing the next timedocs/priorities.mdis 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-runMakes 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-ghrefs,resolve --check-refs/--dry-runbehaviour, 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.logon 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_TOKENmissing or stale in the bms-4 SOPS-deployed env —scripts/priorities.pyexits 2 with “No Management API token in environment” (key name only, never a value). Seedocs/playbooks/supabase-management-api-sql-windows.md.ghlookup failures for a candidate’s refs — the row fail-safe skips (not a phase failure); only alist-candidates/resolveprocess error (non-zero exit) fails the phase itself.- Constraint or schema drift on
dev_r_priorities/dev_r_agent_sessions— checksupabase/migrations/for the latest applied migration andapply-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.
Related
- Issue #4559 (original design) · #5364 / #5391 (DB cutover)
docs/plans/plan-4559-priorities-status-sync.md·docs/plans/plan-5364-priorities-supabase-table.mddocs/playbooks/supabase-management-api-sql-windows.md— Management API access patterndocs/eu-ai-act-compliance.md§4 —nightly-devops-triagerisk registration.