Skip to content

[feature] Flag invalid pull requests that do not reference validate #709#716

Open
prathmeshkulkarni-coder wants to merge 2 commits into
openwisp:masterfrom
prathmeshkulkarni-coder:feature/709-flag-invalid-prs
Open

[feature] Flag invalid pull requests that do not reference validate #709#716
prathmeshkulkarni-coder wants to merge 2 commits into
openwisp:masterfrom
prathmeshkulkarni-coder:feature/709-flag-invalid-prs

Conversation

@prathmeshkulkarni-coder

@prathmeshkulkarni-coder prathmeshkulkarni-coder commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #709 .

Description of Changes

What this does

External contributors must now link their pull request to an issue that maintainers have already validated, before it can be reviewed. This helps filter out unsolicited, low-quality, or AI-generated PRs that don't address a problem maintainers actually want worked on.

Maintainers, org members, and collaborators are unaffected — they can keep opening PRs as before, with or without a linked issue.

How it works

  1. When an external contributor opens or updates a PR, the bot checks if it references an issue that is:

    • open
    • labeled with something other than invalid or wontfix
    • in the openwisp organization (same repo or another org repo)
    • assigned to one of the approved contributor project boards
  2. If no such issue is referenced, the bot:

    • adds the invalid label
    • posts a comment explaining why, and how to fix it
  3. A daily scheduled check re-evaluates flagged PRs:

    • if the PR now links to a validated issue → invalid is removed automatically
    • if it's still unresolved 24 hours after the warning comment → the PR is closed automatically

Changes in this PR

  • Workflow triggers (bot-autoassign-pr-issue-link.yml): now also runs on edited and ready_for_review events, so contributors don't have to wait for the daily job to get re-checked after fixing their PR.
  • Issue reference parsing (utils.py): added extract_all_linked_issues, which recognizes same-repo (#123), cross-repo (org/repo#123), and full issue URL references.
  • Validation logic (base.py): added validate_pr_issues, which applies the checks above and exempts owners, members, collaborators, and trusted bots (e.g. Dependabot).
  • Bot behavior: labels invalid PRs, posts the warning comment, and automatically removes the label once the PR is updated to reference a validated issue.
  • Scheduled cleanup (stale_pr_bot.py): automatically closes PRs still flagged invalid 24 hours after the warning comment.
  • Tests: added coverage in test_issue_assignment_bot.py and test_stale_pr_bot.py for the positive, negative, and edge cases.
  • Docs: updated docs/developer/reusable-github-utils.rst to describe the new triggers, requirements, and policy.

Screenshot

N/A

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 29d0c4a0-ff7c-4abb-aaf5-f816aedbd92a

📥 Commits

Reviewing files that changed from the base of the PR and between 52e3df0 and ceeff76.

📒 Files selected for processing (10)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • .github/workflows/reusable-bot-autoassign.yml
  • docs/developer/reusable-github-utils.rst
  • setup.py
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

In Django code, mark all user-facing strings as translatable using the Django internationalization framework.

Files:

  • setup.py
**/*

📄 CodeRabbit inference engine (Custom checks)

**/*: For bug fixes in core user-facing functionality, fix the root cause rather than hiding or bypassing the problem.
Bug fixes must include a regression test that reproduces the bug and fails when the fix is removed, except for valid exceptions such as one-line fixes or impractical GitHub Actions workflow fixes.
User-interface bug fixes must include a Selenium browser regression test; missing coverage should be reported as a warning.
Tests must be deterministic and non-flaky; avoid dependence on timing, sleeps, system time, randomness without a fixed seed, race conditions, network services, filesystem state, environment-specific configuration, execution order, shared global state, hardcoded ports, or improperly awaited asynchronous operations.
When a change request modifies documented behavior, update the documentation to describe the new behavior.
Pull requests containing UI changes must include screenshots or screen recordings in the pull request description.
Update tests for non-trivial behavior changes and validate the modified behavior.
Flag changes that may break existing behavior or public APIs unless a clear migration path is provided.
Feature requests must have an issue explaining the feature, validated and accepted by an organization member through labels or project assignment.
New features must be documented; heavily UI-related features should have a dedicated documentation section or page.
Add tests for new features without significantly decreasing coverage; prefer Selenium browser tests for UI-impacting features.
Non-organization members must target maintainer-validated issues with at least one label other than wontfix or invalid and assignment to either the OpenWISP Contributor's Board or OpenWISP Priorities for next releases project.
Flag potential security vulnerabilities.
Flag obvious performance regressions such as heavy loops, repeated I/O, or unoptimized queries.
Cryptic or non-obvious code, including regexes, complex shell commands, and ...

Files:

  • setup.py
  • docs/developer/reusable-github-utils.rst
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • setup.py
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)


[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 9-9: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level

(excessive-permissions)


[error] 10-10: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)


[warning] 9-9: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)


[error] 29-29: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)

.github/workflows/reusable-bot-autoassign.yml

[error] 49-49: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 46-46: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)

🔇 Additional comments (14)
.github/actions/bot-autoassign/base.py (4)

52-100: projectItems(first: 10) is not paginated — a valid PR can be falsely flagged invalid.

If a linked issue has more than 10 project items, the required OpenWISP board may fall outside the first page, so validate_pr_issues returns False and the PR gets labeled invalid/auto-closed. Paginate via pageInfo/endCursor until hasNextPage is false before deciding project membership.


72-77: result.get("data", {}) yields None on a {"data": null} response, raising AttributeError instead of the intended ValueError. Use (result.get("data") or {}).get("repository"). The preceding errors check covers the common data: null + errors shape, but a bare null-data response still slips through.


128-128: Move extract_all_linked_issues and GithubException imports to module scope. No deferred-import requirement exists here; the github import inside the for loop also repeats every iteration. As per coding guidelines, imports belong at the top unless deferral is necessary.

Also applies to: 147-147


206-263: LGTM!

.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (1)

863-937: 📐 Maintainability & Code Quality | ⚡ Quick win

Consider adding a test for the ValidationAPIError re-raise path. process_stale_prs re-raises when validate_pr_issues throws (API/permission failure → workflow fails for retry), but no case here patches validate_pr_issues to raise and asserts propagation plus no label/close side effects. As per coding guidelines, add tests for every behavior change.

Source: Coding guidelines

.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/issue_assignment_bot.py (1)

436-468: LGTM!

.github/actions/bot-autoassign/stale_pr_bot.py (2)

372-428: LGTM!


513-514: LGTM!

.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (1)

908-1184: LGTM!

.github/workflows/bot-autoassign-pr-issue-link.yml (1)

5-48: LGTM!

.github/workflows/reusable-bot-autoassign.yml (1)

15-70: LGTM!

docs/developer/reusable-github-utils.rst (1)

76-176: LGTM!

setup.py (1)

81-81: LGTM!


📝 Walkthrough

Walkthrough

This PR adds linked-issue parsing and validation for external pull requests, including project-board checks, invalid-label handling, and bot warning comments. The stale PR bot rechecks invalid PRs, removes the label when they become valid, or closes them after 24 hours if they remain invalid. Workflows, tests, documentation, and the PyGithub minimum version were also updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Contributor
  participant IssueAssignmentBot
  participant GitHubBot
  participant GitHubAPI

  Contributor->>IssueAssignmentBot: open or edit PR
  IssueAssignmentBot->>GitHubBot: validate_pr_issues(pr)
  GitHubBot->>GitHubAPI: fetch linked issue and project data
  GitHubAPI-->>GitHubBot: issue data
  GitHubBot-->>IssueAssignmentBot: valid or invalid

  alt invalid
    IssueAssignmentBot->>GitHubAPI: add invalid label
    IssueAssignmentBot->>GitHubAPI: create warning comment
  else valid
    IssueAssignmentBot->>GitHubAPI: remove invalid label
  end
Loading
sequenceDiagram
  participant StalePRBot
  participant GitHubBot
  participant GitHubAPI

  StalePRBot->>GitHubBot: validate_pr_issues(pr)
  alt valid
    StalePRBot->>GitHubAPI: remove invalid label
  else still invalid
    StalePRBot->>GitHubBot: get_bot_comment(invalid_unvalidated_issue)
    alt comment older than 24h
      StalePRBot->>GitHubAPI: post close comment
      StalePRBot->>GitHubAPI: close PR
    else no qualifying comment
      StalePRBot->>GitHubAPI: post warning comment
    end
  end
Loading

Possibly related PRs

Suggested labels: enhancement, github_actions, helper-bots

Suggested reviewers: nemesifier, eeshu-yadav


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
General Rules ❌ Error Docs are stale: the bot section still says it does not auto-close PRs, but invalid PRs now auto-close after 24h; the warning comment also omits a direct guidelines link. Revise the docs/comment text to match the new 24h auto-close behavior and add the missing Contributing Guidelines reference.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the feature and references the linked issue.
Description check ✅ Passed The description includes the checklist, linked issue, change summary, and screenshot section from the template.
Linked Issues check ✅ Passed The changes implement the required validation, labeling, comment, and 24-hour closure flow for external PRs.
Out of Scope Changes check ✅ Passed All changes relate to the PR automation, tests, docs, or required dependency and workflow updates.
Bug Fixes ✅ Passed This is bot/workflow automation, not a core user-facing bug fix, so the regression-test criteria are not applicable.
Features ✅ Passed PASS: issue #709 is open, labeled enhancement, and assigned to both required OpenWISP project boards; the PR also adds docs/tests and no UI screenshot is needed.
Changes ✅ Passed Docs were updated for the new PR-validation flow, tests cover the added validation/label/comment paths, and there are no UI changes requiring screenshots.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coveralls

