From ae7518e70e8f38ec8d12131af6f9ccd9d6dea2c2 Mon Sep 17 00:00:00 2001 From: lml2468 Date: Sun, 31 May 2026 20:01:54 +0800 Subject: [PATCH 1/3] fix: align 4 reusable workflows to design intent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit octo-pr-review-feed: - Remove 'opened' and 'synchronize' from ALLOWED_ACTIONS; only ready_for_review and review_requested match the design intent - Simplify message construction (no more synchronize branch) octo-pr-result-notify: - Remove pr_reopened from ALLOWED_KINDS (dead code, no caller sends it) - Add reviewer_display fallback: show (unknown) when reviewer is empty so review notification messages are never left with a blank field reusable-check-sprint: - Add isDraft to GraphQL query - Skip Sprint check for draft PRs (exit 0) — design intent is to block only 'formal' (non-draft) PRs reusable-pr-contributor-welcome: - Expand welcome comment: add Contributing Guide link, Discord invite, and CI-check reminder for first-time external contributors --- .github/workflows/octo-pr-result-notify.yml | 14 +++++-------- .github/workflows/octo-pr-review-feed.yml | 21 ++++++------------- .github/workflows/reusable-check-sprint.yml | 6 ++++++ .../reusable-pr-contributor-welcome.yml | 9 ++++++-- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/.github/workflows/octo-pr-result-notify.yml b/.github/workflows/octo-pr-result-notify.yml index bbad5b8..edeb2f6 100644 --- a/.github/workflows/octo-pr-result-notify.yml +++ b/.github/workflows/octo-pr-result-notify.yml @@ -82,7 +82,7 @@ jobs: import urllib.error ALLOWED_KINDS = { - "pr_merged", "pr_closed", "pr_reopened", + "pr_merged", "pr_closed", "review_approved", "review_changes_requested" } @@ -199,22 +199,18 @@ jobs: f"\U0001f464 {pr_author}\n" f"\U0001f517 {pr_url}" ) - elif event_kind == "pr_reopened": - message = ( - f"\U0001f504 [{repo_name}] PR #{pr_number} reopened · {pr_title}\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) elif event_kind == "review_approved": + reviewer_display = reviewer if reviewer else "(unknown)" message = ( f"✅ [{repo_name}] PR #{pr_number} approved · {pr_title}\n" - f"\U0001f464 reviewer: {reviewer}\n" + f"\U0001f464 reviewer: {reviewer_display}\n" f"\U0001f517 {pr_url}" ) elif event_kind == "review_changes_requested": + reviewer_display = reviewer if reviewer else "(unknown)" message = ( f"\U0001f501 [{repo_name}] PR #{pr_number} changes requested · {pr_title}\n" - f"\U0001f464 reviewer: {reviewer}\n" + f"\U0001f464 reviewer: {reviewer_display}\n" f"\U0001f517 {pr_url}" ) diff --git a/.github/workflows/octo-pr-review-feed.yml b/.github/workflows/octo-pr-review-feed.yml index 8a7d218..61ac0a4 100644 --- a/.github/workflows/octo-pr-review-feed.yml +++ b/.github/workflows/octo-pr-review-feed.yml @@ -60,7 +60,7 @@ jobs: import urllib.request import urllib.error - ALLOWED_ACTIONS = {"opened", "ready_for_review", "review_requested", "synchronize"} + ALLOWED_ACTIONS = {"ready_for_review", "review_requested"} def require_env(name): value = os.environ.get(name, "").strip() @@ -156,25 +156,16 @@ jobs: sys.exit(0) emoji_map = { - "opened": "\U0001f535", "ready_for_review": "✅", "review_requested": "\U0001f440", - "synchronize": "\U0001f501", } emoji = emoji_map[event_action] - if event_action == "synchronize": - message = ( - f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title} — new commits pushed\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) - else: - message = ( - f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title}\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) + message = ( + f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title}\n" + f"\U0001f464 {pr_author}\n" + f"\U0001f517 {pr_url}" + ) failed = [] send_message(api_base_url, token, project_group_id, message, failed) diff --git a/.github/workflows/reusable-check-sprint.yml b/.github/workflows/reusable-check-sprint.yml index a36b2ad..379adb7 100644 --- a/.github/workflows/reusable-check-sprint.yml +++ b/.github/workflows/reusable-check-sprint.yml @@ -325,6 +325,7 @@ jobs: query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { + isDraft closingIssuesReferences(first: 100) { nodes { number @@ -370,6 +371,11 @@ jobs: data = graphql(pr_query, variables=pr_variables) pr = data["repository"]["pullRequest"] + + if pr.get("isDraft"): + print("✅ Skipping Sprint check for draft PR.") + sys.exit(0) + linked_issues = pr["closingIssuesReferences"]["nodes"] pr_items = pr["projectItems"]["nodes"] except (KeyError, TypeError) as e: diff --git a/.github/workflows/reusable-pr-contributor-welcome.yml b/.github/workflows/reusable-pr-contributor-welcome.yml index f204473..bfbecc4 100644 --- a/.github/workflows/reusable-pr-contributor-welcome.yml +++ b/.github/workflows/reusable-pr-contributor-welcome.yml @@ -107,10 +107,15 @@ jobs: } const body = [ - `Hey @${username}, thanks for your first contribution to Octo! 🎉`, + `Hey @${username}, thanks for your first PR to Octo! 🎉`, '', - 'Welcome to the community, and looking forward to more! 🐙', + 'A maintainer will review it soon. While you wait, please make sure all CI checks pass.', '', + 'A few helpful links:', + '- 📖 [Contributing Guide](https://github.com/Mininglamp-OSS/.github/blob/main/CONTRIBUTING.md)', + '- 💬 [Discord Community](https://discord.gg/vj9Vsj9hSB)', + '', + 'Welcome aboard! 🐙', '— Octo Team', ].join('\n'); From d76a5d6020aed4add5e5861868ef49dc30587856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=98=8E=20=28Octo=20Bot=29?= <27oa0iEYms60b3f1076_bot@octo.ai> Date: Sun, 31 May 2026 20:38:34 +0800 Subject: [PATCH 2/3] fix: check isDraft before sprint resolution in reusable-check-sprint.yml --- .github/workflows/reusable-check-sprint.yml | 46 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-check-sprint.yml b/.github/workflows/reusable-check-sprint.yml index 379adb7..d85cdfd 100644 --- a/.github/workflows/reusable-check-sprint.yml +++ b/.github/workflows/reusable-check-sprint.yml @@ -243,6 +243,47 @@ jobs: return datetime.now(ZoneInfo("Asia/Shanghai")).date() + # --- Step 0: Skip draft PRs before any board-state queries --------------- + # + # This must run BEFORE Step 1 (sprint resolution) so draft PRs are never + # blocked by board-health failures (missing Sprint field, no active + # iteration, insufficient token scopes, etc.). + # + # NOTE: Callers must include `ready_for_review` in their pull_request_target + # types to ensure this check re-evaluates when a draft PR is marked ready + # for review. Without it, the green "skipped" result cached on the draft + # SHA will remain valid after the PR is promoted to ready, allowing it to + # merge without Sprint validation. + # See: https://github.com/Mininglamp-OSS/.github/pull/51 + + draft_data = graphql( + """ + query($owner: String!, $name: String!, $number: Int!) { + repository(owner: $owner, name: $name) { + pullRequest(number: $number) { + isDraft + } + } + } + """, + variables={"owner": REPO_OWNER, "name": REPO_SHORT, "number": PR_NUMBER}, + ) + + try: + is_draft = draft_data["repository"]["pullRequest"]["isDraft"] + except (KeyError, TypeError) as e: + print(f"::error::Failed to read PR draft status for #{PR_NUMBER}. " + f"Detail: {e}") + sys.exit(1) + + if is_draft: + print("✅ Skipping Sprint check for draft PR.") + print("::notice::Draft PR detected. Sprint check skipped.") + print("::notice::Callers must include `ready_for_review` in " + "pull_request_target types to re-evaluate this check on " + "draft→ready transition.") + sys.exit(0) + # --- Step 1: Determine the current sprint iteration ----------------------- try: @@ -325,7 +366,6 @@ jobs: query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { - isDraft closingIssuesReferences(first: 100) { nodes { number @@ -372,10 +412,6 @@ jobs: data = graphql(pr_query, variables=pr_variables) pr = data["repository"]["pullRequest"] - if pr.get("isDraft"): - print("✅ Skipping Sprint check for draft PR.") - sys.exit(0) - linked_issues = pr["closingIssuesReferences"]["nodes"] pr_items = pr["projectItems"]["nodes"] except (KeyError, TypeError) as e: From 70ca5a17b98783056090709992f8a21781c4b4d7 Mon Sep 17 00:00:00 2001 From: lml2468 Date: Sun, 31 May 2026 20:56:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20hoist=20reviewer=5Fdisplay,=20update=20caller=20req?= =?UTF-8?q?uirements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reusable-check-sprint.yml: update header usage example to include ready_for_review and converted_to_draft in pull_request_target types, with an inline note that ready_for_review is REQUIRED for the draft-skip to be safe (else a stale green from the draft SHA bypasses the Sprint gate). Make the runtime ::notice:: more explicit about the same requirement. Remove stray blank line at the Step 2 query block. - octo-pr-result-notify.yml: hoist reviewer_display computation out of the event_kind branches into the input-parsing section so it is computed once. --- .github/workflows/octo-pr-result-notify.yml | 3 +-- .github/workflows/reusable-check-sprint.yml | 16 ++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/octo-pr-result-notify.yml b/.github/workflows/octo-pr-result-notify.yml index edeb2f6..5e6eda5 100644 --- a/.github/workflows/octo-pr-result-notify.yml +++ b/.github/workflows/octo-pr-result-notify.yml @@ -170,6 +170,7 @@ jobs: pr_author = sanitize_text(require_env('PR_AUTHOR'), max_len=80) event_kind = require_env("EVENT_KIND") reviewer = sanitize_text(get_env('REVIEWER'), max_len=80) + reviewer_display = reviewer if reviewer else "(unknown)" pr_additions = int(get_env("PR_ADDITIONS", "0") or "0") pr_deletions = int(get_env("PR_DELETIONS", "0") or "0") pr_changed_files = int(get_env("PR_CHANGED_FILES", "0") or "0") @@ -200,14 +201,12 @@ jobs: f"\U0001f517 {pr_url}" ) elif event_kind == "review_approved": - reviewer_display = reviewer if reviewer else "(unknown)" message = ( f"✅ [{repo_name}] PR #{pr_number} approved · {pr_title}\n" f"\U0001f464 reviewer: {reviewer_display}\n" f"\U0001f517 {pr_url}" ) elif event_kind == "review_changes_requested": - reviewer_display = reviewer if reviewer else "(unknown)" message = ( f"\U0001f501 [{repo_name}] PR #{pr_number} changes requested · {pr_title}\n" f"\U0001f464 reviewer: {reviewer_display}\n" diff --git a/.github/workflows/reusable-check-sprint.yml b/.github/workflows/reusable-check-sprint.yml index d85cdfd..35d7956 100644 --- a/.github/workflows/reusable-check-sprint.yml +++ b/.github/workflows/reusable-check-sprint.yml @@ -59,9 +59,13 @@ # # on: # pull_request_target: -# types: [opened, synchronize, reopened, edited] +# types: [opened, synchronize, reopened, edited, ready_for_review, converted_to_draft] # # Include "edited" so the check re-runs when the PR description is # # updated (e.g. developer adds "Closes #" after opening). +# # Include "ready_for_review" and "converted_to_draft" so the check +# # re-runs across draft↔ready transitions. REQUIRED for the draft-skip +# # to be safe: without "ready_for_review", a stale green from the draft +# # SHA persists after promotion to ready and bypasses the Sprint gate. # # jobs: # check-sprint: @@ -278,10 +282,11 @@ jobs: if is_draft: print("✅ Skipping Sprint check for draft PR.") - print("::notice::Draft PR detected. Sprint check skipped.") - print("::notice::Callers must include `ready_for_review` in " - "pull_request_target types to re-evaluate this check on " - "draft→ready transition.") + print("::notice::Draft PR detected. Sprint check skipped (exit 0).") + print("::notice::REQUIRED caller config: include `ready_for_review` " + "in `pull_request_target.types`. Without it, this green " + "result is cached on the draft SHA and persists after the " + "PR is marked ready, bypassing the Sprint gate.") sys.exit(0) # --- Step 1: Determine the current sprint iteration ----------------------- @@ -411,7 +416,6 @@ jobs: data = graphql(pr_query, variables=pr_variables) pr = data["repository"]["pullRequest"] - linked_issues = pr["closingIssuesReferences"]["nodes"] pr_items = pr["projectItems"]["nodes"] except (KeyError, TypeError) as e: