Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1068,29 +1068,48 @@ jobs:
# Compare live refs on every sweep instead of waiting for an age
# threshold: previous-head checks are never useful merge evidence.
queue_hygiene_ready=true
if ! open_pr_heads_json="$(
open_pr_heads_json="{}"
if open_pr_refs_tsv="$(
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/${repo_full_name}/pulls?state=open&per_page=100" \
--paginate \
| jq -sc '
add
| map(
select(
.head.repo.full_name != null and
.head.ref != null and
.head.sha != null
)
| {
key: "\(.head.repo.full_name):\(.head.ref)",
value: .head.sha
}
)
| from_entries
| jq -sr '
add[]
| [
(if (.head.repo.full_name | type) == "string" then .head.repo.full_name else "" end),
(if (.head.ref | type) == "string" then .head.ref else "" end)
]
| @tsv
'
)"; then
while IFS=$'\t' read -r head_repo head_ref; do
if [ -z "$head_repo" ] || [ -z "$head_ref" ]; then
echo "::warning::Current-HEAD cancellation skipped for ${repo_full_name}: an open PR has a malformed head repository or ref. No run will be cancelled from incomplete evidence."
queue_hygiene_ready=false
break
fi
encoded_head_ref="$(jq -rn --arg value "$head_ref" '$value | split("/") | map(@uri) | join("/")')"
if ! live_head_sha="$(
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/${head_repo}/git/ref/heads/${encoded_head_ref}" \
--jq '.object.sha // empty'
)" || ! [[ "$live_head_sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::warning::Current-HEAD cancellation skipped for ${repo_full_name}: live ref ${head_repo}:${head_ref} could not be resolved safely. No run will be cancelled from incomplete evidence."
queue_hygiene_ready=false
break
fi
open_pr_heads_json="$(
jq \
--arg key "${head_repo}:${head_ref}" \
--arg value "$live_head_sha" \
'. + {($key): $value}' \
<<<"$open_pr_heads_json"
)"
Comment thread
seonghobae marked this conversation as resolved.
done <<<"$open_pr_refs_tsv"
Comment thread
seonghobae marked this conversation as resolved.
else
echo "::warning::Current-HEAD cancellation skipped for ${repo_full_name}: open PR head refs could not be read safely. No run will be cancelled from incomplete evidence."
open_pr_heads_json="{}"
queue_hygiene_ready=false
fi
Comment thread
seonghobae marked this conversation as resolved.
if ! current_default_sha="$(
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Resolve each open PR head through its live Git reference before queue hygiene
cancels superseded runs, and fail closed when any ref cannot be read. This
prevents a briefly stale pull-request payload from cancelling current-head
Checks without adding an arbitrary grace period.
- Route Strix cross-provider fallbacks to explicit direct-OpenAI models
(`openai-direct/...`) through the OpenAI inference endpoint instead of
inheriting a provider-specific primary base: the workflow now provisions
Expand Down
37 changes: 37 additions & 0 deletions docs/doctoring/queue-hygiene-live-ref-race.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Queue hygiene live-reference race

## Incident

On 2026-08-26, LineageWeave PR #667 received a new same-repository head
`37cc9ab1163f213105d420618e2e8ee69ec6673d`. Its new pull-request workflows
started, but the organization queue sweep cancelled them while GitHub's open-PR
payload still exposed the preceding head. The runs were current for the branch
ref and stale only in the pull-request listing used by the cancellation map.

This was a control-plane defect, not a test failure. Re-running the jobs without
repairing the comparison source would leave the same race available to every
repository in the organization.

## Decision

Queue hygiene still enumerates open pull requests to identify eligible head
repositories and branch names. Before cancelling anything, it now resolves each
head through GitHub's `Get a reference` endpoint and compares active runs with
that live Git reference. A missing, inaccessible, or malformed ref makes the
repository's cancellation pass unavailable; no run is cancelled from partial
evidence.

No time delay or grace-period heuristic is used. A branch ref is the exact
commit pointer the check run is meant to validate. The existing rule remains:
previous-head runs may be cancelled, current-head runs may not.

## Verification

- `uv run --group dev pytest -q tests/test_required_workflow_queue_contract.py`
- `actionlint .github/workflows/pr-review-merge-scheduler.yml`
- `git diff --check`

## Reference

GitHub. (n.d.). *REST API endpoints for Git references*. GitHub Docs. Retrieved
August 26, 2026, from https://docs.github.com/en/rest/git/refs
1 change: 1 addition & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ flowchart LR
| G-12 | CSAP/SOC 2 통제 목표와 PII masking 대안은 doctoring에 흩어져 있으며 evidence-to-control mapping의 live completeness가 미확인이다 | PII를 마스킹하면 업무가 멈추고, 원문 접근을 허용하면 감사·유출 위험이 커진다 | consent/purpose/access lease, field-level encryption/tokenization, redaction-at-egress, audit/revocation와 CSAP/SOC 2 evidence map을 구현한다 |
| G-13 | hourly scheduler는 존재하지만 no-op/credential unavailable/queued Checks의 customer next action을 모든 caller가 동일한 receipt로 내는지 미확인이다 | 자동화가 실패해도 운영자가 무엇을 고쳐야 하는지 알 수 없다 | `skipped_credential_unavailable` receipt와 다음 행동 문구를 exact-head Checks로 검증한 뒤 병합하고, bounded receipt schema, retry floor, single-flight, no secret fallback을 모든 caller contract test로 고정한다 |
| G-14 | release/changelog/version 증거가 각 PR에 분산되고 현재 central repo 보호 main의 release candidate가 명확하지 않다 | 운영자는 어떤 기능이 supportable release인지 확인할 수 없다 | merge 후 release readiness ledger, CHANGELOG, semantic version/tag, rollback/operability evidence를 함께 갱신한다 |
| G-15 | 조직 큐 정리기가 open-PR payload의 직전 head와 새 branch ref가 잠시 달랐던 LineageWeave #667의 current-head runs를 취소했다 | 정상 Checks가 코드 실패처럼 사라지고 모든 소비 저장소의 병합 루프가 반복 재실행에 갇힌다 | open PR의 repo/ref는 탐색에만 쓰고 취소 판정 SHA는 live Git reference에서 다시 읽는다. ref를 완전하게 읽지 못하면 해당 저장소에서는 아무 run도 취소하지 않는다 |

## 4. 열린 PR live inventory

Expand Down
7 changes: 7 additions & 0 deletions tests/test_required_workflow_queue_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,13 @@ def test_org_queue_sweep_covers_target_repositories_on_a_heartbeat() -> None:
assert "ORG_SWEEP_STALE_QUEUE_HOURS" in workflow
assert "/actions/runs?status=${active_status}&per_page=100" in workflow
assert "for active_status in queued in_progress" in workflow
assert '"/repos/${head_repo}/git/ref/heads/${encoded_head_ref}"' in workflow
assert '$value | split("/") | map(@uri) | join("/")' in workflow
assert "--jq '.object.sha // empty'" in workflow
assert 'open_pr_heads_json="$(\n jq' in workflow
assert "live ref ${head_repo}:${head_ref} could not be resolved safely" in workflow
assert "an open PR has a malformed head repository or ref" in workflow
assert "select(.head.repo.full_name != null and .head.ref != null)" not in workflow
assert '"pull_request" or .event == "pull_request_target"' in workflow
assert "$current_pr_head == null or .head_sha != $current_pr_head" in workflow
assert ".head_sha != $current_default_sha" in workflow
Expand Down
Loading