From 8ceddd18875a12813f4fc9a26b80441b479db4ee Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Mon, 9 Mar 2026 00:50:59 +0530 Subject: [PATCH 1/7] [chores] Added reusable active autoassign bots workflow and fixed exit codes --- .../bot-autoassign/issue_assignment_bot.py | 8 +-- .../actions/bot-autoassign/pr_reopen_bot.py | 10 ++-- .../tests/test_issue_assignment_bot.py | 8 +-- .../tests/test_pr_reopen_bot.py | 10 ++-- .github/workflows/reusable-bot-autoassign.yml | 52 +++++++++++++++++++ 5 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/reusable-bot-autoassign.yml diff --git a/.github/actions/bot-autoassign/issue_assignment_bot.py b/.github/actions/bot-autoassign/issue_assignment_bot.py index 9e807a8e..20360ac5 100644 --- a/.github/actions/bot-autoassign/issue_assignment_bot.py +++ b/.github/actions/bot-autoassign/issue_assignment_bot.py @@ -273,7 +273,7 @@ def handle_issue_comment(self): try: if self.event_payload.get("issue", {}).get("pull_request"): print("Comment is on a PR, not an issue - skipping") - return False + return True comment = self.event_payload.get("comment", {}) issue = self.event_payload.get("issue", {}) comment_body = comment.get("body", "") @@ -285,7 +285,7 @@ def handle_issue_comment(self): if self.is_assignment_request(comment_body): return self.respond_to_assignment_request(issue_number, commenter) print("Comment does not contain assignment request") - return False + return True except Exception as e: print(f"Error handling issue comment: {e}") return False @@ -311,7 +311,7 @@ def handle_pull_request(self): self.unassign_issues_from_pr(pr_body, pr_author) return True print(f"PR action '{action}' not handled") - return False + return True except Exception as e: print(f"Error handling pull request: {e}") return False @@ -328,7 +328,7 @@ def run(self): return self.handle_pull_request() else: print(f"Event type '{self.event_name}'" " not supported") - return False + return True except Exception as e: print(f"Error in main execution: {e}") return False diff --git a/.github/actions/bot-autoassign/pr_reopen_bot.py b/.github/actions/bot-autoassign/pr_reopen_bot.py index bf2bfa34..b711cc7e 100644 --- a/.github/actions/bot-autoassign/pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/pr_reopen_bot.py @@ -89,7 +89,7 @@ def run(self): return self.handle_pr_reopen() else: print(f"Event type '{self.event_name}'" " not supported") - return False + return True except Exception as e: print(f"Error in main execution: {e}") return False @@ -113,15 +113,15 @@ def handle_contributor_activity(self): return False if not issue_data.get("pull_request"): print("Comment is on an issue," " not a PR, skipping") - return False + return True pr = self.repo.get_pull(pr_number) if not pr.user or commenter != pr.user.login: print("Comment not from PR author, skipping") - return False + return True labels = [label.name for label in pr.get_labels()] if "stale" not in labels: print("PR is not stale, skipping") - return False + return True try: pr.remove_from_labels("stale") print("Removed stale label") @@ -169,7 +169,7 @@ def run(self): return self.handle_contributor_activity() else: print(f"Event type '{self.event_name}'" " not supported") - return False + return True except Exception as e: print(f"Error in main execution: {e}") return False diff --git a/.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py b/.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py index 7a21e4ae..68135dc2 100644 --- a/.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py +++ b/.github/actions/bot-autoassign/tests/test_issue_assignment_bot.py @@ -291,7 +291,7 @@ def test_skip_pr_comment(self, bot_env): }, } ) - assert not bot.handle_issue_comment() + assert bot.handle_issue_comment() def test_non_assignment_comment(self, bot_env): bot = IssueAssignmentBot() @@ -304,7 +304,7 @@ def test_non_assignment_comment(self, bot_env): }, } ) - assert not bot.handle_issue_comment() + assert bot.handle_issue_comment() def test_no_payload(self, bot_env): bot = IssueAssignmentBot() @@ -385,7 +385,7 @@ def test_unsupported_action(self, bot_env): }, } ) - assert not bot.handle_pull_request() + assert bot.handle_pull_request() class TestRun: @@ -431,7 +431,7 @@ def test_pull_request_event(self, bot_env): def test_unsupported_event(self, bot_env): bot = IssueAssignmentBot() bot.event_name = "push" - assert not bot.run() + assert bot.run() def test_no_github_client(self, bot_env): bot = IssueAssignmentBot() diff --git a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py index 04572cea..335ffd72 100644 --- a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py @@ -108,7 +108,7 @@ def test_handle_pr_reopen_no_payload(self, bot_env): def test_run_unsupported_event(self, bot_env): bot = PRReopenBot() bot.event_name = "push" - assert not bot.run() + assert bot.run() class TestPRActivityBot: @@ -160,7 +160,7 @@ def test_handle_contributor_activity_not_author(self, bot_env): mock_pr = Mock() mock_pr.user.login = "testuser" bot_env["repo"].get_pull.return_value = mock_pr - assert not bot.handle_contributor_activity() + assert bot.handle_contributor_activity() def test_handle_contributor_activity_pr_not_stale(self, bot_env): bot = PRActivityBot() @@ -177,7 +177,7 @@ def test_handle_contributor_activity_pr_not_stale(self, bot_env): mock_pr.user.login = "testuser" mock_pr.get_labels.return_value = [] bot_env["repo"].get_pull.return_value = mock_pr - assert not bot.handle_contributor_activity() + assert bot.handle_contributor_activity() def test_handle_contributor_activity_not_pr(self, bot_env): bot = PRActivityBot() @@ -190,7 +190,7 @@ def test_handle_contributor_activity_not_pr(self, bot_env): "comment": {"user": {"login": "testuser"}}, } ) - assert not bot.handle_contributor_activity() + assert bot.handle_contributor_activity() def test_handle_contributor_activity_no_payload(self, bot_env): bot = PRActivityBot() @@ -199,4 +199,4 @@ def test_handle_contributor_activity_no_payload(self, bot_env): def test_run_unsupported_event(self, bot_env): bot = PRActivityBot() bot.event_name = "push" - assert not bot.run() + assert bot.run() diff --git a/.github/workflows/reusable-bot-autoassign.yml b/.github/workflows/reusable-bot-autoassign.yml new file mode 100644 index 00000000..8972e88d --- /dev/null +++ b/.github/workflows/reusable-bot-autoassign.yml @@ -0,0 +1,52 @@ +name: Reusable Bot Autoassign + +on: + workflow_call: + inputs: + bot_command: + required: true + type: string + description: "The bot to execute (e.g., 'issue_assignment', 'pr_reopen', 'stale_pr')" + secrets: + OPENWISP_BOT_APP_ID: + required: true + OPENWISP_BOT_PRIVATE_KEY: + required: true + +jobs: + run-bot: + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.OPENWISP_BOT_APP_ID }} + private-key: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} + + - name: Checkout openwisp-utils + uses: actions/checkout@v6 + with: + repository: openwisp/openwisp-utils + ref: master + path: openwisp-utils + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install dependencies + run: pip install -e openwisp-utils/.[github_actions] + + - name: Execute bot script + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + REPOSITORY: ${{ github.repository }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + run: > + if [ -n "$GITHUB_EVENT_PATH" ]; then + python openwisp-utils/.github/actions/bot-autoassign/__main__.py ${{ inputs.bot_command }} "$GITHUB_EVENT_PATH" + else + python openwisp-utils/.github/actions/bot-autoassign/__main__.py ${{ inputs.bot_command }} + fi From 69331140bc666a0ae1d8966b1e0c4a225d44711a Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Mon, 9 Mar 2026 00:54:57 +0530 Subject: [PATCH 2/7] [fix] Quote pip install target to prevent bash glob expansion --- .github/workflows/reusable-bot-autoassign.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-bot-autoassign.yml b/.github/workflows/reusable-bot-autoassign.yml index 8972e88d..0886c6c5 100644 --- a/.github/workflows/reusable-bot-autoassign.yml +++ b/.github/workflows/reusable-bot-autoassign.yml @@ -37,7 +37,7 @@ jobs: python-version: "3.13" - name: Install dependencies - run: pip install -e openwisp-utils/.[github_actions] + run: pip install -e 'openwisp-utils/.[github_actions]' - name: Execute bot script env: From e1620de7d84afa5a7b6c18d13c630b92e5f3d9c9 Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Mon, 9 Mar 2026 01:02:34 +0530 Subject: [PATCH 3/7] [fix] Fix shell syntax and protect against shell injection --- .github/workflows/reusable-bot-autoassign.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-bot-autoassign.yml b/.github/workflows/reusable-bot-autoassign.yml index 0886c6c5..922bee1c 100644 --- a/.github/workflows/reusable-bot-autoassign.yml +++ b/.github/workflows/reusable-bot-autoassign.yml @@ -44,9 +44,10 @@ jobs: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} REPOSITORY: ${{ github.repository }} GITHUB_EVENT_NAME: ${{ github.event_name }} - run: > + BOT_COMMAND: ${{ inputs.bot_command }} + run: | if [ -n "$GITHUB_EVENT_PATH" ]; then - python openwisp-utils/.github/actions/bot-autoassign/__main__.py ${{ inputs.bot_command }} "$GITHUB_EVENT_PATH" + python openwisp-utils/.github/actions/bot-autoassign/__main__.py "$BOT_COMMAND" "$GITHUB_EVENT_PATH" else - python openwisp-utils/.github/actions/bot-autoassign/__main__.py ${{ inputs.bot_command }} + python openwisp-utils/.github/actions/bot-autoassign/__main__.py "$BOT_COMMAND" fi From c253c40824a384a1843208bb39264ea7008d7d89 Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Tue, 10 Mar 2026 01:50:46 +0530 Subject: [PATCH 4/7] [docs] Added the docs --- docs/developer/reusable-github-utils.rst | 140 ++++++++++++++++------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/docs/developer/reusable-github-utils.rst b/docs/developer/reusable-github-utils.rst index a6aa5a61..bfa7295d 100644 --- a/docs/developer/reusable-github-utils.rst +++ b/docs/developer/reusable-github-utils.rst @@ -86,70 +86,126 @@ the following environment variables at runtime: ``GITHUB_TOKEN``, **Setup for Other Repositories** -To enable the auto-assignment bot in another OpenWISP repository, add -workflow files under ``.github/workflows/``. Each workflow needs to: +To enable the auto-assignment bot in another OpenWISP repository, you must +create four workflow files under ``.github/workflows/`` that call the +reusable GitHub Workflow. This reusable workflow automatically handles +token generation, environment setup, and executing the bot scripts. -1. Generate a GitHub App token using the OpenWISP Bot credentials. -2. Checkout ``openwisp-utils`` to get the bot scripts. -3. Install the bot dependencies via ``pip install -e .[github_actions]``. -4. Run the appropriate bot command. - -Below is a complete example for the issue assignment bot. You can find all -four workflow files in the ``openwisp-utils`` repository under -``.github/workflows/`` (``bot-autoassign-issue.yml``, +You can find the required workflow files (``bot-autoassign-issue.yml``, ``bot-autoassign-pr-issue-link.yml``, ``bot-autoassign-pr-reopen.yml``, -``bot-autoassign-stale-pr.yml``). +``bot-autoassign-stale-pr.yml``) in the ``openwisp-utils`` repository. It +is recommended to simply copy them to your repository, or recreate them +using the following examples. + +**1. Issue Assignment Bot** +(``.github/workflows/bot-autoassign-issue.yml``) .. code-block:: yaml name: Issue Assignment Bot - on: issue_comment: types: [created] - permissions: contents: read issues: write - concurrency: group: bot-autoassign-issue-${{ github.repository }}-${{ github.event.issue.number }} cancel-in-progress: true - jobs: respond-to-assign-request: - runs-on: ubuntu-latest if: github.event.issue.pull_request == null - steps: - - name: Generate GitHub App token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.OPENWISP_BOT_APP_ID }} - private-key: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} + uses: openwisp/openwisp-utils/.github/workflows/reusable-bot-autoassign.yml@master + with: + bot_command: issue_assignment + secrets: + OPENWISP_BOT_APP_ID: ${{ secrets.OPENWISP_BOT_APP_ID }} + OPENWISP_BOT_PRIVATE_KEY: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} - - name: Checkout openwisp-utils - uses: actions/checkout@v6 - with: - repository: openwisp/openwisp-utils - path: openwisp-utils +**2. PR Issue Link** +(``.github/workflows/bot-autoassign-pr-issue-link.yml``) - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" +.. code-block:: yaml - - name: Install dependencies - run: pip install -e openwisp-utils/.[github_actions] + name: PR Issue Auto-Assignment + on: + pull_request_target: + types: [opened, reopened, closed] + permissions: + contents: read + issues: write + pull-requests: read + concurrency: + group: bot-autoassign-pr-link-${{ github.repository }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: + auto-assign-issue: + uses: openwisp/openwisp-utils/.github/workflows/reusable-bot-autoassign.yml@master + with: + bot_command: issue_assignment + secrets: + OPENWISP_BOT_APP_ID: ${{ secrets.OPENWISP_BOT_APP_ID }} + OPENWISP_BOT_PRIVATE_KEY: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} - - name: Run issue assignment bot - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - REPOSITORY: ${{ github.repository }} - GITHUB_EVENT_NAME: ${{ github.event_name }} - run: > - python openwisp-utils/.github/actions/bot-autoassign/__main__.py - issue_assignment "$GITHUB_EVENT_PATH" +**3. PR Reopen** (``.github/workflows/bot-autoassign-pr-reopen.yml``) + +.. code-block:: yaml + + name: PR Reopen Reassignment + on: + pull_request_target: + types: [reopened] + issue_comment: + types: [created] + permissions: + contents: read + issues: write + pull-requests: write + concurrency: + group: bot-autoassign-pr-reopen-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true + jobs: + reassign-on-reopen: + if: github.event_name == 'pull_request_target' && github.event.action == 'reopened' + uses: openwisp/openwisp-utils/.github/workflows/reusable-bot-autoassign.yml@master + with: + bot_command: pr_reopen + secrets: + OPENWISP_BOT_APP_ID: ${{ secrets.OPENWISP_BOT_APP_ID }} + OPENWISP_BOT_PRIVATE_KEY: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} + handle-pr-activity: + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login + uses: openwisp/openwisp-utils/.github/workflows/reusable-bot-autoassign.yml@master + with: + bot_command: pr_reopen + secrets: + OPENWISP_BOT_APP_ID: ${{ secrets.OPENWISP_BOT_APP_ID }} + OPENWISP_BOT_PRIVATE_KEY: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} + +**4. Stale PR** (``.github/workflows/bot-autoassign-stale-pr.yml``) + +.. code-block:: yaml + + name: Stale PR Management + on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + permissions: + contents: read + issues: write + pull-requests: write + concurrency: + group: bot-autoassign-stale-pr-${{ github.repository }} + cancel-in-progress: false + jobs: + manage-stale-prs-python: + uses: openwisp/openwisp-utils/.github/workflows/reusable-bot-autoassign.yml@master + with: + bot_command: stale_pr + secrets: + OPENWISP_BOT_APP_ID: ${{ secrets.OPENWISP_BOT_APP_ID }} + OPENWISP_BOT_PRIVATE_KEY: ${{ secrets.OPENWISP_BOT_PRIVATE_KEY }} GitHub Workflows ---------------- From 153ff20375d06bb881cc64b768634211b07b3e2a Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Tue, 10 Mar 2026 11:47:14 +0530 Subject: [PATCH 5/7] [fix] Return True on early skips to avoid GitHub Actions failures --- .../bot-autoassign/issue_assignment_bot.py | 10 +++++----- .../actions/bot-autoassign/pr_reopen_bot.py | 14 ++++++------- .../actions/bot-autoassign/stale_pr_bot.py | 20 +++++++++---------- .../tests/test_pr_reopen_bot.py | 2 +- .../bot-autoassign/tests/test_stale_pr_bot.py | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/actions/bot-autoassign/issue_assignment_bot.py b/.github/actions/bot-autoassign/issue_assignment_bot.py index 20360ac5..624753d8 100644 --- a/.github/actions/bot-autoassign/issue_assignment_bot.py +++ b/.github/actions/bot-autoassign/issue_assignment_bot.py @@ -281,14 +281,14 @@ def handle_issue_comment(self): issue_number = issue.get("number") if not all([comment_body, commenter, issue_number]): print("Missing required comment data") - return False + return True if self.is_assignment_request(comment_body): return self.respond_to_assignment_request(issue_number, commenter) print("Comment does not contain assignment request") return True except Exception as e: print(f"Error handling issue comment: {e}") - return False + return True def handle_pull_request(self): if not self.event_payload: @@ -302,7 +302,7 @@ def handle_pull_request(self): pr_body = pr.get("body", "") if not all([pr_number, pr_author]): print("Missing required PR data") - return False + return True if action in ["opened", "reopened"]: self.auto_assign_issues_from_pr(pr_number, pr_author, pr_body) # We consider the event handled even if no issues were linked @@ -314,7 +314,7 @@ def handle_pull_request(self): return True except Exception as e: print(f"Error handling pull request: {e}") - return False + return True def run(self): if not self.github or not self.repo: @@ -331,7 +331,7 @@ def run(self): return True except Exception as e: print(f"Error in main execution: {e}") - return False + return True finally: print("Issue Assignment Bot completed") diff --git a/.github/actions/bot-autoassign/pr_reopen_bot.py b/.github/actions/bot-autoassign/pr_reopen_bot.py index b711cc7e..639f7c34 100644 --- a/.github/actions/bot-autoassign/pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/pr_reopen_bot.py @@ -69,15 +69,15 @@ def handle_pr_reopen(self): pr_body = pr.get("body", "") if not all([pr_number, pr_author]): print("Missing required PR data") - return False + return True print(f"Handling PR #{pr_number}" f" reopen by {pr_author}") reassigned = self.reassign_issues_to_author(pr_number, pr_author, pr_body) self.remove_stale_label(pr_number) print(f"Reassigned {len(reassigned)}" f" issues to {pr_author}") return True except Exception as e: - print(f"Error handling PR reopen: {e}") - return False + print(f"Error handling reopened PR: {e}") + return True def run(self): if not self.github or not self.repo: @@ -101,7 +101,7 @@ class PRActivityBot(GitHubBot): def handle_contributor_activity(self): if not self.event_payload: print("No event payload available") - return False + return True try: issue_data = self.event_payload.get("issue", {}) pr_number = issue_data.get("number") @@ -110,7 +110,7 @@ def handle_contributor_activity(self): ) if not all([pr_number, commenter]): print("Missing required comment data") - return False + return True if not issue_data.get("pull_request"): print("Comment is on an issue," " not a PR, skipping") return True @@ -157,7 +157,7 @@ def handle_contributor_activity(self): return True except Exception as e: print(f"Error handling contributor activity: {e}") - return False + return True def run(self): if not self.github or not self.repo: @@ -172,7 +172,7 @@ def run(self): return True except Exception as e: print(f"Error in main execution: {e}") - return False + return True finally: print("PR Activity Bot completed") diff --git a/.github/actions/bot-autoassign/stale_pr_bot.py b/.github/actions/bot-autoassign/stale_pr_bot.py index 95cca167..4f7c405e 100644 --- a/.github/actions/bot-autoassign/stale_pr_bot.py +++ b/.github/actions/bot-autoassign/stale_pr_bot.py @@ -144,11 +144,11 @@ def unassign_linked_issues(self, pr): def close_stale_pr(self, pr, days_inactive): if pr.state == "closed": print(f"PR #{pr.number} is already closed, skipping") - return False + return True try: pr_author = pr.user.login if pr.user else None if not pr_author: - return False + return True close_lines = [ "", f"Hi @{pr_author} 👋,", @@ -204,13 +204,13 @@ def close_stale_pr(self, pr, days_inactive): return True except Exception as e: print(f"Error closing PR #{pr.number}: {e}") - return False + return True def mark_pr_stale(self, pr, days_inactive): try: pr_author = pr.user.login if pr.user else None if not pr_author: - return False + return True unassign_lines = [ "", f"Hi @{pr_author} 👋,", @@ -267,13 +267,13 @@ def mark_pr_stale(self, pr, days_inactive): return True except Exception as e: print(f"Error marking PR #{pr.number}" f" as stale: {e}") - return False + return True def send_stale_warning(self, pr, days_inactive): try: pr_author = pr.user.login if pr.user else None if not pr_author: - return False + return True remaining = self.DAYS_BEFORE_UNASSIGN - days_inactive warning_lines = [ "", @@ -316,12 +316,12 @@ def send_stale_warning(self, pr, days_inactive): return True except Exception as e: print("Error sending warning" f" for PR #{pr.number}: {e}") - return False + return True def process_stale_prs(self): if not self.repo: print("GitHub repository not initialized") - return False + return True try: open_prs = self.repo.get_pulls(state="open") processed_count = 0 @@ -381,7 +381,7 @@ def process_stale_prs(self): return True except Exception as e: print(f"Error in process_stale_prs: {e}") - return False + return True def run(self): if not self.github or not self.repo: @@ -392,7 +392,7 @@ def run(self): return self.process_stale_prs() except Exception as e: print(f"Error in main execution: {e}") - return False + return True finally: print("Stale PR Management Bot completed") diff --git a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py index 335ffd72..de92f799 100644 --- a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py @@ -194,7 +194,7 @@ def test_handle_contributor_activity_not_pr(self, bot_env): def test_handle_contributor_activity_no_payload(self, bot_env): bot = PRActivityBot() - assert not bot.handle_contributor_activity() + assert bot.handle_contributor_activity() def test_run_unsupported_event(self, bot_env): bot = PRActivityBot() diff --git a/.github/actions/bot-autoassign/tests/test_stale_pr_bot.py b/.github/actions/bot-autoassign/tests/test_stale_pr_bot.py index 76fa6f49..20892a3a 100644 --- a/.github/actions/bot-autoassign/tests/test_stale_pr_bot.py +++ b/.github/actions/bot-autoassign/tests/test_stale_pr_bot.py @@ -231,7 +231,7 @@ def test_already_closed(self, bot_env): bot = StalePRBot() mock_pr = Mock() mock_pr.state = "closed" - assert not bot.close_stale_pr(mock_pr, 60) + assert bot.close_stale_pr(mock_pr, 60) mock_pr.create_issue_comment.assert_not_called() mock_pr.edit.assert_not_called() From a9b28ac97230b05bf3a9df8551a99f5d286a3d9c Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Thu, 12 Mar 2026 00:51:06 +0530 Subject: [PATCH 6/7] [fix] Return False on true exceptions to avoid masking failures --- .../actions/bot-autoassign/issue_assignment_bot.py | 6 +++--- .github/actions/bot-autoassign/pr_reopen_bot.py | 8 ++++---- .github/actions/bot-autoassign/stale_pr_bot.py | 12 ++++++------ .../bot-autoassign/tests/test_pr_reopen_bot.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/actions/bot-autoassign/issue_assignment_bot.py b/.github/actions/bot-autoassign/issue_assignment_bot.py index 624753d8..52b60eae 100644 --- a/.github/actions/bot-autoassign/issue_assignment_bot.py +++ b/.github/actions/bot-autoassign/issue_assignment_bot.py @@ -288,7 +288,7 @@ def handle_issue_comment(self): return True except Exception as e: print(f"Error handling issue comment: {e}") - return True + return False def handle_pull_request(self): if not self.event_payload: @@ -314,7 +314,7 @@ def handle_pull_request(self): return True except Exception as e: print(f"Error handling pull request: {e}") - return True + return False def run(self): if not self.github or not self.repo: @@ -331,7 +331,7 @@ def run(self): return True except Exception as e: print(f"Error in main execution: {e}") - return True + return False finally: print("Issue Assignment Bot completed") diff --git a/.github/actions/bot-autoassign/pr_reopen_bot.py b/.github/actions/bot-autoassign/pr_reopen_bot.py index 639f7c34..05197289 100644 --- a/.github/actions/bot-autoassign/pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/pr_reopen_bot.py @@ -77,7 +77,7 @@ def handle_pr_reopen(self): return True except Exception as e: print(f"Error handling reopened PR: {e}") - return True + return False def run(self): if not self.github or not self.repo: @@ -101,7 +101,7 @@ class PRActivityBot(GitHubBot): def handle_contributor_activity(self): if not self.event_payload: print("No event payload available") - return True + return False try: issue_data = self.event_payload.get("issue", {}) pr_number = issue_data.get("number") @@ -157,7 +157,7 @@ def handle_contributor_activity(self): return True except Exception as e: print(f"Error handling contributor activity: {e}") - return True + return False def run(self): if not self.github or not self.repo: @@ -172,7 +172,7 @@ def run(self): return True except Exception as e: print(f"Error in main execution: {e}") - return True + return False finally: print("PR Activity Bot completed") diff --git a/.github/actions/bot-autoassign/stale_pr_bot.py b/.github/actions/bot-autoassign/stale_pr_bot.py index 4f7c405e..04d9e1ac 100644 --- a/.github/actions/bot-autoassign/stale_pr_bot.py +++ b/.github/actions/bot-autoassign/stale_pr_bot.py @@ -204,7 +204,7 @@ def close_stale_pr(self, pr, days_inactive): return True except Exception as e: print(f"Error closing PR #{pr.number}: {e}") - return True + return False def mark_pr_stale(self, pr, days_inactive): try: @@ -267,7 +267,7 @@ def mark_pr_stale(self, pr, days_inactive): return True except Exception as e: print(f"Error marking PR #{pr.number}" f" as stale: {e}") - return True + return False def send_stale_warning(self, pr, days_inactive): try: @@ -316,12 +316,12 @@ def send_stale_warning(self, pr, days_inactive): return True except Exception as e: print("Error sending warning" f" for PR #{pr.number}: {e}") - return True + return False def process_stale_prs(self): if not self.repo: print("GitHub repository not initialized") - return True + return False try: open_prs = self.repo.get_pulls(state="open") processed_count = 0 @@ -381,7 +381,7 @@ def process_stale_prs(self): return True except Exception as e: print(f"Error in process_stale_prs: {e}") - return True + return False def run(self): if not self.github or not self.repo: @@ -392,7 +392,7 @@ def run(self): return self.process_stale_prs() except Exception as e: print(f"Error in main execution: {e}") - return True + return False finally: print("Stale PR Management Bot completed") diff --git a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py index de92f799..335ffd72 100644 --- a/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/tests/test_pr_reopen_bot.py @@ -194,7 +194,7 @@ def test_handle_contributor_activity_not_pr(self, bot_env): def test_handle_contributor_activity_no_payload(self, bot_env): bot = PRActivityBot() - assert bot.handle_contributor_activity() + assert not bot.handle_contributor_activity() def test_run_unsupported_event(self, bot_env): bot = PRActivityBot() From fdaa739545ab7b0679486cd6ea649298236a28dc Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Thu, 12 Mar 2026 00:51:06 +0530 Subject: [PATCH 7/7] [fix] Return False on true exceptions to avoid masking failures --- .github/actions/bot-autoassign/issue_assignment_bot.py | 4 ++-- .github/actions/bot-autoassign/pr_reopen_bot.py | 4 ++-- .github/actions/bot-autoassign/stale_pr_bot.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/bot-autoassign/issue_assignment_bot.py b/.github/actions/bot-autoassign/issue_assignment_bot.py index 52b60eae..20360ac5 100644 --- a/.github/actions/bot-autoassign/issue_assignment_bot.py +++ b/.github/actions/bot-autoassign/issue_assignment_bot.py @@ -281,7 +281,7 @@ def handle_issue_comment(self): issue_number = issue.get("number") if not all([comment_body, commenter, issue_number]): print("Missing required comment data") - return True + return False if self.is_assignment_request(comment_body): return self.respond_to_assignment_request(issue_number, commenter) print("Comment does not contain assignment request") @@ -302,7 +302,7 @@ def handle_pull_request(self): pr_body = pr.get("body", "") if not all([pr_number, pr_author]): print("Missing required PR data") - return True + return False if action in ["opened", "reopened"]: self.auto_assign_issues_from_pr(pr_number, pr_author, pr_body) # We consider the event handled even if no issues were linked diff --git a/.github/actions/bot-autoassign/pr_reopen_bot.py b/.github/actions/bot-autoassign/pr_reopen_bot.py index 05197289..c99a166e 100644 --- a/.github/actions/bot-autoassign/pr_reopen_bot.py +++ b/.github/actions/bot-autoassign/pr_reopen_bot.py @@ -69,7 +69,7 @@ def handle_pr_reopen(self): pr_body = pr.get("body", "") if not all([pr_number, pr_author]): print("Missing required PR data") - return True + return False print(f"Handling PR #{pr_number}" f" reopen by {pr_author}") reassigned = self.reassign_issues_to_author(pr_number, pr_author, pr_body) self.remove_stale_label(pr_number) @@ -110,7 +110,7 @@ def handle_contributor_activity(self): ) if not all([pr_number, commenter]): print("Missing required comment data") - return True + return False if not issue_data.get("pull_request"): print("Comment is on an issue," " not a PR, skipping") return True diff --git a/.github/actions/bot-autoassign/stale_pr_bot.py b/.github/actions/bot-autoassign/stale_pr_bot.py index 04d9e1ac..dfecc7fd 100644 --- a/.github/actions/bot-autoassign/stale_pr_bot.py +++ b/.github/actions/bot-autoassign/stale_pr_bot.py @@ -148,7 +148,7 @@ def close_stale_pr(self, pr, days_inactive): try: pr_author = pr.user.login if pr.user else None if not pr_author: - return True + return False close_lines = [ "", f"Hi @{pr_author} 👋,", @@ -210,7 +210,7 @@ def mark_pr_stale(self, pr, days_inactive): try: pr_author = pr.user.login if pr.user else None if not pr_author: - return True + return False unassign_lines = [ "", f"Hi @{pr_author} 👋,", @@ -273,7 +273,7 @@ def send_stale_warning(self, pr, days_inactive): try: pr_author = pr.user.login if pr.user else None if not pr_author: - return True + return False remaining = self.DAYS_BEFORE_UNASSIGN - days_inactive warning_lines = [ "",