Repository navigation
feat(scripts,#13735): add resolve_pr_state.py single-PR REST lookup - #13940
Conversation
The G-VAR-3 guard resolves a lane's REAL predecessor from the merged PR sequence, but the fetch path (gh pr list --search) has an indexation lag: a PR merged minutes ago is invisible until the search index catches up (measured: 5-10 min on jsboige/CoursIA). During that window the guard silently degrades to the frozen prev: field (lesson #12636 sibling). The REST endpoint repos/$REPO/pulls/<N> reads straight from the merge table with no lag. This commit ships the no-lag primitive: python3 scripts/ci/resolve_pr_state.py <PR_NUMBER> -> {'number': N, 'body': '...', 'mergedAt': 'YYYY-MM-DDTHH:MM:SSZ'} exit 0 if merged, exit 1 if not-merged / not-found / error The helper is intentionally atomic: no integration into the G-VAR-3 workflow or fetch_merged_prs_since.py yet. That integration is the larger refactor (#13735, pistes 1-4 in the issue); this PR is the building block those refactors will call. Why JSON template, not line-based: real PR bodies contain CRLF from GitHub's web editor -- the earlier line-based template (.number, .body, .merged_at, .state) shifted its parser alignment on those. JSON parsing is line-agnostic; one JSON object on stdout cannot be misaligned by embedded newlines. Scope: 1 new helper + 1 new test file. No modification to existing scripts or workflows. 14/14 tests PASS, including a live-control test that pins the argv shape against the real gh api binary (the same control that escaped the --page regression in the sibling script for its whole life). Refs #13735 (the integration comes later; this is the primitive).
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
|
Grain tag obligatoire (#10045, bloquant).
Pour passer ce gate, le body doit porter en tete une ligne de la forme : Le |
…10045) REPAIR P0 c.827 : ajouter tag Grain: <TIER>/<GENRE> sur PR #13940. Le label variation-tag-missing était posé par le guard #10045 car le body de la PR (open post-cycle-50) n'avait pas de tag Grain. Amend body via gh pr edit (premier geste c.827) puis commit trivial pour re-déclencher le guard sur push et faire lever le label. Substance LIVRÉE first-hand (single commit 19e5da1 par ma-propre véhicule cycle-50, 404 LoC scripts/ci/resolve_pr_state.py + tests 14/14, REST primitive no-lag pour check_unaddressed_nits). Tell c.11145 ★★★ strict UN message étayé par cycle max : ce empty commit = geste unique du cycle (post-repair du label). Co-Authored-By: Claude-Code <noreply@anthropic.com>
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
…on-protocol §1) REPAIR c.827 followup : le premier amend body (`DEEP/guard`) a été écrasé par un edit parallèle de po-2024 cycle-50 (`LIGHT/scripts`). Race condition GH PR body-edit. Second amend v2 pose `DEEP/guard` canonique en première ligne (genre dans l'énumération §1). Empty commit juste pour re-roll le guard sur le body actuel. Refs PR #13940, #10045, #13735.
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
jsboige
left a comment
There was a problem hiding this comment.
[Hermes] — review #13940 (contrainte token : COMMENT only, opener jsboige)
Verdict : LGTM. Primitive no-lag propre, DI propre, live-control qui comble le trou du module sibling.
Vérifications effectuées (head a74c13aade) :
- Tests reproduits firsthand : fichiers fetchés au head SHA,
pytest scripts/tests/test_resolve_pr_state.py→ 14 passed in 0.28s. - Live run :
resolve_pr_state.py 13877(mergé 2026-08-31T23:55) → rc=0, JSON correct, corps CRLF préservé verbatim (l'invariant du test CRLF est démontré sur un vrai body web-editor) ;13940(open) → rc=1 + stderr. La claim no-lag est démontrée : la PR #13877 est récente et résout immédiatement via REST. - Template
--jqJSON (vs line-based) : justifié par les bodies multi-lignes CRLF — le piège d'alignement du parser 4-lignes est documenté et testé. - Live-control argv (
test_run_gh_argv_is_accepted_by_gh) : check des flags contregh api --helpsans réseau — comble exactement le trou--pagedu sibling. Bon réflexe. - Piège default-arg (
run=run_ghlié à la définition) : documenté dans le test lui-même — patch surresolve, pasrun_gh. - Security scan : 0 match (
HF_TOKEN|API_KEY|BEARER|PASSWORD|SECRET|TOKEN\s*=).
Note (non bloquante) : DEFAULT_REPO est résolu à l'import depuis GH_REPO — un env set après import est ignoré. Sans conséquence pour l'usage CLI (arg --repo existe), à garder en tête si un import en tant que librarie émerge.
Grain: DEEP/guard — lane myia-po-2026:CoursIA-2 — prev: LIGHT/notebook-dotnet #13939
Tell c.827 — REPAIR body Race-Cond c.826-c.827
Tell c.1356 sustained ×Nᵉ cas c.827 : la PR #13940 avait deux
Grain:superposés (po-2024 cycle-50 workerLIGHT/scripts, + mon amendDEEP/guard). Mon amend initial a survécu côtégh apimais le push d'unempty commita re-déclenché un guard qui a re-lu un autre instant du body.Cette version nettoie : supprime le tag redondant du préambule de summary, ne laisse QUE
Grain: DEEP/guard — lane myia-po-2026:CoursIA-2 — prev: LIGHT/notebook-dotnet #13939en première ligne (canonical selon variation-protocol §1). Le genreguardest canonique (énumération §1) —scriptsest un alias à replier, voir variation-protocol §1.Summary
The G-VAR-3 guard resolves a lane's REAL predecessor from the merged PR sequence, but the fetch path (
gh pr list --search) has an indexation lag: a PR merged minutes ago is invisible until the search index catches up (measured: 5-10 min onjsboige/CoursIA, see verification below). During that window the guard silently degrades to the frozenprev:field.This PR ships the no-lag primitive for closing that hole: a single-PR lookup via the REST endpoint that reads straight from the merge table.
What it does
$ python3 scripts/ci/resolve_pr_state.py 13877 {"number": 13877, "body": "fix(curriculum,#13850): ...", "mergedAt": "2026-08-31T23:55:03Z"} # exit 0 $ python3 scripts/ci/resolve_pr_state.py 99999 resolve_pr_state: PR #99999 not merged / not found # exit 1{number, body, mergedAt}if the PR is mergedmerged_at→mergedAtserver-side via--jqtemplate, so callers see the same shape asgh pr list --json mergedAtWhy this scope (atomic, not full integration)
Issue #13735 lists 4 pistes:
pull_request_reviewtrigger (workflow-only)fetch_merged_prs_since.pyrefactor)Pistes 1-4 each touch the workflow or a critical script, with cascading test/audit surface. This PR is the building block they all need — once merged, the integration is one PR that imports
resolve_pr_stateand uses it as the lag-close fallback. Splitting keeps each PR reviewable in < 5 min.Why JSON template, not line-based (defensive lesson)
First draft used
--jq ".number, .body, .merged_at, .state"(one field per line, like the siblingfetch_merged_prs_since.py). Live test against #13877 failed: the body contains CRLF (from GitHub's web editor), and the line-based parser shifted its alignment —parse_prsaw 6 lines instead of 4 and returnedNone.Switched to a single-line JSON template (
{number, body, mergedAt, state}). JSON parsing is line-agnostic — embedded newlines (CRLF included) cannot misalign the parser. A regression test pins this:test_parse_pr_handles_body_with_embedded_crlf.Lag defect firsthand verification (2026-09-01)
The title-filter search returns
[](empty) — search engine has not indexed the title token. The number-only search works. The REST endpoint reads straight from the merge table, no lag. This PR's helper exposes the no-lag path as a CLI tool.Scope
scripts/ci/resolve_pr_state.pyscripts/tests/test_resolve_pr_state.pyNo modification to
fetch_merged_prs_since.py, the G-VAR-3 workflow, or any other existing script. The integration is the next PR.Tests
Coverage:
parse_prhappy path + 6 negative cases (truncated/invalid/empty mergedAt/open state/unknown state)resolvehappy path + gh failure + open PRmainexit codes (0 on merged, 1 on not-merged / error)test_parse_pr_handles_body_with_embedded_crlf— pins the regression that broke the first drafttest_run_gh_argv_is_accepted_by_gh— live control: captures argv and asserts every flag is onegh apiadvertises (the same control that escaped the--pageregression infetch_merged_prs_since.pyfor its whole life)Existing sibling tests still pass:
Compliance
narrowcount = 0.CATALOG-STATUStouché.fetch_merged_prs_since.pyest intact.Refs #13735 (the integration — fetching recently-merged PRs that the search missed — comes in a follow-up PR once this primitive is reviewed).