Skip to content

Detect stale ready PR head drift#693

Merged
sungjunlee merged 3 commits into
mainfrom
issue-691-ready-head-drift
Jun 9, 2026
Merged

Detect stale ready PR head drift#693
sungjunlee merged 3 commits into
mainfrom
issue-691-ready-head-drift

Conversation

@sungjunlee

@sungjunlee sungjunlee commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Closes #691.

Summary

  • Add review preflight ready_status for ready_to_merge runs by comparing GitHub's live PR headRefOid with review.last_reviewed_sha / git.head_sha.
  • Add guarded recover-state support for ready_to_merge -> review_pending only when live PR HEAD drift is objective evidence; the recovery updates git.head_sha and emits state_recovery with pr_number, previous_head_sha, new_head_sha, last_reviewed_sha, and the operator reason.
  • Add a minimal finalize-time stacked-base guard for non-default PR bases, with --allow-stacked-base-hazard <reason> as the explicit override.

Validation

  • node --test tests/relay/scripts/run-preflight.test.js tests/relay-dispatch/scripts/recover-state.test.js tests/relay-dispatch/scripts/relay-events.test.js tests/relay-dispatch/scripts/cli-schema.test.js tests/relay-dispatch/scripts/cli-unknown-flag-wiring.test.js
  • node --test tests/relay-review/scripts/review-runner*.test.js
  • node --test tests/relay-merge/scripts/gate-check.test.js tests/relay-merge/scripts/finalize-run.test.js
  • git diff --check
  • git diff --cached --check

Deferred

  • No semantic reviewer heuristics or PR dependency graph subsystem. The stacked-base check intentionally stays one-level and mechanical: if the PR base is not the default branch, it looks only for an associated PR whose head is that base branch.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • PR HEAD 변경 감지 시 자동 복구 메커니즘 추가
    • 병합 시 스태킹된 PR 베이스 위험 탐지 및 차단 기능
    • Ready-to-merge 상태에서 실시간 PR HEAD 추적 및 상태 리포팅 강화
  • 문서

    • 상태 복구 가이드 및 전이 조건 명확화
    • 병합 전 안전 검사 플로우 설명 추가

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sungjunlee, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 53 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e449590a-2f07-4825-bbf7-2aee69a9eb92

📥 Commits

Reviewing files that changed from the base of the PR and between fe039e7 and d1e044d.

📒 Files selected for processing (5)
  • skills/relay-dispatch/scripts/recover-state.js
  • skills/relay-merge/scripts/finalize-run.js
  • skills/relay/SKILL.md
  • tests/relay-dispatch/scripts/recover-state.test.js
  • tests/relay-merge/scripts/finalize-run.test.js

워크스루

이 PR은 ready_to_merge 상태 이후 PR HEAD가 진전될 때를 감지하고 감사 가능한 상태 복구를 제공하며, 스택된 PR 베이스 병합 순서 해저드를 경고하는 기능을 구현합니다. recover-state.js에 객관적 드리프트 증거가 있을 때만 review_pending으로 되돌리는 새로운 전이 경로를 추가하고, run-preflight.js는 라이브 PR HEAD를 조회해 ready_status를 계산하며, finalize-run.js는 스택된 베이스 위험을 탐지하고 선택적으로 차단합니다.

변경 사항

Ready-to-Merge Head Drift Detection and Recovery

Layer / File(s) Summary
Ready-to-merge drift detection and recovery core
skills/relay-dispatch/scripts/recover-state.js, tests/relay-dispatch/scripts/recover-state.test.js, tests/relay-review/scripts/review-runner.test.js
recover-state.js에 READY_TO_MERGE → REVIEW_PENDING 전이를 추가하고, gh pr view로 라이브 PR HEAD를 조회한 뒤 review.last_reviewed_sha/git.head_sha와 비교해 드리프트 증거를 검증합니다. 드리프트 없으면 복구를 거부하고, 있으면 매니페스트를 업데이트하고 previous_head_sha/new_head_sha/pr_number를 포함한 STATE_RECOVERY 이벤트를 기록합니다. recover-state 및 review-runner 테스트는 드리프트 감지 시 정상 전이, 드리프트 부재 시 거부, 이벤트 필드 검증을 확인합니다.
Live PR head detection and ready status computation
skills/relay/scripts/run-preflight.js, tests/relay/scripts/run-preflight.test.js
run-preflight.js의 review 단계에 fetchLivePrHead 함수를 추가해 gh pr view로 라이브 headRefOid를 조회하고, buildReadyStatus로 manifest/reviewed SHA 대비 라이브 HEAD를 비교해 merge_ready/stale_ready/not_ready 상태를 계산합니다. stale_ready 판정 시 next_action을 recover_ready_to_review_pending_then_rerun_review로 설정합니다. RELAY_GH_BIN 환경변수로 gh 바이너리 경로를 제어할 수 있도록 변경했습니다. run-preflight 테스트는 라이브 HEAD 모킹으로 merge_ready와 stale_ready 케이스를 검증합니다.
Stacked PR base hazard detection and handling
skills/relay-merge/scripts/finalize-run.js, tests/relay-merge/scripts/finalize-run.test.js
finalize-run.js에서 fetchPreMergeContext에 baseRefName을 추가하고, fetchDefaultBranchName/fetchPrsForHeadBranch/buildStackedBaseGuard/assertStackedBaseGuard로 스택된 베이스를 감지합니다. 베이스 브랜치가 기본값이 아니고 대상 베이스 PR이 미병합이면 blocked 상태로 판정합니다. --allow-stacked-base-hazard 플래그 없으면 MERGE_BLOCKED 이벤트를 기록하고 예외를 발생시킵니다. finalize-run 테스트는 베이스 PR CLOSED/미존재 시 차단, 플래그로 오버라이드, --skip-review 우회 불가를 검증합니다.
CLI schema and operational documentation
skills/relay-dispatch/scripts/cli-schema.js, skills/relay-dispatch/references/recovery-playbook.md, skills/relay/references/preflight-guards.md
cli-schema.js에 --allow-stacked-base-hazard 플래그를 finalize-run 명령에 추가합니다. recovery-playbook.md는 PR HEAD 변경 후 ready_to_merge → review_pending 복구 예시 명령, 허용 조건(라이브 PR HEAD와 SHA 불일치), 이벤트 필드(previous_head_sha/new_head_sha/pr_number)를 문서화합니다. preflight-guards.md는 ready_to_merge 시 라이브 PR headRefOid 조회, stale_ready와 merge_ready 분기, ready_status JSON 필드를 설명합니다.

🎯 4 (복합도) | ⏱️ ~45분

🐰 Drift를 감지한 토끼가 말했네:
PR이 헤드를 자주 밀쳐낸다네,
라이브 조회하고 비교해,
드리프트 증거가 있을 때만,
감사 이벤트로 돌아가네~ 🔄

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 요약하고 있습니다. 'ready_to_merge' 상태에서 PR HEAD 드리프트를 감지하는 기능이 PR의 핵심이며, 제목이 이를 직접적으로 반영하고 있습니다.
Linked Issues check ✅ Passed PR이 #691의 모든 주요 코딩 요구사항을 충족합니다: (1) 라이브 PR HEAD 드리프트 감지 및 ready_status 추가 [run-preflight.js], (2) ready_to_merge -> review_pending 복구 경로 구현 [recover-state.js], (3) 감사 이벤트 기록(state_recovery with pr_number, head SHA들) [recover-state.js, 테스트], (4) 스태킹된 PR 경고 추가 [finalize-run.js], (5) 회귀 테스트 포함 [모든 테스트 파일].
Out of Scope Changes check ✅ Passed 모든 변경사항이 #691의 범위 내입니다. 추가된 기능들(ready_status 감지, recover-state 전이, stacked-base 경고, 관련 테스트)은 모두 요구된 목표를 직접 지원하며, 의미론적 리뷰 휴리스틱이나 완전한 의존성 그래프 시스템은 의도적으로 제외되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-691-ready-head-drift

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sungjunlee

Copy link
Copy Markdown
Owner Author

Relay Review Round 1

Verdict: CHANGES_REQUESTED
Summary: 계약 검토에서 2개 차단 이슈를 찾았습니다. VERIFIED: guarded ready_to_merge recovery, structured state_recovery event, stacked-base block, listed regression test coverage, no semantic review heuristics or dependency graph. PARTIAL: stale_ready preflight result is produced but the relay caller does not consume it; audited recovery does not ensure the retained review checkout is actually at the live PR HEAD before review-runner runs.
Contract: FAIL
Quality Review: NOT_RUN
Quality Execution: PASS
Issues:

  • skills/relay/SKILL.md:95 — stale_ready preflight result is not integrated into the relay review flow: run-preflight now reports ready_status.status='stale_ready', but the main relay Step 4 snapshots REVIEW_BEFORE and then still proceeds to invoke relay-review without branching on ready_status. For an already ready_to_merge run with PR HEAD drift, review-runner rejects state != review_pending, so the new stale report is not actionable in the full relay workflow. Add a Step 4 branch before invoking relay-review: stale_ready should run audited recover-state -> review_pending and then review; merge_ready should avoid rerunning review.
  • skills/relay-dispatch/scripts/recover-state.js:459 — stale-ready recovery can leave review-runner on the old local checkout: recover-state accepts GitHub's live PR HEAD as drift evidence and writes git.head_sha to that SHA, but it does not verify or update the retained worktree/branch. review-runner later derives reviewHeadSha from git rev-parse HEAD in paths.worktree, so a remote-only PR advance can review/apply against the old checkout and even overwrite the manifest head back to the stale local SHA. The added test masks this by committing the new head locally before recovery.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe039e73d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +299 to +303