coveralls commented Jul 7, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 97.645%. remained the same — prathmeshkulkarni-coder:feature/709-flag-invalid-prs into openwisp:master

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added enhancement github_actions Pull requests that update GitHub Actions code helper-bots Helper bots, release management automation labels Jul 7, 2026

@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: 7

🤖 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 @.github/actions/bot-autoassign/base.py:
- Around line 29-59: The issue is that get_issue_projects in base.py calls a
non-existent self.github.raw_graphql API, which will fail with an AttributeError
during the project lookup. Update get_issue_projects to use the supported
GraphQL path on self.github, such as self.github.requester.graphql_query (or an
explicit GraphQL POST), and adjust the result parsing accordingly because
graphql_query returns a data payload plus headers rather than the current
dict-with-errors shape. Keep the existing projectItems/node extraction logic
intact in get_issue_projects after normalizing the response.

In @.github/actions/bot-autoassign/issue_assignment_bot.py:
- Around line 456-462: The label handling in issue_assignment_bot.py mixes
production logic with Mock-detection via pr_obj.labels and the assert_called
heuristic, which should be removed. Update the labels branch in the pull-request
processing flow to rely only on the existing try/except around the list
comprehension in the relevant handler, so it safely handles non-iterable values
without checking for unittest.mock internals. Keep the behavior in the
surrounding pull-request label processing logic the same, but simplify the
condition to avoid any test-framework-specific branching.

In @.github/actions/bot-autoassign/stale_pr_bot.py:
- Around line 444-469: The warning-comment body is duplicated in
`stale_pr_bot.py` and `issue_assignment_bot.py`, so the message can drift over
time. Extract the full `comment_body` text into a shared helper or constant on
`GitHubBot` in `base.py`, then have `handle_pull_request` and the corresponding
stale-PR path call that shared symbol instead of building the string inline.
- Line 443: The greeting built from stale_pr_bot.py’s PR author lookup can
become malformed when pr.user is missing because pr_author falls back to an
empty string and later produces a bad mention. Update the author resolution in
the PR comment flow around pr_author so it only formats the greeting when a
valid login exists, and use a safe fallback message or skip the mention entirely
when pr.user is falsy.
- Around line 418-419: Remove the redundant per-iteration datetime import inside
the loop in stale_pr_bot.py and rely on the existing module-level import
instead. Update the loop logic that uses datetime/timezone so it references the
already imported symbols consistently, matching how get_days_since_activity uses
datetime.now(timezone.utc).
- Around line 405-416: The warning-comment lookup in stale_pr_bot.py is
duplicating the logic from has_bot_comment and uses a weaker bot check. Update
the auto-close path to reuse has_bot_comment(pr, "invalid_unvalidated_issue")
from the existing helper in stale_pr_bot.py, and extend that helper if needed to
return the matched comment or timestamp so this block can still gate the 24h
timer. Make sure the identification relies on self.bot_login rather than
comment.user.type == "Bot" so only this bot’s own warning comment is matched.
- Around line 398-476: The `process_stale_prs` flow is swallowing failures from
the `invalid` PR handling path, so GitHub/API permission errors in
`validate_pr_issues` get logged but the run still appears successful. In the
`invalid` label branch of `stale_pr_bot.py`, make `validate_pr_issues` failures
propagate by re-raising the exception or having the outer PR loop return `False`
on failure, so the job fails fast instead of silently skipping the PR. Use the
`validate_pr_issues` call and the surrounding per-PR exception handling in
`process_stale_prs` to locate the fix.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: e0d7c777-05c7-4d9d-b099-3feb0f5adcb8

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3639a and f227393.

📒 Files selected for processing (8)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • docs/developer/reusable-github-utils.rst
📜 Review details
⚠️ CI failures not shown inline (28)

