Skip to content

ci: auto-unblock issues on blocker-close — flip Blocked → Todo when all blockers resolve - #576

Merged
robotrocketscience merged 3 commits into
mainfrom
ci/issue-570-auto-unblock
May 10, 2026
Merged

ci: auto-unblock issues on blocker-close — flip Blocked → Todo when all blockers resolve#576
robotrocketscience merged 3 commits into
mainfrom
ci/issue-570-auto-unblock

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #570.

Adds .github/workflows/auto-unblock-on-blocker-close.yml: triggered on issues.closed, finds every open issue whose body references the closing issue as a blocker (Blocked-by / Blocked by / Depends on / gate), and for each:

  • If all referenced blockers are now CLOSED and the issue's project-board status is currently Blocked → flip status Blocked → Todo and post a confirmation comment.
  • If some blockers remain open → post a one-line "still blocked by …" status comment.

A hidden marker <!-- auto-unblock:#<closed-issue> --> makes the comment idempotent across re-runs.

Auth — operator action required

Project-board mutations require a PAT with Projects: write scope, stored as repo secret PROJECT_TOKEN. Issue comments use the default GITHUB_TOKEN.

If PROJECT_TOKEN is unset OR the GraphQL mutation fails for any reason, the workflow falls back to comment-only mode: it still posts the confirmation comment (mentioning @robotrocketscience) and exits 0. No silent failures.

The auth swap uses GH_TOKEN="$PROJECT_TOKEN" gh api graphql ... rather than --header "Authorization: bearer ..."gh api always sets its own Authorization header from GH_TOKEN, so passing a custom header produces two Authorization headers and the gh-internal one wins. Env override is the canonical fix.

Project-board constants