return {
prNumber: live.prNumber || prNumber,
headRefName: live.headRefName,
oldSha: differsFromReviewed ? lastReviewedSha : manifestHeadSha,
newSha: live.headSha,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the local checkout before recovering ready drift

When a ready_to_merge PR was advanced from another checkout, this recovery accepts the live GitHub headRefOid and stamps it into the manifest, but it never fetches or verifies that the retained worktree/branch is actually at that SHA. The next review-runner still reviews git rev-parse HEAD from the local checkout, so it can review the old code (or overwrite the manifest back to the old SHA), leaving the stale-ready recovery unable to produce a fresh review of the new PR head. Please fetch/update or refuse unless the local review checkout matches live.headSha.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
skills/relay-merge/scripts/finalize-run.js (1)

683-700: ⚖️ Poor tradeoff

중복 로직 추출 고려 (선택 사항)

세 경로(skip-review, READY_TO_MERGE, force-finalize)에서 거의 동일한 패턴이 반복됩니다:

  1. buildStackedBaseGuard 호출
  2. blocked 상태면 MERGE_BLOCKED 이벤트 기록
  3. assertStackedBaseGuard 호출

현재 코드는 각 경로의 컨텍스트(head_sha 소스 등)가 명시적으로 드러나 디버깅에 유리합니다. 향후 가드 로직이 복잡해지면 헬퍼 함수로 추출을 검토해 볼 수 있습니다.

Also applies to: 744-760, 764-780

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/relay-merge/scripts/finalize-run.js` around lines 683 - 700, The
repeated pattern calling buildStackedBaseGuard(...), conditionally appending a
MERGE_BLOCKED event via appendRunEvent(repoPath, safeData.run_id, { event:
EVENTS.MERGE_BLOCKED, ... }), and then calling assertStackedBaseGuard(...)
should be extracted into a small helper to reduce duplication; implement a
function (e.g., ensureStackedBaseGuard(repoPath, prNumber, headSha,
stackedBaseOverrideReason, safeData)) that calls buildStackedBaseGuard with the
provided args, appends the MERGE_BLOCKED event with the correct context (use
safeData.run_id, safeData.state, currentHeadSha / passed headSha,
safeData.review?.rounds), and finally calls assertStackedBaseGuard, then replace
the three duplicated blocks (including the one using preMerge, and the blocks
around lines cited) with calls to this helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/relay-dispatch/scripts/recover-state.test.js`:
- Around line 126-146: writeGhPrHeadScript currently only overrides PATH so an
external RELAY_GH_BIN in the parent environment can bypass the stub and make
recover-state.js tests flaky; update writeGhPrHeadScript to return an env
mapping that also sets RELAY_GH_BIN to the stub gh path (or explicitly unsets
it) so the test process always uses the created stub; reference the
writeGhPrHeadScript helper and the recover-state.js usage and ensure the
returned object includes RELAY_GH_BIN: ghPath (and still composes PATH) to
prevent inheritance from parent env.

In `@tests/relay/scripts/run-preflight.test.js`:
- Around line 76-77: The test fixture returns an env that only mutates PATH,
leaving RELAY_GH_BIN to inherit from the host and potentially bypass test stubs;
update the returned env object (the env property alongside repoRoot, runId,
manifestPath) to explicitly set RELAY_GH_BIN to the test stub inside binDir
(e.g., RELAY_GH_BIN: `${binDir}/gh`) or to an empty/controlled value so the test
cannot pick up an external binary, ensuring binDir is used instead of
process.env.RELAY_GH_BIN.

---

Nitpick comments:
In `@skills/relay-merge/scripts/finalize-run.js`:
- Around line 683-700: The repeated pattern calling buildStackedBaseGuard(...),
conditionally appending a MERGE_BLOCKED event via appendRunEvent(repoPath,
safeData.run_id, { event: EVENTS.MERGE_BLOCKED, ... }), and then calling
assertStackedBaseGuard(...) should be extracted into a small helper to reduce
duplication; implement a function (e.g., ensureStackedBaseGuard(repoPath,
prNumber, headSha, stackedBaseOverrideReason, safeData)) that calls
buildStackedBaseGuard with the provided args, appends the MERGE_BLOCKED event
with the correct context (use safeData.run_id, safeData.state, currentHeadSha /
passed headSha, safeData.review?.rounds), and finally calls
assertStackedBaseGuard, then replace the three duplicated blocks (including the
one using preMerge, and the blocks around lines cited) with calls to this
helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2bce686-23f4-47bf-bcd0-8d542cc054d2

📥 Commits

Reviewing files that changed from the base of the PR and between 26e5511 and fe039e7.

📒 Files selected for processing (10)
  • skills/relay-dispatch/references/recovery-playbook.md
  • skills/relay-dispatch/scripts/cli-schema.js
  • skills/relay-dispatch/scripts/recover-state.js
  • skills/relay-merge/scripts/finalize-run.js
  • skills/relay/references/preflight-guards.md
  • skills/relay/scripts/run-preflight.js
  • tests/relay-dispatch/scripts/recover-state.test.js
  • tests/relay-merge/scripts/finalize-run.test.js
  • tests/relay-review/scripts/review-runner.test.js
  • tests/relay/scripts/run-preflight.test.js

Comment on lines +126 to +146
function writeGhPrHeadScript(headRefOid, { number = 334, headRefName = "issue-211" } = {}) {
const binDir = fs.mkdtempSync(path.join(os.tmpdir(), "relay-gh-head-"));
const ghPath = path.join(binDir, "gh");
fs.writeFileSync(ghPath, `#!/usr/bin/env node
const args = process.argv.slice(2);
if (args[0] === "pr" && args[1] === "view") {
process.stdout.write(JSON.stringify({
number: ${JSON.stringify(number)},
headRefName: ${JSON.stringify(headRefName)},
headRefOid: ${JSON.stringify(headRefOid)}
}));
process.exit(0);
}
console.error("unexpected gh args: " + args.join(" "));
process.exit(2);
`, "utf-8");
fs.chmodSync(ghPath, 0o755);
return {
PATH: `${binDir}${path.delimiter}${process.env.PATH || ""}`,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

GH 스텁이 RELAY_GH_BIN 상속값에 의해 우회될 수 있습니다.

현재 helper는 PATH만 덮어써서, 상위 환경에 RELAY_GH_BIN이 있으면 recover-state.js가 해당 바이너리를 우선 사용해 테스트가 비결정적으로 깨질 수 있습니다. 테스트 env에 RELAY_GH_BIN을 명시해 고정해 주세요.

패치 제안
 function writeGhPrHeadScript(headRefOid, { number = 334, headRefName = "issue-211" } = {}) {
@@
   return {
     PATH: `${binDir}${path.delimiter}${process.env.PATH || ""}`,
+    RELAY_GH_BIN: ghPath,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/relay-dispatch/scripts/recover-state.test.js` around lines 126 - 146,
writeGhPrHeadScript currently only overrides PATH so an external RELAY_GH_BIN in
the parent environment can bypass the stub and make recover-state.js tests
flaky; update writeGhPrHeadScript to return an env mapping that also sets
RELAY_GH_BIN to the stub gh path (or explicitly unsets it) so the test process
always uses the created stub; reference the writeGhPrHeadScript helper and the
recover-state.js usage and ensure the returned object includes RELAY_GH_BIN:
ghPath (and still composes PATH) to prevent inheritance from parent env.

Comment on lines +76 to +77
return { repoRoot, runId, manifestPath, env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` } };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

테스트 환경에서 RELAY_GH_BIN을 고정하지 않아 외부 설정에 취약합니다.

run-preflight.jsRELAY_GH_BIN을 PATH보다 우선 사용합니다. 현재 fixture env는 PATH만 바꿔서, CI/로컬의 기존 RELAY_GH_BIN 값에 따라 테스트가 스텁을 우회할 수 있습니다.

패치 제안
-  return { repoRoot, runId, manifestPath, env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` } };
+  return {
+    repoRoot,
+    runId,
+    manifestPath,
+    env: {
+      ...process.env,
+      PATH: `${binDir}:${process.env.PATH}`,
+      RELAY_GH_BIN: ghPath,
+    },
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/relay/scripts/run-preflight.test.js` around lines 76 - 77, The test
fixture returns an env that only mutates PATH, leaving RELAY_GH_BIN to inherit
from the host and potentially bypass test stubs; update the returned env object
(the env property alongside repoRoot, runId, manifestPath) to explicitly set
RELAY_GH_BIN to the test stub inside binDir (e.g., RELAY_GH_BIN: `${binDir}/gh`)
or to an empty/controlled value so the test cannot pick up an external binary,
ensuring binDir is used instead of process.env.RELAY_GH_BIN.

@sungjunlee

Copy link
Copy Markdown
Owner Author

Relay Review Round 2

Verdict: CHANGES_REQUESTED
Summary: Done Criteria evidence: VERIFIED preflight stale_ready/merge_ready detection; VERIFIED guarded ready_to_merge -> review_pending recovery with objective PR HEAD drift; VERIFIED state_recovery event fields for stale-ready recovery; VERIFIED review-runner after audited recovery via added integration test; VERIFIED stacked-base blocking for non-default base hazards; VERIFIED regression tests added; VERIFIED no semantic review heuristics/readiness calibration/dependency graph subsystem. Blocking issue remains in the contract-tier rubric: the stacked-base override is explicit but not durably audited in the run event journal.
Contract: FAIL
Quality Review: NOT_RUN
Quality Execution: PASS
Issues:

  • skills/relay-merge/scripts/finalize-run.js:750 — Stacked-base override reason is not persisted in the audit trail: --allow-stacked-base-hazard <reason> makes buildStackedBaseGuard() return status: "overridden", but the merge paths only append an event when stackedBaseGuard.status === "blocked". The override reason is returned in the transient JSON result, while the durable merge_finalize event omits override_class, required_reason, pr_number, or any stacked-base override marker. That fails the rubric requirement that override behavior, if present, be auditable in the relay run record. Persist an override audit event, or include the override audit fields on the merge event using the existing override_class/required_reason event schema.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d36a217a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +250 to +251
const merged = candidates.find((entry) => normalizePrState(entry.state) === "MERGED" || Boolean(entry.mergedAt));
if (merged) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Block reused base branches with open PRs

When a stacked base branch name has been reused, gh pr list --head <base> --state all can return both an older merged PR and the current open/unmerged base PR. Because this clears the guard as soon as it finds any merged candidate, the later OPEN candidate is ignored and the dependent PR can be merged even though its current non-default base branch is still unmerged. Prefer blocking when any matching open PR exists, or otherwise ensure the merged PR corresponds to the current base branch head.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1e044dd0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +310 to +312
return {
status: overrideReason ? "overridden" : "blocked",
reason,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow legitimate non-default base branches

When a relay run is created from a long-lived non-default branch such as release/1.2 or develop, the PR base is intentionally not the repository default, but there may be no PR whose head branch is that base. In that case fetchPrsForHeadBranch() returns no candidates and this path marks the guard as blocked with base_pr_missing, so finalize-run refuses to merge an otherwise valid PR unless the operator uses an override. The guard should distinguish actual stacked bases from normal non-default target branches, for example by consulting the manifest/base policy or only blocking when an unmerged base PR is found.

Useful? React with 👍 / 👎.

@sungjunlee

Copy link
Copy Markdown
Owner Author

Relay Review

Verdict: LGTM
Summary: 검토 결과 PASS. Done Criteria 모두 VERIFIED: ready_to_merge preflight가 live PR HEAD drift를 stale_ready로 보고하고 unchanged는 merge_ready로 유지함; ready_to_merge -> review_pending recovery는 객관적 HEAD drift와 retained worktree HEAD 확인 뒤에만 허용됨; state_recovery 이벤트에 이전/목표 상태, PR 번호, 이전/신규 SHA, 사유가 남음; recovery 뒤 review-runner 재진입 테스트가 있음; finalize-run의 stacked-base guard가 block/override를 처리하고 override audit fields를 merge_finalize에 영구 기록함; 요구된 회귀 테스트가 추가됨; semantic review heuristic이나 dependency graph subsystem은 보이지 않음. Round 2의 audit 누락 이슈는 해결됨.
Contract: PASS
Quality Review: PASS
Quality Execution: PASS
Rounds: 3

@sungjunlee sungjunlee merged commit 3941087 into main Jun 9, 2026
2 checks passed
@sungjunlee sungjunlee deleted the issue-691-ready-head-drift branch June 9, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

relay-review: detect PR HEAD drift after ready_to_merge and recover review state

1 participant