GitHub Actions: OpenWISP Utils CI Build / Python==3.10 django~=5.0.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.11 django~=5.1.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.11 django~=4.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.13 django~=5.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.10 django~=5.1.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.12 django~=5.0.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.11 django~=5.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.13 django~=5.1.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.12 django~=5.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.10 django~=5.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.12 django~=4.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.10 django~=4.2.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.12 django~=5.1.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / Python==3.11 django~=5.0.0: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Post job cleanup.
 [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/openwisp-utils/openwisp-utils --files-from manifest.txt --use-compress-program zstdmt
 /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
 /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
 /usr/bin/tar: Exiting with failure status due to previous errors
 ##[warning]Failed to save: "/usr/bin/tar" failed with error: The process '/usr/bin/tar' failed with exit code 2

GitHub Actions: OpenWISP Utils CI Build / 3_Python==3.10 django~=5.1.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

inux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Downloading charset_normalizer-3.4.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (223 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (218 kB)
 Using cached click_didyo...

GitHub Actions: OpenWISP Utils CI Build / 2_Python==3.10 django~=5.0.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

ltidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Downloading charset_normalizer-3.4.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (223 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp310-cp310-manylin...

GitHub Actions: OpenWISP Utils CI Build / 1_Python==3.11 django~=5.1.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (217 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (58 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (371 kB)
 Using cached...

GitHub Actions: OpenWISP Utils CI Build / 5_Python==3.11 django~=4.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

B)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Downloading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (217 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2...

GitHub Actions: OpenWISP Utils CI Build / 4_Python==3.13 django~=5.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

balls-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (467 kB)
 Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (801 kB)
 Using cached redis-8.0.1-...

GitHub Actions: OpenWISP Utils CI Build / 7_Python==3.12 django~=5.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (105 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached p...

GitHub Actions: OpenWISP Utils CI Build / 8_Python==3.12 django~=5.0.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

 kB)
 Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (366 kB)
 Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
 Using cached sqlparse-0.5.5-py3-...

GitHub Actions: OpenWISP Utils CI Build / 6_Python==3.13 django~=5.1.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (467 kB)
 Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (801 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (365 kB)
 Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
 Using cached sqlparse-0.5.5-py3-none-any.whl (46 kB)
 Using cached tablib-3.9.0-py3-none-any.whl (49 kB)
 Using cached twisted-26.4.0-py3-none-any.whl (3.2 MB)
 Using cached automat-25.4.16-py3-none-any.whl (42 kB)
 Using cached constantly-23.10.4-py3-none-any.whl (13 kB)
 Using cached ...

GitHub Actions: OpenWISP Utils CI Build / 10_Python==3.12 django~=4.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (105 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014...

GitHub Actions: OpenWISP Utils CI Build / 9_Python==3.11 django~=5.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Downloading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (217 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (58 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pyn...

GitHub Actions: OpenWISP Utils CI Build / 12_Python==3.12 django~=5.1.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (221 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (366 kB)
 Using cached ...

GitHub Actions: OpenWISP Utils CI Build / 13_Python==3.10 django~=4.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

ib-2.1-py3-none-any.whl (40 kB)
 Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
 Using cached jsonschema-4.26.0-py3-none-any.whl (90 kB)
 Using cached markdown-3.10.2-py3-none-any.whl (108 kB)
 Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (98 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Downloading charset_normalizer-3.4.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (223 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 U...

GitHub Actions: OpenWISP Utils CI Build / 11_Python==3.10 django~=5.2.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

 Using cached geopy-2.4.1-py3-none-any.whl (125 kB)
 Using cached geographiclib-2.1-py3-none-any.whl (40 kB)
 Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
 Using cached jsonschema-4.26.0-py3-none-any.whl (90 kB)
 Using cached markdown-3.10.2-py3-none-any.whl (108 kB)
 Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (98 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Downloading charset_normalizer-3.4.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (223 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3...

GitHub Actions: OpenWISP Utils CI Build / 14_Python==3.11 django~=5.0.0.txt: [feature] Flag invalid pull requests that do not reference validate #709

Conclusion: failure

View job details

_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Downloading cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (217 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (58 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (371 kB)
 Using cached...
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)


[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 3-5: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🔇 Additional comments (13)
.github/actions/bot-autoassign/stale_pr_bot.py (1)

430-437: LGTM!

.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/base.py (1)

191-213: 🩺 Stability & Availability

Let comment lookup failures surface. Returning False here makes callers treat a comment-listing/read error as “no bot comment yet,” which can lead to duplicate warning comments on retry.

.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (4)

888-916: LGTM!


917-945: LGTM!


1034-1085: LGTM!


946-1033: 🎯 Functional Correctness

Make sure bot_env["repo"] is the repo returned by bot.github.get_repo(...). These tests only stub bot_env["repo"].get_issue(...); if validate_pr_issues uses a different repo mock, the cases won’t exercise the intended issue path.

.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (1)

863-950: LGTM!

.github/actions/bot-autoassign/issue_assignment_bot.py (2)

445-450: LGTM!


452-500: Core validate/label/comment flow looks correct.

Control flow correctly separates closed (early return, no validation) from opened/reopened/edited/ready_for_review (validate → toggle label → conditionally comment), and has_bot_comment prevents duplicate warning comments across repeated invalid states. This matches the described contract in validate_pr_issues/has_bot_comment (base.py) and the accompanying tests.

.github/workflows/bot-autoassign-pr-issue-link.yml (1)

5-5: 🔒 Security & Privacy

Verify no PR-head checkout occurs downstream now that edited/ready_for_review also trigger this privileged workflow.

zizmor flags pull_request_target as a dangerous trigger since it runs with base-repo secrets/permissions while exposed to attacker-influenced event data. This trigger type predates this change (already used for opened/reopened/closed), but expanding it to edited/ready_for_review increases how often the privileged job runs on external PR metadata (including PR body text, which is now parsed for linked-issue extraction). The steps referenced in the docs example checkout the base ref via actions/checkout@v7 without overriding ref, which is safe, but this file's own job body (lines 6-16) isn't fully shown here.

Please confirm the reusable workflow invoked by this job never checks out github.event.pull_request.head.sha/ref or otherwise executes PR-controlled code with these elevated permissions.

Source: Linters/SAST tools

docs/developer/reusable-github-utils.rst (2)

76-82: 🎯 Functional Correctness | ⚡ Quick win

Validate the "24 hours" closure claim against the actual Stale PR job cadence.

This bullet (and the matching warning-comment text in issue_assignment_bot.py) states invalid PRs are "automatically closed after 24 hours if the issue is not resolved." Per the unchanged "How Stale PR Detection Works" section below, the Stale PR job runs once daily. If the actual auto-close logic lives in that same daily job, worst-case latency could approach ~48 hours (grace period + up to a full day until the next scheduled run) rather than a strict 24 hours.

Since the closing logic lives in stale_pr_bot.py (not included in this review), please confirm the described SLA is accurate, or soften the wording (e.g., "closed on the next daily check after 24 hours have elapsed") to avoid setting an inaccurate expectation for contributors.


177-177: LGTM!

Comment thread .github/actions/bot-autoassign/issue_assignment_bot.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py
@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from f227393 to 976b572 Compare July 7, 2026 13:48

@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: 3

🤖 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 @.github/actions/bot-autoassign/base.py:
- Around line 34-61: The GraphQL helper in get_issue_projects uses
self.github.requester.graphql_query with named variables, which requires a newer
PyGithub than the current floor permits. Update the project’s PyGithub minimum
version to 2.5.0+ wherever the dependency constraint is defined, or change
get_issue_projects to avoid the newer requester/graphql API and use only
supported calls. Keep the fix centered on the get_issue_projects path so the
dependency requirement matches the code actually used.

In @.github/actions/bot-autoassign/stale_pr_bot.py:
- Around line 504-506: The per-PR exception handling in stale_pr_bot.py is too
broad: the blanket re-raise in the PR processing loop now stops the entire stale
run on any single PR failure. Narrow the fail-fast behavior in the PR-processing
logic around the validate_pr_issues path so only verification/API or permission
failures are re-raised, and keep unrelated per-PR errors handled in a way that
lets the loop continue to the next PR. Use the existing error handling block
inside the PR iteration to distinguish the intended blast radius.

In @.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py:
- Around line 982-1001: The test in IssueAssignmentBot.validate_pr_issues only
covers the empty/early-continue path and never reaches the mixed-label check.
Update test_validate_pr_issues_invalid_labels to add a case with both a valid
label and a disallowed label (for example, bug plus wontfix or invalid) so the
later any(lbl in ["invalid", "wontfix"]) branch is exercised. Keep the existing
mock_pr and mock_issue setup, and adjust mock_issue.labels to explicitly include
a mixed set before asserting the method returns false.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 5131bcfe-f36b-4350-819e-1d96164ac1c2

📥 Commits

Reviewing files that changed from the base of the PR and between f227393 and 976b572.

📒 Files selected for processing (8)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • docs/developer/reusable-github-utils.rst
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 3-5: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🔇 Additional comments (11)
.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (2)

918-925: LGTM!

GithubException(400, {...}, {}) matches the PyGithub GithubException(status, data, headers) constructor signature used across supported 2.x versions, so this test correctly exercises the error propagation path.


926-1094: LGTM!

Rest of TestPRValidation coverage (excluded authors, exempt association, missing/cross-org references, PR-vs-issue detection, closed-issue skip, project-membership checks, invalid-label/comment flow, and label removal on validation) aligns with the validate_pr_issues/handle_pull_request contract shown in base.py and issue_assignment_bot.py.

.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (1)

865-950: LGTM!

The four TestStalePRBotInvalidCheck cases correctly map onto the branches in process_stale_prs for the invalid-label recheck: label removal on revalidation, no-op under 24h, close-with-comment over 24h, and posting the missing warning comment.

.github/workflows/bot-autoassign-pr-issue-link.yml (1)

5-5: 🔒 Security & Privacy

pull_request_target widened to edited/ready_for_review.

zizmor flags pull_request_target as an inherently risky trigger since it runs with base-repo permissions/secrets against untrusted PR metadata. The checkout step here (Line 31) doesn't check out the PR head ref, so arbitrary code execution risk is mitigated, but widening the trigger types does increase how often this privileged workflow runs on external PR content (title/body edits). Worth confirming the intended usage matches this exposure, and that no future step is added that checks out or evaluates PR head content.

Source: Linters/SAST tools

docs/developer/reusable-github-utils.rst (1)

76-82: LGTM!

Documentation updates accurately describe the new PR validation behavior (label/warning-comment/24h auto-close) and the expanded workflow trigger types, keeping docs in sync with the behavior change. As per coding guidelines, "Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change."

Also applies to: 177-177

.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/base.py (3)

12-16: LGTM!


193-252: LGTM!


168-175: 🩺 Stability & Availability

No change needed: handle_pull_request() returning False still bubbles up to main() and sys.exit(1), so the workflow fails for retry even though the exception is swallowed.

			> Likely an incorrect or invalid review comment.
.github/actions/bot-autoassign/issue_assignment_bot.py (1)

448-474: LGTM!

.github/actions/bot-autoassign/stale_pr_bot.py (1)

371-418: LGTM!

Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment thread .github/actions/bot-autoassign/stale_pr_bot.py Outdated
Comment thread .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 976b572 to cc80de8 Compare July 7, 2026 14:13

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for picking this up @prathmeshkulkarni-coder, I gave you access to https://github.com/openwisp/bot-testing-ground, please add the required logic there, start testing PRs there, you may need to create another github account to test the case in which a PR is created by someone who's not a collaborator (as you should be a collaborator of that repo there once you accept the invite).

You're free to test anything you need on that repository without approval.

Let's test all the supported cases and report each one here with a link to the test PR.

Comment thread .github/actions/bot-autoassign/base.py Outdated
f"Error fetching issue {owner}/{repo_name}#{issue_number}: {e}"
)
raise

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Too many blank lines, use blank lines sparingly.

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

Sure thanks i'll give you update after testing

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from cc80de8 to 035dcae Compare July 8, 2026 03:10

@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: 6

🤖 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 @.github/actions/bot-autoassign/base.py:
- Around line 96-123: Move the method-local imports in validate_pr_issues to the
module level: import extract_all_linked_issues and GithubException alongside the
other top-of-file imports in base.py, then remove the inline import statements
from the validation loop since there is no deferred-import need.
- Around line 39-55: The projectItems lookup in the GraphQL query is only
reading the first 10 items, which can miss the approved OpenWISP board and cause
valid PRs to be treated as invalid. Update the repository/issue fetch flow in
the bot-autoassign Base class to paginate projectItems using pageInfo and
endCursor, and keep requesting additional pages until all project items are
collected before checking the board title.

In @.github/actions/bot-autoassign/issue_assignment_bot.py:
- Around line 439-450: The control flow in issue_assignment_bot.py currently
auto-assigns linked issues before the PR validation check, allowing
`auto_assign_issues_from_pr()` to run ahead of `validate_pr_issues()` for
`opened`/`reopened`. Update the `handle_pull_request` path so
`validate_pr_issues()` runs first for relevant actions, and only call
`auto_assign_issues_from_pr()` when the PR is valid and not labeled invalid.
Keep the existing `action` handling, but reorder or gate the logic around
`pr_obj`, `is_valid`, `auto_assign_issues_from_pr`, and `validate_pr_issues` so
external PRs cannot assign issues before validation.

In @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py:
- Around line 863-950: Add a test in TestStalePRBotInvalidCheck that covers the
ValidationAPIError path in process_stale_prs by patching validate_pr_issues to
raise ValidationAPIError and asserting the exception is re-raised. Reuse the
existing mock_pr setup (invalid label, repo.get_pulls, and any comment state
needed) so the test exercises the same invalid-PR flow, and verify no label
removal, comment creation, or close/edit actions happen when
StalePRBot.process_stale_prs encounters this error.
- Line 915: Move the deferred timedelta import into the module’s top-level
import block in test_stale_pr_bot.py, since it is not needed lazily. Update the
existing datetime import near the top so timedelta is imported there, and remove
the local import in the test body. Use the datetime import statements and any
test helper/function around the stale PR bot tests as the reference points when
relocating it.

In `@docs/developer/reusable-github-utils.rst`:
- Around line 76-82: Clarify the PR validation description so “if the issue is
not resolved” clearly refers to the pull request still missing a validated issue
reference, not the linked GitHub issue. Update the wording in the PR validation
section of reusable-github-utils.rst to state that the 24-hour timer starts when
the warning comment is posted and that the PR is closed only if it still lacks a
validated issue reference after that period.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 75db9ec5-92d0-4841-9bf5-f8cb1da82544

📥 Commits

Reviewing files that changed from the base of the PR and between 976b572 and 035dcae.

📒 Files selected for processing (10)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • .github/workflows/reusable-bot-autoassign.yml
  • docs/developer/reusable-github-utils.rst
  • setup.py
📜 Review details
⏰ Context from checks skipped due to timeout. (14)
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{py,html,txt}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • setup.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

Files:

  • setup.py
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • setup.py
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)


[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 3-5: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🔇 Additional comments (10)
.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/base.py (2)

12-16: LGTM!


193-252: LGTM!

.github/actions/bot-autoassign/stale_pr_bot.py (1)

15-16: LGTM!

Also applies to: 375-431, 516-517

.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (1)

888-1100: LGTM!

.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (1)

863-901: LGTM!

Also applies to: 929-950

.github/workflows/bot-autoassign-pr-issue-link.yml (1)

5-5: LGTM!

.github/workflows/reusable-bot-autoassign.yml (1)

15-24: LGTM!

Also applies to: 45-46

docs/developer/reusable-github-utils.rst (1)

177-177: LGTM!

setup.py (1)

81-81: 🔒 Security & Privacy

PyGithub 2.5.0 is compatible here Requester.graphql_query still returns a (headers, data) tuple, and GithubException.status remains available for the 404 check.

Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment on lines +39 to +55
projectItems(first: 10) {
nodes {
project {
title
}
}
}
}
}
}
"""
variables = {"owner": owner, "repo": repo_name, "issueNumber": issue_number}
headers, result = self.github.requester.graphql_query(query, variables)
repo_data = result.get("data", {}).get("repository", {}) or {}
issue_data = repo_data.get("issue", {}) or {}
project_items = issue_data.get("projectItems", {}) or {}
nodes = project_items.get("nodes", []) or []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file outline ==\n'
ast-grep outline .github/actions/bot-autoassign/base.py --view expanded || true

printf '\n== relevant lines ==\n'
nl -ba .github/actions/bot-autoassign/base.py | sed -n '1,180p'

printf '\n== search projectItems and autoassign validation ==\n'
rg -n "projectItems|OpenWISP|invalid label|auto-clos|approved contributor|project board" .github/actions/bot-autoassign -S || true

Repository: openwisp/openwisp-utils

Length of output: 740


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== base.py excerpt ==\n'
awk 'NR>=1 && NR<=140 {printf "%4d  %s\n", NR, $0}' .github/actions/bot-autoassign/base.py

printf '\n== project validation references ==\n'
rg -n "projectItems|pageInfo|endCursor|hasNextPage|get_issue_projects|validate_pr_issues" .github/actions/bot-autoassign -S

Repository: openwisp/openwisp-utils

Length of output: 11495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== validate_pr_issues excerpt around project checks ==\n'
awk 'NR>=150 && NR<=210 {printf "%4d  %s\n", NR, $0}' .github/actions/bot-autoassign/base.py

printf '\n== tests around get_issue_projects / validation ==\n'
awk 'NR>=880 && NR<=1065 {printf "%4d  %s\n", NR, $0}' .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py

Repository: openwisp/openwisp-utils

Length of output: 10896


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path('.github/actions/bot-autoassign/base.py').read_text()
start = text.index('    def validate_pr_issues')
print(text[start:start+2600])
PY

Repository: openwisp/openwisp-utils

Length of output: 2762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path('.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py').read_text().splitlines()
for i in range(888, 1060):
    if i < len(text):
        print(f"{i+1:4d}  {text[i]}")
PY

Repository: openwisp/openwisp-utils

Length of output: 7455


🌐 Web query:

GitHub GraphQL issue projectItems connection first page pagination pageInfo endCursor documentation

💡 Result:

To paginate through the projectItems connection (available via ProjectV2.items) in the GitHub GraphQL API, you must use cursor-based pagination with the pageInfo object [1][2]. The items field accepts first (to specify the page size) and after (to specify the cursor) arguments [3][2]. The pageInfo object returned by the connection provides the necessary fields to navigate the dataset [1][3]: - endCursor: The opaque string representing the cursor of the last item in the current page [4][3]. - hasNextPage: A boolean indicating whether more items are available [3][2]. To retrieve subsequent pages, use the endCursor value from your current response as the after argument in your next request [2]. Example pattern: query { node(id: "PROJECT_ID") {... on ProjectV2 { items(first: 100, after: null) { pageInfo { endCursor hasNextPage } nodes { id #... rest of your query } } } } } In the next iteration, replace the after argument with the endCursor string obtained from the previous response [2]. Continue this process as long as hasNextPage remains true [3][2].

Citations:


Paginate projectItems here

first: 10 can miss the approved OpenWISP board and falsely leave a valid PR marked invalid/auto-closed. Use pageInfo/endCursor pagination instead of relying on a single page.

🤖 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 @.github/actions/bot-autoassign/base.py around lines 39 - 55, The
projectItems lookup in the GraphQL query is only reading the first 10 items,
which can miss the approved OpenWISP board and cause valid PRs to be treated as
invalid. Update the repository/issue fetch flow in the bot-autoassign Base class
to paginate projectItems using pageInfo and endCursor, and keep requesting
additional pages until all project items are collected before checking the board
title.

Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment on lines +96 to +123
# For external contributors, extract linked issues
from utils import extract_all_linked_issues

pr_body = pr.body if isinstance(pr.body, str) else ""
linked_issues = extract_all_linked_issues(pr_body, self.repository_name)
if not linked_issues:
print("No linked issues found in PR body for external contributor.")
return False

current_org = self.repository_name.split("/")[0].lower()
required_projects = [
"OpenWISP Contributor's Board",
"OpenWISP Priorities for next releases",
]

for owner, repo_name, issue_number in linked_issues:
# 1. Check if the issue belongs to the same organization
if owner.lower() != current_org:
print(
f"Issue {owner}/{repo_name}#{issue_number} does not belong "
f"to organization {current_org}, skipping validation."
)
continue

try:
from github import GithubException

target_repo = self.github.get_repo(f"{owner}/{repo_name}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move method-local imports to module scope.

extract_all_linked_issues and GithubException are imported inside validate_pr_issues without an evident deferred-import requirement.

♻️ Proposed cleanup
+from github import GithubException
+from utils import extract_all_linked_issues
+
 ...
-        from utils import extract_all_linked_issues
-
         pr_body = pr.body if isinstance(pr.body, str) else ""
 ...
-                from github import GithubException
-
                 target_repo = self.github.get_repo(f"{owner}/{repo_name}")

As per coding guidelines, **/*.py files should place imports at the top unless deferral is necessary.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# For external contributors, extract linked issues
from utils import extract_all_linked_issues
pr_body = pr.body if isinstance(pr.body, str) else ""
linked_issues = extract_all_linked_issues(pr_body, self.repository_name)
if not linked_issues:
print("No linked issues found in PR body for external contributor.")
return False
current_org = self.repository_name.split("/")[0].lower()
required_projects = [
"OpenWISP Contributor's Board",
"OpenWISP Priorities for next releases",
]
for owner, repo_name, issue_number in linked_issues:
# 1. Check if the issue belongs to the same organization
if owner.lower() != current_org:
print(
f"Issue {owner}/{repo_name}#{issue_number} does not belong "
f"to organization {current_org}, skipping validation."
)
continue
try:
from github import GithubException
target_repo = self.github.get_repo(f"{owner}/{repo_name}")
from github import GithubException
from utils import extract_all_linked_issues
# For external contributors, extract linked issues
pr_body = pr.body if isinstance(pr.body, str) else ""
linked_issues = extract_all_linked_issues(pr_body, self.repository_name)
if not linked_issues:
print("No linked issues found in PR body for external contributor.")
return False
current_org = self.repository_name.split("/")[0].lower()
required_projects = [
"OpenWISP Contributor's Board",
"OpenWISP Priorities for next releases",
]
for owner, repo_name, issue_number in linked_issues:
# 1. Check if the issue belongs to the same organization
if owner.lower() != current_org:
print(
f"Issue {owner}/{repo_name}#{issue_number} does not belong "
f"to organization {current_org}, skipping validation."
)
continue
try:
target_repo = self.github.get_repo(f"{owner}/{repo_name}")
🤖 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 @.github/actions/bot-autoassign/base.py around lines 96 - 123, Move the
method-local imports in validate_pr_issues to the module level: import
extract_all_linked_issues and GithubException alongside the other top-of-file
imports in base.py, then remove the inline import statements from the validation
loop since there is no deferred-import need.

Source: Coding guidelines

Comment thread .github/actions/bot-autoassign/issue_assignment_bot.py Outdated
Comment on lines +863 to +950


class TestStalePRBotInvalidCheck:
def test_invalid_pr_becomes_valid(self, bot_env):
bot = StalePRBot()
mock_label = Mock()
mock_label.name = "invalid"

mock_pr = Mock()
mock_pr.labels = [mock_label]
mock_pr.number = 100
bot_env["repo"].get_pulls.return_value = [mock_pr]

with patch.object(bot, "validate_pr_issues", return_value=True):
assert bot.process_stale_prs()
mock_pr.remove_from_labels.assert_called_once_with("invalid")
mock_pr.edit.assert_not_called()

def test_invalid_pr_still_invalid_under_24h(self, bot_env):
bot = StalePRBot()
mock_label = Mock()
mock_label.name = "invalid"

mock_pr = Mock()
mock_pr.labels = [mock_label]
mock_pr.number = 100
bot_env["repo"].get_pulls.return_value = [mock_pr]

mock_comment = Mock()
mock_comment.user.login = bot.bot_login
mock_comment.body = "<!-- bot:invalid_unvalidated_issue --> Warning comment"
mock_comment.created_at = datetime.now(timezone.utc)
mock_pr.get_issue_comments.return_value = [mock_comment]

with patch.object(bot, "validate_pr_issues", return_value=False):
assert bot.process_stale_prs()
mock_pr.remove_from_labels.assert_not_called()
mock_pr.edit.assert_not_called()

def test_invalid_pr_still_invalid_over_24h_closes(self, bot_env):
bot = StalePRBot()
mock_label = Mock()
mock_label.name = "invalid"

mock_pr = Mock()
mock_pr.labels = [mock_label]
mock_pr.number = 100
bot_env["repo"].get_pulls.return_value = [mock_pr]

mock_comment = Mock()
mock_comment.user.login = bot.bot_login
mock_comment.body = "<!-- bot:invalid_unvalidated_issue --> Warning comment"
from datetime import timedelta

mock_comment.created_at = datetime.now(timezone.utc) - timedelta(hours=25)
mock_pr.get_issue_comments.return_value = [mock_comment]

with patch.object(bot, "validate_pr_issues", return_value=False):
assert bot.process_stale_prs()
mock_pr.remove_from_labels.assert_not_called()
mock_pr.create_issue_comment.assert_called_once()
assert (
"automatically closed" in mock_pr.create_issue_comment.call_args[0][0]
)
mock_pr.edit.assert_called_once_with(state="closed")

def test_invalid_pr_missing_warning_comment_posts_warning(self, bot_env):
bot = StalePRBot()
mock_label = Mock()
mock_label.name = "invalid"

mock_pr = Mock()
mock_pr.labels = [mock_label]
mock_pr.number = 100
mock_pr.user.login = "contributor"
bot_env["repo"].get_pulls.return_value = [mock_pr]

mock_pr.get_issue_comments.return_value = []

with patch.object(bot, "validate_pr_issues", return_value=False):
assert bot.process_stale_prs()
mock_pr.remove_from_labels.assert_not_called()
mock_pr.create_issue_comment.assert_called_once()
assert (
"<!-- bot:invalid_unvalidated_issue -->"
in mock_pr.create_issue_comment.call_args[0][0]
)
mock_pr.edit.assert_not_called()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a test for ValidationAPIError re-raise when validate_pr_issues raises.

process_stale_prs re-raises ValidationAPIError when validate_pr_issues throws, but no test in TestStalePRBotInvalidCheck covers this error path. Per the PR objectives, API or permission errors should fail the workflow for retry. As per coding guidelines, tests should be added for every behavior change.

Would you like me to generate the missing test case?

🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py around lines 863 -
950, Add a test in TestStalePRBotInvalidCheck that covers the ValidationAPIError
path in process_stale_prs by patching validate_pr_issues to raise
ValidationAPIError and asserting the exception is re-raised. Reuse the existing
mock_pr setup (invalid label, repo.get_pulls, and any comment state needed) so
the test exercises the same invalid-PR flow, and verify no label removal,
comment creation, or close/edit actions happen when StalePRBot.process_stale_prs
encounters this error.

Source: Coding guidelines

mock_comment = Mock()
mock_comment.user.login = bot.bot_login
mock_comment.body = "<!-- bot:invalid_unvalidated_issue --> Warning comment"
from datetime import timedelta

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move timedelta import to the top of the file.

The local from datetime import timedelta import violates the project's coding guidelines, which require imports at the top of the file unless deferral is necessary. timedelta has no deferral justification here.

As per coding guidelines: "Place imports at the top of the file; only defer imports when necessary."

♻️ Proposed fix
-        from datetime import timedelta
-
         mock_comment.created_at = datetime.now(timezone.utc) - timedelta(hours=25)

Add timedelta to the existing top-level datetime import (if not already present):

-from datetime import datetime, timezone
+from datetime import datetime, timedelta, timezone
🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py at line 915, Move
the deferred timedelta import into the module’s top-level import block in
test_stale_pr_bot.py, since it is not needed lazily. Update the existing
datetime import near the top so timedelta is imported there, and remove the
local import in the test body. Use the datetime import statements and any test
helper/function around the stale PR bot tests as the reference points when
relocating it.

Source: Coding guidelines

Comment thread docs/developer/reusable-github-utils.rst
@prathmeshkulkarni-coder

prathmeshkulkarni-coder commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@nemesifier , the bot is working but not as I thought

  1. test: collaborator pr test change bot-testing-ground#110 the bot didn't give invalid tag because i am collaborator
  2. Create test.md bot-testing-ground#111 here bot has given invalid tag because the PR didn't link to any issue (Testing for 24hr close PR)
  3. Test2 bot-testing-ground#114 here i was trying to start bot by linking valid issue and trying to remove invalid label but it didn't work
  4. Test3 bot-testing-ground#115 here i linked the PR to the valid issue but the bot still add invalid tag

I am still working on the bot

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

I think here is the problem
Screenshot from 2026-07-08 12-57-51

Run if [ -n "$GITHUB_EVENT_PATH" ]; then
Issue Assignment Bot starting for event: pull_request_target
No linked issues found in PR body
Issue openwisp/openwisp-utils#709 is not assigned to any required project, skipping validation.
Added 'invalid' label to PR #115
Posted unvalidated issue warning comment on PR #115
Issue Assignment Bot completed

the bot can't able to access project board and giving false flag to issue

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@prathmeshkulkarni-coder is it a permission issue? Can you at least check if it can detect whether it is assigned to any project or not and if so, know at least the project ID? We don't need to access the project board, (infact, we have to avoid that completely as it's a waste of compute) we just need to know it is assigned to one of the two designated boards.

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@nemesifier
Yes it is permission issue, the github can't able to give anything in return .It is very strict
there are two ways either giving permission or If member manually added label to issue like bug or accepted or verified
In second case if member add label then we can remove the project's board code completely.

@nemesifier

Copy link
Copy Markdown
Member

I updated the permissions of the github app to allow reading projects, please double check on your end.

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 035dcae to 52e3df0 Compare July 8, 2026 17:42
@coderabbitai coderabbitai Bot added bug and removed enhancement labels Jul 8, 2026
@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 52e3df0 to c843e8d Compare July 8, 2026 17:50

@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

♻️ Duplicate comments (3)
docs/developer/reusable-github-utils.rst (1)

76-82: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify "if the issue is not resolved" wording.

"Automatically closed after 24 hours if the issue is not resolved" is ambiguous — a contributor could misread "the issue" as the linked GitHub issue rather than the PR's missing-reference problem. The 24-hour clock starts from the warning comment, and the closure condition is that the PR still lacks a validated issue reference. This was flagged in a previous review and remains unresolved.

📝 Proposed wording fix
-  warning comment, and are automatically closed after 24 hours if the
-  issue is not resolved.
+  warning comment, and are automatically closed after 24 hours if the
+  PR is not updated to reference a validated issue.
🤖 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 `@docs/developer/reusable-github-utils.rst` around lines 76 - 82, Update the PR
validation description to remove the ambiguous “if the issue is not resolved”
wording and make it explicit that the 24-hour timer starts from the warning
comment and the PR is closed only if it still lacks a validated issue reference.
Use the existing “PR validation” section in reusable-github-utils.rst to
rephrase the closure condition clearly so it refers to the PR’s
missing-reference problem, not the linked GitHub issue.
.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (2)

863-950: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for ValidationAPIError re-raise when validate_pr_issues raises.

process_stale_prs re-raises ValidationAPIError when validate_pr_issues throws, but no test in TestStalePRBotInvalidCheck covers this error path. Per the PR objectives, API or permission errors should fail the workflow for retry. This was flagged in a previous review and remains unresolved.

As per coding guidelines: "Add or update tests for every behavior change."

Would you like me to generate the missing test case?

🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py around lines 863 -
950, Add a test in TestStalePRBotInvalidCheck to cover the error path where
StalePRBot.process_stale_prs re-raises ValidationAPIError when
validate_pr_issues throws. Mock a PR with the invalid label and patch
bot.validate_pr_issues to raise ValidationAPIError, then assert the exception
propagates instead of being swallowed. Place the new case alongside the existing
invalid-pr tests so it is easy to find with process_stale_prs and
validate_pr_issues.

Source: Coding guidelines


915-915: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move timedelta import to the top of the file.

The local from datetime import timedelta import still violates the project's coding guidelines, which require imports at the top of the file unless deferral is necessary. timedelta has no deferral justification here. This was flagged in a previous review and remains unresolved.

As per coding guidelines: "Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)."

♻️ Proposed fix
-        from datetime import timedelta
-
         mock_comment.created_at = datetime.now(timezone.utc) - timedelta(hours=25)

Add timedelta to the existing top-level datetime import:

-from datetime import datetime, timezone
+from datetime import datetime, timedelta, timezone
🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py at line 915, The
test file still has a deferred `timedelta` import inside the test scope, which
violates the import-at-top convention. Move `timedelta` into the existing
top-level `datetime` import near the other imports in `test_stale_pr_bot.py`,
and remove the local import so `timedelta` is referenced only from the
module-level import.

Source: Coding guidelines

🤖 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 @.github/actions/bot-autoassign/base.py:
- Around line 59-67: Remove the unnecessary intra-method blank lines in
base.py’s bot auto-assign logic, especially around the repository/project item
extraction in the method that builds repo_data, issue_data, v2_items,
classic_items, and nodes. Tighten the body by deleting the blank lines that
contain trailing whitespace so the method has no empty interior separators and
passes openwisp-qa-format/flake8 W293.
- Around line 58-72: The GraphQL fetch in the project-title lookup path
currently swallows API failures by returning an empty list; update the logic in
the method that calls github.requester.graphql_query to check the top-level
errors field in the GraphQL result and raise an exception when present instead
of continuing. Keep the existing parsing of
issue_data/projectV2Items/projectItems for successful responses, but fail fast
so validate_pr_issues does not treat permission or API errors as “no projects.”

---

Duplicate comments:
In @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py:
- Around line 863-950: Add a test in TestStalePRBotInvalidCheck to cover the
error path where StalePRBot.process_stale_prs re-raises ValidationAPIError when
validate_pr_issues throws. Mock a PR with the invalid label and patch
bot.validate_pr_issues to raise ValidationAPIError, then assert the exception
propagates instead of being swallowed. Place the new case alongside the existing
invalid-pr tests so it is easy to find with process_stale_prs and
validate_pr_issues.
- Line 915: The test file still has a deferred `timedelta` import inside the
test scope, which violates the import-at-top convention. Move `timedelta` into
the existing top-level `datetime` import near the other imports in
`test_stale_pr_bot.py`, and remove the local import so `timedelta` is referenced
only from the module-level import.

In `@docs/developer/reusable-github-utils.rst`:
- Around line 76-82: Update the PR validation description to remove the
ambiguous “if the issue is not resolved” wording and make it explicit that the
24-hour timer starts from the warning comment and the PR is closed only if it
still lacks a validated issue reference. Use the existing “PR validation”
section in reusable-github-utils.rst to rephrase the closure condition clearly
so it refers to the PR’s missing-reference problem, not the linked GitHub issue.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 57b86e5f-f6e1-4398-9559-998ed5e377e8

📥 Commits

Reviewing files that changed from the base of the PR and between 035dcae and 52e3df0.

📒 Files selected for processing (10)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • .github/workflows/reusable-bot-autoassign.yml
  • docs/developer/reusable-github-utils.rst
  • setup.py
📜 Review details
⏰ Context from checks skipped due to timeout. (14)
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.0.0
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{py,html,txt}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • setup.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

Files:

  • setup.py
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • setup.py
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)


[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 3-5: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🔇 Additional comments (15)
.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (1)

888-1100: LGTM!

.github/workflows/bot-autoassign-pr-issue-link.yml (1)

5-5: LGTM!

.github/workflows/reusable-bot-autoassign.yml (1)

15-24: LGTM!

Also applies to: 45-46

docs/developer/reusable-github-utils.rst (1)

177-177: LGTM!

setup.py (1)

81-81: 🔒 Security & Privacy

PyGithub 2.5.0 already matches the expected GraphQL API
requester.graphql_query() returns the (headers, data) tuple that get_issue_projects unpacks, so this pin is fine.

			> Likely an incorrect or invalid review comment.
.github/actions/bot-autoassign/base.py (6)

39-46: projectItems/projectV2Items fetch only the first 10 items.

first: 10 can miss the approved OpenWISP board when an issue is on more boards, falsely marking a valid PR invalid. Paginate via pageInfo/endCursor.


107-146: Move method-local imports to module scope.

extract_all_linked_issues (line 108) and GithubException (line 132) are imported inside the method without a deferred-import need.

As per coding guidelines, **/*.py files should place imports at the top unless deferral is necessary.

Source: Coding guidelines


12-16: LGTM!


74-105: LGTM!


148-202: LGTM!


204-263: LGTM!

.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/issue_assignment_bot.py (2)

439-450: Validate before auto-assigning linked issues.

For opened/reopened, auto_assign_issues_from_pr() (Line 440) still runs before validate_pr_issues() (Line 450) — the block falls through rather than gating on validity. An external PR that references an unvalidated issue (open but not on a required project board) can still get that issue auto-assigned to the author before the PR is labeled invalid, since auto_assign_issues_from_prget_valid_linked_issues does not check project-board membership.

Gate the auto-assign on validity, e.g. load pr_obj/is_valid first and only call auto_assign_issues_from_pr() when valid.


451-474: LGTM!

.github/actions/bot-autoassign/stale_pr_bot.py (1)

15-16: LGTM!

Also applies to: 375-431, 516-517

Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment on lines +59 to +67
repo_data = result.get("data", {}).get("repository", {}) or {}
issue_data = repo_data.get("issue", {}) or {}

# Combine both V2 and Classic projects just in case
v2_items = issue_data.get("projectV2Items", {}) or {}
classic_items = issue_data.get("projectItems", {}) or {}

nodes = (v2_items.get("nodes", []) or []) + (classic_items.get("nodes", []) or [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove blank lines with trailing whitespace inside the method body.

Lines 61, 65 (and the interior blank at 67) are unnecessary intra-body blank lines and carry trailing whitespace, which will fail openwisp-qa-format (flake8 W293).

As per coding guidelines: "Avoid unnecessary blank lines inside function and method bodies" and "Run openwisp-qa-format after editing".

🤖 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 @.github/actions/bot-autoassign/base.py around lines 59 - 67, Remove the
unnecessary intra-method blank lines in base.py’s bot auto-assign logic,
especially around the repository/project item extraction in the method that
builds repo_data, issue_data, v2_items, classic_items, and nodes. Tighten the
body by deleting the blank lines that contain trailing whitespace so the method
has no empty interior separators and passes openwisp-qa-format/flake8 W293.

Source: Coding guidelines

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch 2 times, most recently from ac4d021 to 2e39c9e Compare July 8, 2026 18:42
@coderabbitai coderabbitai Bot added enhancement and removed bug labels Jul 8, 2026

@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: 1

♻️ Duplicate comments (3)
.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (2)

915-915: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move timedelta import to the top of the file.

The local from datetime import timedelta inside the test body violates the project's coding guidelines, which require imports at the top of the file unless deferral is necessary. timedelta has no deferral justification here.

As per coding guidelines: "Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)"

♻️ Proposed fix
-        from datetime import timedelta
-
         mock_comment.created_at = datetime.now(timezone.utc) - timedelta(hours=25)

Add timedelta to the existing top-level datetime import:

-from datetime import datetime, timezone
+from datetime import datetime, timedelta, timezone
🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py at line 915, Move
the deferred timedelta import in the test body to the module-level imports to
match the project’s import rules. Update the existing top-of-file datetime
import in test_stale_pr_bot.py to include timedelta, and remove the local import
from the test method so the test logic still uses timedelta from the top-level
scope.

Source: Coding guidelines


863-950: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for ValidationAPIError re-raise when validate_pr_issues raises.

process_stale_prs re-raises ValidationAPIError when validate_pr_issues throws, but no test in TestStalePRBotInvalidCheck covers this error path. Per the PR objectives, API or permission errors should fail the workflow for retry. As per coding guidelines, tests should be added for every behavior change.

🤖 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 @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py around lines 863 -
950, `TestStalePRBotInvalidCheck` is missing coverage for the
`ValidationAPIError` re-raise path in `StalePRBot.process_stale_prs`. Add a test
that patches `validate_pr_issues` to raise `ValidationAPIError`, then assert
`process_stale_prs` propagates that exception and does not call label removal,
issue comments, or `edit`. Use the existing `mock_pr`,
`bot_env["repo"].get_pulls`, and `process_stale_prs` setup to keep the new test
aligned with the other invalid-PR cases.

Source: Coding guidelines

docs/developer/reusable-github-utils.rst (1)

74-79: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Clarify "if the issue is not resolved" wording.

Line 78-79 still says "automatically closed after 24 hours if the issue is not resolved." A contributor could misread "the issue" as the linked GitHub issue rather than the PR's missing-reference problem. While the detailed section at lines 110-116 is clear, this summary line remains ambiguous.

📝 Proposed wording fix
-  warning comment, and are automatically closed after 24 hours if the
-  issue is not resolved.
+  warning comment, and are automatically closed after 24 hours if the
+  PR is not updated to reference a validated issue.
🤖 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 `@docs/developer/reusable-github-utils.rst` around lines 74 - 79, Clarify the
PR validation summary text so “if the issue is not resolved” clearly refers to
the pull request’s invalid-reference problem, not the linked GitHub issue.
Update the summary wording in the reusable-github-utils documentation near the
PR validation section to explicitly say the pull request is closed after 24
hours if the missing/invalid reference is not fixed, keeping it consistent with
the detailed explanation below.
🤖 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 @.github/actions/bot-autoassign/base.py:
- Around line 66-84: The GraphQL response handling in the repository lookup path
can still crash when result["data"] is null because repo_data is read via
result.get("data", {}).get("repository"); update the parsing in the same flow
that checks repo_data, issue_node, and project_items to use a safe fallback like
result.get("data") or {} before accessing nested fields. Also add a test
covering the {"data": null, "errors": [...]} shape in the GraphQL error path,
since the existing error test only exercises GithubException.

---

Duplicate comments:
In @.github/actions/bot-autoassign/tests/test_stale_pr_bot.py:
- Line 915: Move the deferred timedelta import in the test body to the
module-level imports to match the project’s import rules. Update the existing
top-of-file datetime import in test_stale_pr_bot.py to include timedelta, and
remove the local import from the test method so the test logic still uses
timedelta from the top-level scope.
- Around line 863-950: `TestStalePRBotInvalidCheck` is missing coverage for the
`ValidationAPIError` re-raise path in `StalePRBot.process_stale_prs`. Add a test
that patches `validate_pr_issues` to raise `ValidationAPIError`, then assert
`process_stale_prs` propagates that exception and does not call label removal,
issue comments, or `edit`. Use the existing `mock_pr`,
`bot_env["repo"].get_pulls`, and `process_stale_prs` setup to keep the new test
aligned with the other invalid-PR cases.

In `@docs/developer/reusable-github-utils.rst`:
- Around line 74-79: Clarify the PR validation summary text so “if the issue is
not resolved” clearly refers to the pull request’s invalid-reference problem,
not the linked GitHub issue. Update the summary wording in the
reusable-github-utils documentation near the PR validation section to explicitly
say the pull request is closed after 24 hours if the missing/invalid reference
is not fixed, keeping it consistent with the detailed explanation below.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: c6b24465-da5e-4c0e-9f7d-88f9d16ac2aa

📥 Commits

Reviewing files that changed from the base of the PR and between 52e3df0 and 2e39c9e.

📒 Files selected for processing (10)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • .github/workflows/reusable-bot-autoassign.yml
  • docs/developer/reusable-github-utils.rst
  • setup.py
📜 Review details
⏰ Context from checks skipped due to timeout. (14)
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{py,html,txt}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • setup.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

Files:

  • setup.py
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/reusable-github-utils.rst
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • setup.py
🪛 ast-grep (0.44.1)
.github/actions/bot-autoassign/utils.py

[warning] 133-133: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 133-133: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: re.findall(pattern, pr_body, re.IGNORECASE)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

🪛 zizmor (1.26.1)
.github/workflows/bot-autoassign-pr-issue-link.yml

[error] 9-9: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level

(excessive-permissions)


[error] 10-10: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)


[warning] 9-9: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🔇 Additional comments (37)
.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py (7)

888-919: LGTM!


922-983: LGTM!


984-1012: LGTM!


1013-1039: LGTM!


1040-1066: LGTM!


1067-1105: LGTM!


1107-1158: LGTM!

.github/actions/bot-autoassign/tests/test_stale_pr_bot.py (4)

863-879: LGTM!


881-901: LGTM!


902-928: LGTM!


929-950: LGTM!

.github/workflows/bot-autoassign-pr-issue-link.yml (2)

5-10: LGTM!


47-47: LGTM!

.github/workflows/reusable-bot-autoassign.yml (3)

15-29: LGTM!


50-51: LGTM!


69-69: LGTM!

docs/developer/reusable-github-utils.rst (13)

1-19: LGTM!


81-120: LGTM!


121-181: LGTM!


207-218: LGTM!


278-281: LGTM!


311-314: LGTM!


328-351: LGTM!


374-377: LGTM!


388-401: LGTM!


454-496: LGTM!


596-642: LGTM!


687-690: LGTM!


755-762: LGTM!

setup.py (1)

81-81: LGTM!

.github/actions/bot-autoassign/base.py (3)

51-51: 🎯 Functional Correctness

Pagination gap persists. projectItems(first: 20) still reads only a single page; an issue assigned to more than 20 project items could miss the approved OpenWISP board and be treated as unvalidated. Use pageInfo/endCursor pagination.


126-126: 📐 Maintainability & Code Quality

Move method-local imports to module scope. extract_all_linked_issues (Line 126) and GithubException (Line 149) have no deferred-import requirement and should be imported at the top of the file. As per coding guidelines: "Place imports at the top of the file; only defer imports when necessary".

Also applies to: 149-160

Source: Coding guidelines


5-24: LGTM!

Also applies to: 41-44, 216-275

.github/actions/bot-autoassign/issue_assignment_bot.py (2)

446-449: 🗄️ Data Integrity & Integration

Auto-assignment still runs before validation. auto_assign_issues_from_pr() (Line 447) executes unconditionally before validate_pr_issues() (Line 449), so an external PR referencing an unvalidated issue can still assign that issue to the author before the PR is labeled invalid. Gate auto_assign_issues_from_pr() on is_valid.


439-445: LGTM!

Also applies to: 450-473

.github/actions/bot-autoassign/utils.py (1)

115-149: LGTM!

.github/actions/bot-autoassign/stale_pr_bot.py (1)

4-13: LGTM!

Also applies to: 372-427, 513-514

Comment on lines +66 to +84
repo_data = result.get("data", {}).get("repository")
if not repo_data:
raise ValueError(
f"GraphQL could not access repository {owner}/{repo_name}; "
"possible GitHub API or permission error"
)
issue_node = repo_data.get("issue")
if issue_node is None:
raise ValueError(
f"GraphQL could not access issue {owner}/{repo_name}#{issue_number}; "
"possible GitHub API or permission error"
)
project_items = issue_node.get("projectItems")
if project_items is None:
raise ValueError(
f"GraphQL could not read project assignments for "
f"{owner}/{repo_name}#{issue_number}; "
"possible GitHub API or permission error"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('.github/actions/bot-autoassign/base.py')
lines = p.read_text().splitlines()
for i in range(1, min(len(lines), 260)+1):
    if 55 <= i <= 95:
        print(f"{i:4d}: {lines[i-1]}")
PY

printf '\n--- search usage/tests ---\n'
rg -n "get_issue_projects|GraphQL could not access repository|possible GitHub API or permission error|result.get\\(\"data\"" .github/actions/bot-autoassign -S

Repository: openwisp/openwisp-utils

Length of output: 4099


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tests around get_issue_projects ---'
sed -n '920,990p' .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py

printf '\n%s\n' '--- graphql_query definitions/usages ---'
rg -n "def graphql_query|graphql_query\\(" .github/actions/bot-autoassign -S

Repository: openwisp/openwisp-utils

Length of output: 3078


🌐 Web query:

GitHub GraphQL API error response data null errors response shape documentation

💡 Result:

When the GitHub GraphQL API encounters errors, the response structure follows the standard GraphQL specification [1][2]. A response containing errors will include a JSON object with a top-level errors key, and the data key may be present or set to null depending on the nature of the error [1][3]. The standard error response structure is as follows: { "data": null, "errors": [ { "message": "Error description message", "locations": [ { "line": 1, "column": 1 } ] } ] } Key aspects of the error response shape: 1. Data Key: If the error is severe enough to prevent execution of the query (such as a validation or syntax error), the data key will typically be null [1][3]. However, in GraphQL, partial successes are possible; if some parts of a query succeed while others fail, the data key may contain the successfully retrieved information alongside an errors array containing the specific issues [1]. 2. Errors Array: This is a list of error objects [1]. Each object typically contains a message field describing the error [4][1]. 3. Additional Fields: Depending on the type of error, the error objects may include a locations field (indicating where in the query the error occurred) or other debugging information such as reference codes for unexpected system bugs [1][3]. GitHub recommends checking for the presence of the errors key in the response before attempting to use the returned data in production environments [1][5].

Citations:


Handle GraphQL responses with data: null result.get("data", {}) still yields None for {"data": null, "errors": [...]}, so .get("repository") raises AttributeError before the intended ValueError. Use (result.get("data") or {}) and add a test for the null-data error shape; the current GraphQL error test only raises GithubException.

🤖 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 @.github/actions/bot-autoassign/base.py around lines 66 - 84, The GraphQL
response handling in the repository lookup path can still crash when
result["data"] is null because repo_data is read via result.get("data",
{}).get("repository"); update the parsing in the same flow that checks
repo_data, issue_node, and project_items to use a safe fallback like
result.get("data") or {} before accessing nested fields. Also add a test
covering the {"data": null, "errors": [...]} shape in the GraphQL error path,
since the existing error test only exercises GithubException.

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch 6 times, most recently from ec438f1 to 367db15 Compare July 8, 2026 20:17
@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

The issue is still there i am debugging the issue by checking what does the bot getting from project i hope it will be solve

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 367db15 to 6efff15 Compare July 8, 2026 20:24
@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@nemesifier , I got the bug
image
Here's the raw API response as proof:

{'data': {'repository': {'issue': {'projectItems': {'nodes': []}}}}}"

here the bot is getting no data from project and i also checked it for graphql V2 it is valid .
Can you check permission again.

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@nemesifier , the PR get close by itself after 24hr
The project board problem is still there i will look for another solution .

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 6efff15 to baaaa01 Compare July 10, 2026 15:28
@kilo-code-bot

kilo-code-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review since the last reviewed commit (991d6ee). This update migrates the issue-projects lookup in get_issue_projects from the projectsV2 GraphQL connection to projectItems { project { title } }, adds an explicit GraphQL errors check that raises a clear ValueError, and makes project-title extraction null-safe via node.get("project") or {}. The corresponding tests in test_issue_assignment_bot.py were updated consistently to the new response shape.

The changed code is correct and null-safe on all changed lines: the GraphQL migration is valid, the added error handling short-circuits before parsing, and existing behavior tests were updated to match (success, single-project, GraphQL error, null projectItems, and null issue cases). No critical bugs or security vulnerabilities were found on the changed lines.

Under lenient review scope, only critical bugs and security vulnerabilities are flagged; none apply here. Previously noted non-blocking concerns live on files/lines unchanged in this update and remain tracked by existing active review comments, so they are not duplicated.

Files Reviewed (2 files, incremental)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
Previous Review Summaries (5 snapshots, latest commit 991d6ee)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 991d6ee)

Status: No Issues Found | Recommendation: Merge

Incremental review since the last reviewed commit (ba9f9e7). The only change in this update is a test-only formatting change in test_issue_assignment_bot.py (TestPRValidation.test_get_issue_projects_success): a mocked projectsV2.nodes[] entry was collapsed from a multi-line dict onto a single line ({"title": "OpenWISP Contributor's Board"}).

The change is purely cosmetic — the mock data is semantically identical and remains self-consistent with get_issue_projects in base.py. No critical bugs or security vulnerabilities were found on the changed lines.

Previously noted non-blocking concerns live on files/lines unchanged in this update and remain tracked by existing active review comments, so they are not duplicated here.

Files Reviewed (1 file, incremental)
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py

Previous review (commit ba9f9e7)

Status: No Issues Found | Recommendation: Merge

Incremental review since the last reviewed commit (3691a26). The only change in this update is test-only: it updates the mocked GraphQL responses in test_issue_assignment_bot.py (TestPRValidation) to match the production projectsV2 { nodes { title } } query and parsing in base.py — replacing the old projectItems { nodes { project { title } } } shape with projectsV2.nodes[].title for the success cases and projectsV2: None for the null case.

The updated mocks are self-consistent with get_issue_projects in base.py (reads projectsV2nodesnode.get("title")), so the tests correctly exercise the current code path. No critical bugs or security vulnerabilities were found on the changed lines.

Previously noted non-blocking concerns live on files/lines unchanged in this update and remain tracked by existing active review comments, so they are not duplicated here.

Files Reviewed (1 file, incremental)
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py

Previous review (commit 3691a26)

Status: No Issues Found | Recommendation: Merge

Incremental review since the last reviewed commit (f1b7ee1). The only change in this update refactors the GraphQL query in get_issue_projects (.github/actions/bot-autoassign/base.py) from projectItems { nodes { project { title } } } to projectsV2 { nodes { title } }, and updates the response parsing (projects_v2.get("nodes") and node.get("title")) to match. The query and parsing logic are self-consistent and the null/permission-error guards are preserved. No critical bugs or security vulnerabilities were found on the changed lines.

Previously noted non-blocking concerns live on files/lines unchanged in this update and remain tracked by existing active review comments, so they are not duplicated here.

Files Reviewed (1 file, incremental)
  • .github/actions/bot-autoassign/base.py

Previous review (commit f1b7ee1)

Status: No Issues Found | Recommendation: Merge

Incremental review since the last reviewed commit. The only change in this update is a single line in .github/workflows/reusable-bot-autoassign.yml adding owner: ${{ github.repository_owner }} to the actions/create-github-app-token@v3 step. This correctly scopes the generated GitHub App token to the organization owner, which is appropriate for this feature's cross-repo issue validation. No critical bugs or security vulnerabilities were found on the changed line.

Previously noted non-blocking concerns live on files unchanged in this update and remain tracked by existing active review comments, so they are not duplicated here.

Files Reviewed (1 file, incremental)
  • .github/workflows/reusable-bot-autoassign.yml

Previous review (commit baaaa01)

Status: No Issues Found | Recommendation: Merge

Lenient review focused on critical bugs and security vulnerabilities in changed lines only. The feature is well-scoped: it exempts maintainers/collaborators and configured bot authors, requires external PRs to reference a validated organization issue, labels invalid PRs, posts a single marker-tracked warning comment, and auto-closes after 24h via the stale job. Tests cover positive, negative, and edge cases, docs are updated, and the PR aligns with issue #709.

No new critical or security issues were found on the changed lines. Notable non-blocking concerns (GraphQL data: null handling / project pagination, PyGithub version floor, duplicated warning-comment body, method-local imports) are already tracked by existing active review comments, so they are not duplicated here.

Files Reviewed (10 files)
  • .github/actions/bot-autoassign/base.py
  • .github/actions/bot-autoassign/issue_assignment_bot.py
  • .github/actions/bot-autoassign/stale_pr_bot.py
  • .github/actions/bot-autoassign/utils.py
  • .github/actions/bot-autoassign/tests/test_issue_assignment_bot.py
  • .github/actions/bot-autoassign/tests/test_stale_pr_bot.py
  • .github/workflows/bot-autoassign-pr-issue-link.yml
  • .github/workflows/reusable-bot-autoassign.yml
  • docs/developer/reusable-github-utils.rst
  • setup.py

Reviewed by claude-opus-4.8 · Input: 30 · Output: 4.6K · Cached: 436.3K

@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch 4 times, most recently from ba9f9e7 to 991d6ee Compare July 10, 2026 16:15
…issues openwisp#709

- Add pull_request_target edited/ready_for_review triggers
- Implement regex issue references extraction helper
- Add GraphQL project v2 board assignment check
- Validate that PRs from external contributors link to open, labeled, and validated organization issues
- Apply 'invalid' label and post warning comment if invalid
- Automatically close invalid PRs after 24 hours in the stale PR workflow
- Update documentation and add unit tests

Closes openwisp#709
@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from 991d6ee to e719d10 Compare July 10, 2026 16:49

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I checked the project lookup again.

The openwisp-companion GitHub App installation now has organization_projects: read and repository_projects: read, and the GraphQL query returns the expected projects for openwisp/openwisp-utils#709.

Please rerun the test in openwisp/bot-testing-ground with the real app. That repo is using the reusable workflow from this branch, so it should now be able to read the project assignments.

Before merging, please also add owner: ${{ github.repository_owner }} to the direct actions/create-github-app-token@v3 step in .github/workflows/bot-autoassign-pr-issue-link.yml, so this repo uses the org installation token too.

The project-board check should stay. Labels alone are not enough for this feature because issue #709 explicitly requires the linked issue to be assigned to one of the contributor project boards.

Comment thread .github/actions/bot-autoassign/issue_assignment_bot.py Outdated
Comment thread .github/actions/bot-autoassign/base.py Outdated
Comment thread .github/actions/bot-autoassign/tests/test_stale_pr_bot.py Outdated
@prathmeshkulkarni-coder
prathmeshkulkarni-coder force-pushed the feature/709-flag-invalid-prs branch from a9442b9 to ceeff76 Compare July 19, 2026 09:04
@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@prathmeshkulkarni-coder

Copy link
Copy Markdown
Contributor Author

@nemesifier , the bot works perfectly now
PR1 : When PR has no valid link bot gives invalid label and comment on PR .
PR2 : When PR has valid issue linked bot doesn't gives label.
But one more thing the auto_assign_issues_from_pr() is still using the older extract_linked_issues() function. That older function is hard-coded to explicitly ignore cross-repo links and only look for local links (like #709). Because i linked a cross-repo issue, it ignores it and prints "No linked issues found", skipping the assignment step.
image
It doesn't give any problem just the comment gets on log .

PR3 : When PR has issue link but issue is not valid ( Does not present on Project board)

PR4 : When PR is updated with valid issue link bot removes the invalid tag .

The bot works perfectly on my side and i tested every condition .

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

Labels

enhancement github_actions Pull requests that update GitHub Actions code helper-bots Helper bots, release management automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Flag invalid pull requests that do not reference validated issues

3 participants