Hard-coded in the workflow (per the data already gathered for the repo's user-owned project, aelfrice v2.1):

  • PROJECT_ID = PVT_kwHOEHqEMc4BWDLC
  • STATUS_FIELD_ID = PVTSSF_lAHOEHqEMc4BWDLCzhRaei8
  • STATUS_OPTION_TODO = f9451636
  • STATUS_OPTION_BLOCKED = 3bc23bae

Replay script

scripts/replay-auto-unblock.py exercises the same regex + decision logic without mutating anything. Output for the spec's regression case (#154 + #437):

=== auto-unblock replay ===
  Target issue:    #154
  Simulated close: #437
  Repository:      robotrocketscience/aelfrice

[body source] provided via --body flag

── Body snippet (first 300 chars) ──────────────────────────────
  Blocked-by: #307\nDepends on: #437

── Trigger check: does body reference #437 as a blocker? ──
  YES — matched pattern(s): ['Depends on: #437']

── All blocker references in body ──────────────────────────────
  #307
  #437

── Blocker states (#437 simulated as CLOSED) ──────────
  Fetching state of #307... CLOSED

  #307: CLOSED  (closedAt: 2026-04-30T16:14:09Z)
  #437: CLOSED  (closedAt: (simulated))

── Decision ────────────────────────────────────────────────────
  WOULD FLIP #154: Blocked → Todo
    (all 2 blocker(s) are CLOSED)
  Workflow action: flip project-board status + post confirmation comment.

── False-positive guard ────────────────────────────────────────
  Test body: 'Blocked-by: #4372'
  Matches #437? NO  (correct — \b prevents #4372 matching #437)

Note: the replay uses a --body override because issue #154's real body uses informal prose ("remains pending the calibrated #437 reproducibility harness…") rather than the formal Blocked-by: / Depends on: syntax the workflow parses. The script is honest about this — under #154's real body it reports "no formal blocker reference found" and exits without claiming a flip would have happened. The flip behavior is correct when bodies use the formal syntax (which the spec mandates and #569's audit workflow will encourage going forward).

Acceptance map

Test plan

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/auto-unblock-on-blocker-close.yml'))" — parses clean
  • uv run python scripts/replay-auto-unblock.py 154 --simulate-closed 437 --body 'Blocked-by: #307\nDepends on: #437' — would-flip
  • uv run pytest -x -q — 3144 passed, 49 skipped
  • Discretion grep on diff vs github/main — empty
  • Operator: add PROJECT_TOKEN repo secret (fine-grained PAT, Projects: write) before relying on automatic flips. Until then the workflow comment-pings instead of flipping.
  • CI staging-gate green

Summary by Sourcery

Add an automation workflow that updates project-board status and comments on dependent issues when a blocking issue is closed, plus a dry-run script to replay and validate this logic against live GitHub data.

New Features:

  • Introduce an issues.closed GitHub Actions workflow that scans open issues for formal blocker references to the closed issue and updates their project-board status from Blocked to Todo when all blockers are resolved.
  • Post automatic status comments on dependent issues indicating whether they are fully unblocked or still blocked by remaining open issues.
  • Add a replay script to simulate the auto-unblock workflow for a given issue and blocker, using the same parsing and decision logic without mutating GitHub state.

Enhancements:

Tests:

  • Include a dry-run replay script as a diagnostic tool to manually verify auto-unblock behavior and regression scenarios against specific issue pairs.

@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 48 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb6a3eff-3679-4d6a-8d66-08c7b368f79a

📥 Commits

Reviewing files that changed from the base of the PR and between fe43541 and 40bb5c6.

📒 Files selected for processing (2)
  • .github/workflows/auto-unblock-on-blocker-close.yml
  • scripts/replay-auto-unblock.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/issue-570-auto-unblock

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.

@sourcery-ai

sourcery-ai Bot commented May 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a GitHub Actions workflow that, when an issue is closed, scans open issues for formal blocker references to it and automatically flips their project status from Blocked to Todo (or posts status comments) plus a Python replay script that dry-runs the same logic against live GitHub data for regression/debugging.

Sequence diagram for auto-unblock workflow on issue close

sequenceDiagram
    actor Developer
    participant GitHub
    participant AutoUnblockWorkflow as auto_unblock_on_blocker_close
    participant GH_CLI as gh_cli_on_runner
    participant GitHub_REST as github_rest_api
    participant GitHub_GQL as github_graphql_api

    Developer->>GitHub: Close blocker issue
    GitHub-->>AutoUnblockWorkflow: issues.closed event

    AutoUnblockWorkflow->>GH_CLI: issue list (open issues, number, body)
    GH_CLI-->>GitHub_REST: GET issues list
    GitHub_REST-->>GH_CLI: Open issues JSON
    GH_CLI-->>AutoUnblockWorkflow: Encoded open issues

    loop For each open issue
        AutoUnblockWorkflow->>AutoUnblockWorkflow: Check body for blocker reference to closed issue
        alt Body references closed issue as blocker
            AutoUnblockWorkflow->>AutoUnblockWorkflow: Parse all blocker numbers via regex
            loop For each blocker
                AutoUnblockWorkflow->>GH_CLI: issue view blocker
                GH_CLI-->>GitHub_REST: GET issue state
                GitHub_REST-->>GH_CLI: state, closedAt
                GH_CLI-->>AutoUnblockWorkflow: blocker state
            end
            alt Any blocker not CLOSED
                AutoUnblockWorkflow->>GH_CLI: issue view comments (idempotency marker check)
                GH_CLI-->>GitHub_REST: GET comments
                GitHub_REST-->>GH_CLI: comments
                GH_CLI-->>AutoUnblockWorkflow: comments JSON
                alt Marker not found
                    AutoUnblockWorkflow->>GH_CLI: issue comment partial unblock status
                    GH_CLI-->>GitHub_REST: POST comment
                    GitHub_REST-->>GH_CLI: Comment created
                else Marker found
                    AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip duplicate comment
                end
            else All blockers CLOSED
                AutoUnblockWorkflow->>GitHub_GQL: query issue projectItems and Status value
                GitHub_GQL-->>AutoUnblockWorkflow: project item id and status optionId
                alt Status optionId is Blocked
                    alt PROJECT_TOKEN set and project item present
                        AutoUnblockWorkflow->>GitHub_GQL: mutation updateProjectV2ItemFieldValue to Todo (GH_TOKEN=PROJECT_TOKEN)
                        GitHub_GQL-->>AutoUnblockWorkflow: mutation result
                        alt Mutation succeeded
                            AutoUnblockWorkflow->>GH_CLI: issue view comments (idempotency marker check)
                            GH_CLI-->>GitHub_REST: GET comments
                            GitHub_REST-->>GH_CLI: comments
                            GH_CLI-->>AutoUnblockWorkflow: comments JSON
                            alt Marker not found
                                AutoUnblockWorkflow->>GH_CLI: issue comment auto unblock with flip confirmation
                                GH_CLI-->>GitHub_REST: POST comment
                                GitHub_REST-->>GH_CLI: Comment created
                            else Marker found
                                AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip duplicate comment
                            end
                        else Mutation failed
                            AutoUnblockWorkflow->>AutoUnblockWorkflow: Flip failed, switch to comment only
                            AutoUnblockWorkflow->>GH_CLI: issue view comments (marker check)
                            GH_CLI-->>GitHub_REST: GET comments
                            GitHub_REST-->>GH_CLI: comments
                            GH_CLI-->>AutoUnblockWorkflow: comments JSON
                            alt Marker not found
                                AutoUnblockWorkflow->>GH_CLI: issue comment auto unblock with manual flip request
                                GH_CLI-->>GitHub_REST: POST comment
                                GitHub_REST-->>GH_CLI: Comment created
                            else Marker found
                                AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip duplicate comment
                            end
                        end
                    else PROJECT_TOKEN missing or no project item
                        AutoUnblockWorkflow->>AutoUnblockWorkflow: Cannot flip, comment only mode
                        AutoUnblockWorkflow->>GH_CLI: issue view comments (marker check)
                        GH_CLI-->>GitHub_REST: GET comments
                        GitHub_REST-->>GH_CLI: comments
                        GH_CLI-->>AutoUnblockWorkflow: comments JSON
                        alt Marker not found
                            AutoUnblockWorkflow->>GH_CLI: issue comment auto unblock with manual flip request
                            GH_CLI-->>GitHub_REST: POST comment
                            GitHub_REST-->>GH_CLI: Comment created
                        else Marker found
                            AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip duplicate comment
                        end
                    end
                else Status not Blocked
                    AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip flip and comment
                end
            end
        else No blocker reference
            AutoUnblockWorkflow->>AutoUnblockWorkflow: Skip issue
        end
    end

    AutoUnblockWorkflow-->>GitHub: Log auto unblock scan complete
Loading

Flow diagram for per-issue auto-unblock decision logic

flowchart TD
    Start["Start scan when blocker issue closes"] --> ListOpen["List up to 1000 open issues via gh issue list"]
    ListOpen --> AnyOpen{Any open issues?}
    AnyOpen -- No --> End["Exit: nothing to do"]
    AnyOpen -- Yes --> ForEachIssue["For each open issue"]

    ForEachIssue --> CheckBody["Does body contain formal blocker reference to closed issue?"]
    CheckBody -- No --> NextIssue["Skip to next issue"]
    NextIssue --> ForEachIssue

    CheckBody -- Yes --> ParseBlockers["Parse all blocker issue numbers with regex"]
    ParseBlockers --> CheckStates["For each blocker: fetch state via gh issue view"]
    CheckStates --> AnyOpenBlocker{Any blocker not CLOSED?}

    AnyOpenBlocker -- Yes --> MarkerCheckPartial["Check for marker comment <!-- auto-unblock:#n -->"]
    MarkerCheckPartial --> MarkerExistsPartial{Marker already present?}
    MarkerExistsPartial -- Yes --> NextIssue
    MarkerExistsPartial -- No --> CommentPartial["Post partial unblock comment listing open blockers"]
    CommentPartial --> NextIssue

    AnyOpenBlocker -- No --> QueryProject["Query project item and Status via GraphQL"]
    QueryProject --> StatusBlocked{Status optionId == Blocked?}

    StatusBlocked -- No --> NextIssue

    StatusBlocked -- Yes --> CanFlip{PROJECT_TOKEN set and project item id found?}

    CanFlip -- No --> MarkerCheckFallback["Check for marker comment"]
    MarkerCheckFallback --> MarkerExistsFallback{Marker already present?}
    MarkerExistsFallback -- Yes --> NextIssue
    MarkerExistsFallback -- No --> CommentFallback["Post auto unblock comment requesting manual Blocked to Todo flip"]
    CommentFallback --> NextIssue

    CanFlip -- Yes --> Mutation["Call updateProjectV2ItemFieldValue to set Status Todo"]
    Mutation --> MutationOk{Mutation succeeded?}

    MutationOk -- No --> MarkerCheckFallback

    MutationOk -- Yes --> MarkerCheckFlip["Check for marker comment"]
    MarkerCheckFlip --> MarkerExistsFlip{Marker already present?}
    MarkerExistsFlip -- Yes --> NextIssue
    MarkerExistsFlip -- No --> CommentFlip["Post auto unblock comment confirming Blocked to Todo flip"]
    CommentFlip --> NextIssue

    ForEachIssue -->|All issues processed| Done["auto-unblock scan complete"]
    Done --> End
Loading

File-Level Changes

Change Details Files
Introduce an issues.closed-triggered workflow that auto-detects dependent issues, evaluates blocker states, and flips project-board status Blocked → Todo with robust fallback and idempotent commenting.
  • Configure a new workflow auto-unblock-on-blocker-close.yml triggered on issues.closed with appropriate permissions and concurrency keyed by the closing issue number.
  • List up to 1000 open issues via gh issue list and filter by a case-insensitive regex that matches formal blocker syntax referencing the closed issue number, using a word boundary to avoid false positives (e.g., [retrieval] Pipeline composition tracker — unified retrieve() with feature-flag gate #154 not matching #1542).
  • For each referencing issue, parse all blocker references from its body, query each blocker issue’s state via gh issue view, and determine whether all blockers are CLOSED or if any remain open.
  • When some blockers remain open, post a partial-unblock status comment containing an HTML marker and skip duplicates by searching existing comments for that marker.
  • When all blockers are closed, look up the issue’s ProjectV2 item in a specific project via a GraphQL query, read its Status field optionId, and only proceed if the current status matches the configured Blocked optionId.
  • Attempt to flip the project item’s Status to the configured Todo optionId via updateProjectV2ItemFieldValue, using GH_TOKEN=$PROJECT_TOKEN to authenticate, handling missing PROJECT_TOKEN or mutation failures by falling back to comment-only mode without failing the workflow.
  • Post a confirmation or fallback comment (including the marker and a list of closed blockers) only if the marker is not already present, keeping both the flip and comment idempotent.
  • Guard all CLI calls with pipefail, defensive defaults, and jq filtering, and ensure the workflow exits successfully even if project mutations are not possible.
.github/workflows/auto-unblock-on-blocker-close.yml
Add a Python replay utility that mirrors the workflow’s blocker-parsing and decision logic against live GitHub issues without making mutations.
  • Define the same blocker regex as in the workflow to parse formal Blocked-by/Depends on/gate references from an issue body, ensuring consistent behavior and word-boundary correctness.
  • Implement CLI arguments to specify the target issue, simulated-closed issue, optional body override, repository, and blocked-status optionId, with helpful usage and epilog docs.
  • Fetch the issue body via gh api unless overridden by --body, then check whether the body actually references the simulated-closed issue with the trigger pattern; exit early with an explanatory message if not.
  • Resolve the state (and closedAt) of each referenced blocker via gh issue view, overriding the simulated-closed issue’s state to CLOSED in-memory for the replay.
  • Print a WOULD FLIP / WOULD NOT FLIP decision plus the implied workflow action (flip + comment vs. still-blocked comment), matching the workflow’s semantics but doing no writes.
  • Demonstrate the false-positive guard by constructing a Add CI workflows, scan config, and align README #2 test body and showing that it does not match the closed issue number under the trigger regex.
scripts/replay-auto-unblock.py

Assessment against linked issues

Issue Objective Addressed Explanation
#570 Add a GitHub Actions workflow at .github/workflows/auto-unblock-on-blocker-close.yml that triggers on issues.closed events.
#570 When an issue closes, automatically scan open issues for formal blocker references (Blocked-by, Blocked by, Depends on, gate), resolve the state of all referenced blockers, and if all are CLOSED and the project-board status is Blocked, flip the status to Todo and post a confirmation comment; if any blockers remain open, post a one-line status comment instead.
#570 Ensure idempotent, safe behavior: do not flip status if the issue is not currently Blocked, do not act when any blocker remains open, avoid duplicate comments via a hidden marker, and guard against false-positive matches (e.g., #1542 not matching #154).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@robotrocketscience robotrocketscience added author-Maxwell PR coordination mutex attn:review Needs review (PR open, awaiting reviewer) labels May 10, 2026
Comment on lines +38 to +40
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 security issue, 2 other issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • In the replay script, the --status-option-blocked argument is parsed but never used; either wire it into the logic or remove the option to avoid confusion.
  • The workflow’s GraphQL query hard-codes repository(owner:"robotrocketscience", name:"aelfrice") while other calls use the REPO env; consider deriving the owner/name from REPO to keep behavior consistent if the workflow is ever reused or the repo is renamed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the replay script, the `--status-option-blocked` argument is parsed but never used; either wire it into the logic or remove the option to avoid confusion.
- The workflow’s GraphQL query hard-codes `repository(owner:"robotrocketscience", name:"aelfrice")` while other calls use the `REPO` env; consider deriving the owner/name from `REPO` to keep behavior consistent if the workflow is ever reused or the repo is renamed.

## Individual Comments

### Comment 1
<location path="scripts/replay-auto-unblock.py" line_range="93-102" />
<code_context>
+        epilog=__doc__,
+    )
+    parser.add_argument("issue", type=int, help="Issue number to check.")
+    parser.add_argument(
+        "--simulate-closed",
+        dest="simulate_closed",
+        type=int,
+        required=True,
+        help="Issue number to treat as CLOSED for this replay.",
+    )
+    parser.add_argument(
+        "--body",
+        default=None,
+        help=(
+            "Override body text (supports \\n escapes). "
+            "If omitted, the live GitHub body is fetched."
+        ),
+    )
+    parser.add_argument(
+        "--repo",
+        default=REPO_DEFAULT,
+        help=f"GitHub repository (default: {REPO_DEFAULT}).",
+    )
+    parser.add_argument(
+        "--status-option-blocked",
+        default="3bc23bae",
+        help="optionId representing Blocked on the project board (default: 3bc23bae).",
</code_context>
<issue_to_address>
**issue:** `--status-option-blocked` argument is parsed but never used in the replay logic.

Since this flag is never used in `main()`, it acts as dead configuration and may mislead users into expecting status-ID validation that doesn’t exist. Please either hook it into the replay behavior (e.g., printing or simulating the expected status check) or remove it to avoid unused options.
</issue_to_address>

### Comment 2
<location path="scripts/replay-auto-unblock.py" line_range="145-50" />
<code_context>
+    print()
+
+    # ── 2. Check if body references the simulated-closed issue as a blocker
+    trigger_pattern = re.compile(
+        rf"(?:blocked[-\s]by:?\s*|blocked\s+by:?\s*|depends\s+on:?\s*|gate:?\s*)"
+        rf"#{re.escape(str(closed_num))}\b",
+        re.IGNORECASE,
+    )
+    triggers = trigger_pattern.findall(body)
</code_context>
<issue_to_address>
**suggestion:** Trigger regex is redefined instead of reusing the global blocker pattern, increasing divergence risk.

Since this regex is manually re-stated and only `closed_num` varies, future syntax changes could cause it to fall out of sync with `BLOCKER_PATTERN`. Consider constructing this pattern from the same building blocks as `BLOCKER_PATTERN` (e.g., shared prefix plus parameterized issue number) so the replay script behavior automatically tracks workflow changes.

Suggested implementation:

```python
    # ── 2. Check if body references the simulated-closed issue as a blocker
    # Reuse the shared BLOCKER_PATTERN so this logic stays in sync with the
    # main workflow's blocker syntax. Filter matches to only those that
    # reference the simulated-closed issue number.
    all_blocker_matches = [m.group(0) for m in BLOCKER_PATTERN.finditer(body)]
    triggers = [match for match in all_blocker_matches if f"#{closed_num}" in match]

```

This change assumes:
1. `BLOCKER_PATTERN` is already defined in this module (or imported) and matches the same blocker syntax used by the workflow.
2. `BLOCKER_PATTERN` is compiled with appropriate flags (e.g., `re.IGNORECASE`) as needed.

If `BLOCKER_PATTERN` is not currently available in this file, you should:
- Import it from the module where it is defined, or
- Move its definition to a shared location and import it here.

If `BLOCKER_PATTERN`'s matches do not always contain the full `#<number>` text, you may instead want to filter using a capturing group (e.g., `m.group("issue") == str(closed_num)`), adjusting the filter expression to match the actual group name or index in the existing pattern.
</issue_to_address>

### Comment 3
<location path="scripts/replay-auto-unblock.py" line_range="55-59" />
<code_context>
    result = subprocess.run(
        ["gh"] + args,
        capture_output=True,
        text=True,
    )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +93 to +102
parser.add_argument(
"--simulate-closed",
dest="simulate_closed",
type=int,
required=True,
help="Issue number to treat as CLOSED for this replay.",
)
parser.add_argument(
"--body",
default=None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: --status-option-blocked argument is parsed but never used in the replay logic.

Since this flag is never used in main(), it acts as dead configuration and may mislead users into expecting status-ID validation that doesn’t exist. Please either hook it into the replay behavior (e.g., printing or simulating the expected status check) or remove it to avoid unused options.

# Same pattern set as the workflow (case-insensitive, word-boundary on issue number).
BLOCKER_PATTERN = re.compile(
r"(?:blocked[-\s]by:?\s*|blocked\s+by:?\s*|depends\s+on:?\s*|gate:?\s*)#(\d+)\b",
re.IGNORECASE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Trigger regex is redefined instead of reusing the global blocker pattern, increasing divergence risk.

Since this regex is manually re-stated and only closed_num varies, future syntax changes could cause it to fall out of sync with BLOCKER_PATTERN. Consider constructing this pattern from the same building blocks as BLOCKER_PATTERN (e.g., shared prefix plus parameterized issue number) so the replay script behavior automatically tracks workflow changes.

Suggested implementation:

    # ── 2. Check if body references the simulated-closed issue as a blocker
    # Reuse the shared BLOCKER_PATTERN so this logic stays in sync with the
    # main workflow's blocker syntax. Filter matches to only those that
    # reference the simulated-closed issue number.
    all_blocker_matches = [m.group(0) for m in BLOCKER_PATTERN.finditer(body)]
    triggers = [match for match in all_blocker_matches if f"#{closed_num}" in match]

This change assumes:

  1. BLOCKER_PATTERN is already defined in this module (or imported) and matches the same blocker syntax used by the workflow.
  2. BLOCKER_PATTERN is compiled with appropriate flags (e.g., re.IGNORECASE) as needed.

If BLOCKER_PATTERN is not currently available in this file, you should:

  • Import it from the module where it is defined, or
  • Move its definition to a shared location and import it here.

If BLOCKER_PATTERN's matches do not always contain the full #<number> text, you may instead want to filter using a capturing group (e.g., m.group("issue") == str(closed_num)), adjusting the filter expression to match the actual group name or index in the existing pattern.

Comment on lines +55 to +59
result = subprocess.run(
["gh"] + args,
capture_output=True,
text=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

@github-actions github-actions Bot added the attn:merge-conflict PR branch needs rebase label May 10, 2026
@github-actions

Copy link
Copy Markdown

This PR is now behind main. Rebase locally so your commit signatures stay intact:

git fetch origin && git checkout 'ci/issue-570-auto-unblock' && git rebase origin/main
# resolve conflicts if any, then
git push --force-with-lease

Auto-rebase was removed because the bot has no signing key; rebasing as the bot strips author signatures and the required_signatures rule on main then blocks the merge. See #341.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:einstein:2026-05-10T06:31:22Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:leibniz:2026-05-10T06:31:40Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:leibniz:2026-05-10T06:31:45Z]

@robotrocketscience
robotrocketscience force-pushed the ci/issue-570-auto-unblock branch from df294eb to 1cb805a Compare May 10, 2026 06:35
…lose (#570)

Triggers on issues.closed. Searches all open issues for formal blocker
references (Blocked-by, Blocked by, Depends on, gate — case-insensitive,
word-boundary on issue number). For each matched issue: if all referenced
blockers are now CLOSED and board status is Blocked, flips status to Todo
and posts a confirmation comment. If any blocker remains open, posts a
one-line status comment. Falls back to comment-only when PROJECT_TOKEN is
absent or the GraphQL mutation fails, pinging @robotrocketscience.
Idempotent via hidden comment marker <!-- auto-unblock:#N -->.
…ecision

Runnable as:
  uv run python scripts/replay-auto-unblock.py <issue> --simulate-closed <N>

Fetches the issue body from GitHub (or accepts --body override), parses
all formal blocker references via the same regex as the workflow, checks
each blocker's live state (overriding the simulated-closed issue to
CLOSED), and prints the parsed blocker list plus the would-be flip decision
without mutating anything. Also demonstrates the \b word-boundary guard
that prevents #1542 from matching when #154 closes.
…ation header

gh api always sets its own Authorization header from GH_TOKEN, so passing
--header "Authorization: bearer ..." produces two Authorization headers
(behavior undefined; gh's wins in practice). Swap PROJECT_TOKEN by
overriding GH_TOKEN for the mutation subprocess, and drop the redundant
header on the read query.
@robotrocketscience
robotrocketscience force-pushed the ci/issue-570-auto-unblock branch from 1cb805a to 40bb5c6 Compare May 10, 2026 06:35
@robotrocketscience
robotrocketscience merged commit 40bb5c6 into main May 10, 2026
20 of 21 checks passed
@robotrocketscience
robotrocketscience deleted the ci/issue-570-auto-unblock branch May 10, 2026 06:36
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:einstein:2026-05-10T06:36:59Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attn:merge-conflict PR branch needs rebase attn:review Needs review (PR open, awaiting reviewer) author-Maxwell PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: auto-unblock issues on blocker-close — flip Blocked → Todo when all blockers resolve (rook)

2 participants