Skip to content
Merged
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
33 changes: 30 additions & 3 deletions apps/decodex/src/manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ fn validate_landing_state(
) {
eyre::bail!("Pull request `{pr_url}` has failed required checks that need repair.");
}

if let Some(other) = gate_view.status_check_rollup_state
&& pull_request::checks_require_wait(Some(other))
{
eyre::bail!(
"Pull request `{pr_url}` is still waiting on checks: statusCheckRollup=`{other}`."
);
}

if pull_request::mergeability_unknown(gate_view) {
eyre::bail!(
"Pull request `{pr_url}` mergeability is still unknown after retry; wait for GitHub to recompute mergeability and retry `decodex land`."
Expand All @@ -825,9 +834,6 @@ fn validate_landing_state(
}

match gate_view.status_check_rollup_state {
Some(other) if pull_request::checks_require_wait(Some(other)) => eyre::bail!(
"Pull request `{pr_url}` is still waiting on checks: statusCheckRollup=`{other}`."
),
Some("SUCCESS") | None => {
debug_assert!(pull_request::manual_landing_gates_satisfied(gate_view));

Expand Down Expand Up @@ -2109,6 +2115,27 @@ exit 1\n",
assert!(error.to_string().contains("retry `decodex land`"));
}

#[test]
fn landing_state_validation_treats_pending_checks_as_wait_even_when_merge_blocked() {
let mut landing_state = sample_landing_state();

landing_state.base_ref_name = String::from("main");
landing_state.merge_state_status = String::from("BLOCKED");
landing_state.status_check_rollup_state = Some(String::from("PENDING"));

let error = manual::validate_landing_state(
&landing_state,
"https://github.com/hack-ink/decodex/pull/64",
"main",
"XY-225",
"deadbeef",
)
.expect_err("pending checks should wait rather than report a generic blocked merge state");

assert!(error.to_string().contains("still waiting on checks"));
assert!(error.to_string().contains("statusCheckRollup=`PENDING`"));
}

#[test]
fn execute_land_merge_uses_admin_merge() {
let temp_dir = TempDir::new().expect("temp dir should create");
Expand Down
7 changes: 5 additions & 2 deletions plugins/decodex/skills/land/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ with GitHub UI, `gh pr merge`, merge queue actions, raw `git`, or direct API mut

## Sequence

1. Confirm the PR exists, the intended base and head are the ones being landed, and the
repository expects Decodex-owned landing.
1. Confirm the PR exists, the intended base and head are the ones being landed, required
checks are green, and the repository expects Decodex-owned landing.
2. Run `decodex land "<summary>"`.
3. For a deliberate non-issue lane, run
`decodex land --manual-authority --pr <URL> "<summary>"`.
Expand All @@ -52,6 +52,9 @@ but intentionally skips tracker closeout and active-label ownership checks.
## Fail-Closed Rules

- If `decodex land` is required, it must run and succeed.
- If `decodex land` reports that checks are still pending or expected, treat that as a
wait condition: keep the tracker issue in its retained review state, keep the active
ownership label in place, wait for CI, and retry `decodex land`.
- Do not substitute `gh pr merge`, GitHub UI, merge queue, raw `git`, direct GitHub API
mutation, or a hand-assembled merge for a failed or unavailable `decodex land`.
- If GitHub merge already happened but `decodex land` stopped during closeout or
Expand Down