Skip to content

[feature/bots] Add changelog bot to generate RST entries using Gemini AI.#584

Open
pushpitkamboj wants to merge 15 commits intoopenwisp:masterfrom
pushpitkamboj:feature/AI_created_changelog
Open

[feature/bots] Add changelog bot to generate RST entries using Gemini AI.#584
pushpitkamboj wants to merge 15 commits intoopenwisp:masterfrom
pushpitkamboj:feature/AI_created_changelog

Conversation

@pushpitkamboj
Copy link
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 #523

Description of Changes

Changelog Bot Workflow

1. TRIGGER

  • Maintainer after approving the PR comments @openwisp-bot changelog on it

2. WORKFLOW ACTIVATION

  • changelog-bot.yml detects the event
  • Validates user has write access to repo
  • Calls reusable-changelog-bot.yml

3. ACKNOWLEDGMENT

  • Adds 🚀 reaction to the maintainer's comment as a sign of acknowledging
  • Signals that the request is being processed

4. ACTION SETUP

  • Checkout step: Clones openwisp/openwisp-utils repo with sparse-checkout (only changelong directory)
  • Sets up Python 3.11
  • Installs google-genai dependency
  • Passes environment variables:
    • GITHUB_TOKEN, GEMINI_API_KEY
    • PR_NUMBER, REPO_NAME, LLM_MODEL

5. CHANGELOG GENERATION (generate_changelog.py)

  • Fetches PR details (title, description, labels)
  • Fetches PR diff (truncated to 15k chars)
  • Fetches commit messages
  • Fetches linked issues from PR body
  • Builds prompt with all context
  • Calls Gemini API to generate RST changelog entry

6. POST COMMENT

  • Posts generated changelog as PR comment
  • Entry is wrapped in \``rst` code block
  • Maintainer can copy directly to CHANGES.rst

7. TESTING

  • Also updated the runtests.py to include the test cases for generate_changlog.py.
  • 27 test cases were written with the help of AI assistance

8. DOCUMENTATION

  • wrote the detailed process in docs/developer/changelog-bot.rst

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

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
📝 Walkthrough

Walkthrough

This PR introduces an automated changelog generation system triggered by pull request review approvals. It includes a Python script that fetches PR metadata (title, body, labels, diff, commits, linked issues) and calls Google Gemini to generate formatted changelog entries. The system is deployed via reusable GitHub Actions workflows that check PR titles for keywords ([feature], [fix], [change]) to determine if a changelog entry is warranted. The generated changelog entry is posted as a formatted comment on the PR for manual review and integration.

Sequence Diagram

sequenceDiagram
    actor Reviewer
    participant GitHub as GitHub
    participant Workflow as Changelog Bot Workflow
    participant Script as generate_changelog.py
    participant GeminiAPI as Google Gemini API
    
    Reviewer->>GitHub: Approves PR with review
    GitHub->>Workflow: Triggers pull_request_review event
    Workflow->>Workflow: Check PR title for [feature/fix/change]
    alt Noteworthy PR detected
        Workflow->>Workflow: Generate GitHub App token
        Workflow->>Script: Execute changelog generator
        Script->>GitHub: Fetch PR details (title, body, labels, branches, author)
        Script->>GitHub: Fetch PR diff
        Script->>GitHub: Fetch recent commits
        Script->>GitHub: Fetch linked issues
        Script->>Script: Build comprehensive prompt with all data
        Script->>GeminiAPI: Call Gemini with prompt and model config
        GeminiAPI->>Script: Return formatted changelog entry
        Script->>GitHub: Post changelog as comment on PR
        GitHub->>Reviewer: Display generated changelog comment
    else No keywords found
        Workflow->>Workflow: Skip changelog generation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding a changelog bot using Gemini AI to generate RST entries, which is the primary change in this PR.
Description check ✅ Passed The PR description follows the template with completed checklist items, references issue #523, and provides detailed description of the changelog bot workflow, testing, and documentation updates.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issue #523: automated changelog generation triggered on PR approval, RST format output as GitHub comment, and selective triggering for notable PR types ([feature], [fix], [change]).
Out of Scope Changes check ✅ Passed All changes are in-scope: new workflows, Python script, tests, documentation, and setup.py updates directly support the changelog bot feature from issue #523; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/developer/changelog-bot.rst`:
- Around line 126-129: The docs claim an automatic trigger on PR approval but
the reusable workflow lacks a pull_request_review event; either remove that docs
section or add support by updating the reusable workflow to handle the
pull_request_review event (add a top-level pull_request_review: entry) and route
it to the same job that handles manual triggers (e.g., the generate_changelog or
changelog-suggest job), guarding execution with a condition like if:
github.event.review.state == 'approved' (or equivalent) so only approvals invoke
the workflow.
- Around line 77-92: The docs list a non-existent trigger "pull_request_review";
update docs/developer/changelog-bot.rst to match the actual workflow by removing
the pull_request_review block and clarifying the bot triggers only on
issue_comment (created) with the trigger-phrase input (e.g., "@openwisp-bot
changelog"); ensure the example shows only the issue_comment trigger and
mentions the use of the trigger-phrase and GEMINI_API_KEY secret so it reflects
changelog-bot.yml's current behavior.
🧹 Nitpick comments (5)
.github/actions/changelog-generator/test_generate_changelog.py (2)

82-109: Consider adding a test for diff fetch failure.

The get_pr_diff function returns an empty string on HTTP/URL errors (graceful degradation). Adding a test case that verifies this behavior would improve coverage.

💡 Optional test for error handling
`@patch`("generate_changelog.urlopen")
def test_returns_empty_on_error(self, mock_urlopen):
    from urllib.error import HTTPError
    mock_urlopen.side_effect = HTTPError(None, 404, "Not Found", {}, None)
    
    result = get_pr_diff("org/repo", 123, "token")
    
    self.assertEqual(result, "")

361-372: Test scenario is slightly unrealistic but still validates error handling.

The test mocks urlopen to return a response with status 403, but in reality urlopen raises HTTPError for non-2xx status codes rather than returning a response object. The implementation's response.status != 201 check (in post_github_comment) would only trigger for unusual cases like status 200 or 204.

However, this test still validates that non-201 responses are rejected, so the overall behavior is correct.

💡 More realistic error test
`@patch`("generate_changelog.urlopen")
def test_raises_on_http_error(self, mock_urlopen):
    from urllib.error import HTTPError
    mock_urlopen.side_effect = HTTPError(
        "https://api.github.com/...", 403, "Forbidden", {}, None
    )

    with self.assertRaises(HTTPError):
        post_github_comment("org/repo", 123, "Test comment", "token")
.github/actions/changelog-generator/generate_changelog.py (1)

140-141: Consider logging the exception for debugging purposes.

The broad except Exception with continue is reasonable here since linked issues are supplementary data, but logging the error would help diagnose issues in production.

💡 Optional: Log the exception
         except Exception:
+            print(f"Warning: Could not fetch issue #{issue_num}", file=sys.stderr)
             continue
.github/actions/changelog-generator/action.yml (1)

31-33: Consider pinning the google-genai dependency version.

The pip install google-genai command installs the latest version, which could lead to unexpected behavior if the SDK introduces breaking changes.

💡 Pin dependency version
     - name: Install dependencies
       shell: bash
-      run: pip install google-genai
+      run: pip install google-genai==1.0.0

Replace 1.0.0 with the tested version.

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

24-27: The fallback in the condition is redundant.

The || '@openwisp-bot changelog' fallback at line 27 is unnecessary since trigger-phrase already has a default value defined at line 15. The condition can be simplified.

💡 Simplify condition
     if: |
       github.event_name == 'issue_comment' && 
       github.event.issue.pull_request && 
-      contains(github.event.comment.body, inputs.trigger-phrase || '@openwisp-bot changelog')
+      contains(github.event.comment.body, inputs.trigger-phrase)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b754e2e and 2374648.

📒 Files selected for processing (7)
  • .github/actions/changelog-generator/action.yml
  • .github/actions/changelog-generator/generate_changelog.py
  • .github/actions/changelog-generator/test_generate_changelog.py
  • .github/workflows/changelog-bot.yml
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/changelog-bot.rst
  • runtests.py
🧰 Additional context used
🧬 Code graph analysis (2)
.github/actions/changelog-generator/test_generate_changelog.py (1)
.github/actions/changelog-generator/generate_changelog.py (9)
  • build_prompt (170-242)
  • call_gemini (145-167)
  • get_env_or_exit (37-43)
  • get_linked_issues (114-142)
  • get_pr_commits (102-111)
  • get_pr_details (66-78)
  • get_pr_diff (81-99)
  • post_github_comment (245-260)
  • main (263-283)
runtests.py (1)
.github/actions/changelog-generator/generate_changelog.py (1)
  • main (263-283)
🪛 Ruff (0.14.14)
.github/actions/changelog-generator/generate_changelog.py

[error] 54-54: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 56-56: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 89-89: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 91-91: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 140-141: try-except-continue detected, consider logging the exception

(S112)


[warning] 140-140: Do not catch blind exception: Exception

(BLE001)


[warning] 161-161: Abstract raise to an inner function

(TRY301)


[warning] 161-161: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 163-163: Consider moving this statement to an else block

(TRY300)


[warning] 165-165: Do not catch blind exception: Exception

(BLE001)


[error] 255-255: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 256-256: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 283-283: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
🔇 Additional comments (24)
.github/actions/changelog-generator/test_generate_changelog.py (7)

1-22: LGTM! Well-structured test suite.

The test file is well-organized with clear test classes for each function, proper use of mocking, and good coverage of edge cases including empty inputs, missing fields, and error conditions.


24-42: LGTM!

Complete coverage for environment variable validation including the edge case of empty strings.


45-79: LGTM!

Good coverage of PR details extraction with proper handling of missing fields.


112-138: LGTM!

Good coverage of commit extraction including SHA truncation and the 10-commit limit.


141-180: LGTM!

Good coverage of issue linking patterns and limits. The empty body test correctly expects an empty list without needing API mocking.


183-279: LGTM!

Comprehensive test coverage for the Gemini API integration including success paths, configuration validation, and error handling.


282-342: LGTM!

Good coverage of prompt construction including all components and truncation behavior.

.github/actions/changelog-generator/generate_changelog.py (9)

37-43: LGTM!

Clean implementation for required environment variable validation.


46-63: LGTM!

The static analysis warning (S310) about URL scheme audit is a false positive here since the URL is always constructed with the hardcoded https://api.github.com prefix.


66-78: LGTM!

Good use of defensive .get() calls with defaults for safe field extraction.


81-99: LGTM!

Good graceful degradation on error, and sensible truncation to prevent overwhelming the LLM with large diffs.


102-111: LGTM!

Clean extraction of commit information with sensible limits.


145-167: LGTM!

Appropriate error handling for external API calls. The static analysis warnings (TRY301, TRY003, TRY300, BLE001) are stylistic preferences; the current implementation is clear and functional.


170-242: LGTM!

Well-structured prompt with comprehensive instructions for RST changelog generation and sensible truncation limits.


245-260: LGTM!

Proper comment posting implementation. Note that urlopen typically raises HTTPError for 4xx/5xx status codes before reaching the status check, but the check provides additional defense for unexpected non-201 success codes.


263-287: LGTM!

Clean orchestration of the changelog generation flow with proper error propagation.

.github/actions/changelog-generator/action.yml (1)

1-49: LGTM overall!

Well-structured composite action with clear input definitions and proper environment variable mapping.

runtests.py (1)

20-27: LGTM!

Clean extension of pytest test discovery to include the new changelog generator tests.

.github/workflows/changelog-bot.yml (1)

1-15: LGTM!

Clean entry-point workflow that delegates to the reusable workflow with appropriate inputs and secrets.

docs/developer/changelog-bot.rst (1)

1-195: LGTM overall!

Comprehensive documentation covering setup, configuration, usage, troubleshooting, and security considerations. The example RST format and workflow configuration are helpful.

.github/workflows/reusable-changelog-bot.yml (4)

41-56: LGTM!

Good permission validation ensuring only users with write access can trigger changelog generation.


58-65: LGTM!

Efficient use of sparse checkout to fetch only the required action directory.


67-77: LGTM!

Good UX providing immediate feedback via the rocket reaction.


79-87: LGTM!

Proper invocation of the changelog generator action with all required inputs.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from ebf86f4 to 10b3e9e Compare February 3, 2026 12:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/actions/changelog-generator/generate_changelog.py:
- Around line 145-167: The long system instruction string in call_gemini exceeds
line length (E501); split or wrap the string passed to
types.GenerateContentConfig.system_instruction (inside the call_gemini function)
into multiple shorter string literals (e.g., use implicit concatenation across
multiple quoted parts or join a list) so the combined instruction remains the
same but no single line exceeds the limit; ensure you only change the string
formatting and keep the same content, model, and other config fields.
- Around line 114-142: The get_linked_issues function can abort the process
because github_api_request raises SystemExit on HTTP/URL errors; update
get_linked_issues to guard calls to github_api_request by catching SystemExit in
addition to Exception (or replace the call with a non-exiting wrapper), so a
failing fetch for any issue_num does not terminate the workflow; modify the
try/except around github_api_request("/repos/{repo}/issues/{issue_num}", token)
in get_linked_issues to handle SystemExit (and continue) and ensure issue_data
failures simply skip that issue.

In @.github/actions/changelog-generator/test_generate_changelog.py:
- Around line 4-21: The test module triggers flake8 E402 because sys.path.insert
runs before importing generate_changelog; fix by suppressing the linter for that
legitimate import order: add "# noqa: E402" to the "from generate_changelog
import ( build_prompt, call_gemini, get_env_or_exit, get_linked_issues,
get_pr_commits, get_pr_details, get_pr_diff, post_github_comment, )" import line
so the linter ignores the post-statement import while keeping sys.path.insert
and the referenced symbols unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2374648 and 38a62ed.

📒 Files selected for processing (7)
  • .github/actions/changelog-generator/action.yml
  • .github/actions/changelog-generator/generate_changelog.py
  • .github/actions/changelog-generator/test_generate_changelog.py
  • .github/workflows/changelog-bot.yml
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/changelog-bot.rst
  • runtests.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/changelog-bot.rst
  • .github/workflows/changelog-bot.yml
  • runtests.py
  • .github/actions/changelog-generator/action.yml
🧰 Additional context used
🧬 Code graph analysis (1)
.github/actions/changelog-generator/test_generate_changelog.py (1)
.github/actions/changelog-generator/generate_changelog.py (9)
  • build_prompt (170-242)
  • call_gemini (145-167)
  • get_env_or_exit (37-43)
  • get_linked_issues (114-142)
  • get_pr_commits (102-111)
  • get_pr_details (66-78)
  • get_pr_diff (81-99)
  • post_github_comment (245-260)
  • main (263-283)
🪛 GitHub Actions: OpenWISP Utils CI Build
.github/actions/changelog-generator/test_generate_changelog.py

[error] 12-12: Flake8: E402 module level import not at top of file.

.github/actions/changelog-generator/generate_changelog.py

[error] 154-154: E501 line too long (174 > 110 characters).

🪛 Ruff (0.14.14)
.github/actions/changelog-generator/generate_changelog.py

[error] 54-54: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 56-56: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 89-89: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 91-91: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 140-141: try-except-continue detected, consider logging the exception

(S112)


[warning] 140-140: Do not catch blind exception: Exception

(BLE001)


[warning] 161-161: Abstract raise to an inner function

(TRY301)


[warning] 161-161: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 163-163: Consider moving this statement to an else block

(TRY300)


[warning] 165-165: Do not catch blind exception: Exception

(BLE001)


[error] 255-255: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 256-256: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 283-283: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (19)
.github/actions/changelog-generator/test_generate_changelog.py (9)

24-42: Good coverage for env var validation.

Covers present, missing, and empty cases cleanly.


45-80: PR detail parsing tests look solid.

Nice coverage for defaults and label extraction.


82-112: Diff retrieval and truncation tests are clear.

Validates both normal and oversized diffs.


114-141: Commit parsing tests are on point.

Covers message truncation and commit limit.


143-183: Linked-issue extraction tests look good.

Covers keyword patterns, max count, and empty body.


185-283: Gemini integration tests are thorough.

Covers model selection, config usage, and error paths.


284-345: Prompt construction tests are comprehensive.

Good coverage for labels, commits, issues, and truncation.


347-375: Comment posting tests look good.

Covers success and non-201 error behavior.


377-378: Main guard is fine.

.github/actions/changelog-generator/generate_changelog.py (10)

1-35: Module header/imports look good.


37-43: Env var guard is clean and direct.


46-63: GitHub API helper is straightforward.


66-78: PR details extraction looks good.


81-99: Diff fetch + truncation is handled well.


102-111: Commit parsing logic is clear.


170-242: Prompt assembly is well structured.


245-261: PR comment posting logic looks good.


263-283: Main flow is clear and cohesive.


286-287: Entry point is fine.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch 5 times, most recently from 2b1b98f to d0d513d Compare February 3, 2026 13:41
@coveralls
Copy link

coveralls commented Feb 3, 2026

Coverage Status

coverage: 97.249%. remained the same
when pulling 7902a4c on pushpitkamboj:feature/AI_created_changelog
into 782c8d9 on openwisp:master.

@atif09
Copy link
Contributor

atif09 commented Feb 3, 2026

A comment by code rabbit hasn't been resolved, apply the changes if you think theyre worth addressing, or just mention why you dont agree to it by replying to it

@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from 9efcef1 to 5f1333c Compare February 3, 2026 15:42
How It Works
------------

1. A maintainer approves a PR or comments ``@openwisp-bot changelog``
Copy link
Member

Choose a reason for hiding this comment

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

Let's make sure the following points are true, covered by unit tests and documented:

  • If PR is approved and @openwisp-bot changelog was explicitly called after the latest changes were pushed, don't do anything
  • If PR is approved and @openwisp-bot changelog was not explicitly called after the latest changes were pushed, the action is triggered only if the PR title contains: [feature, [fix or [change - I think we can skip this for [deps as they don't require much change log writing
  • If @openwisp-bot changelog is called, it will execute regardless of the PR title

BTW: approved by who? Anyone? No, it should be triggered only when approved by a member of the @openwisp organization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the maintainer of the repo, yes that I have done. Basically who has the write access to the openwisp's repos.
Line 43 - 48 in the .github/workflows/reusable-changelog-bot.py has this logic

Copy link
Contributor Author

@pushpitkamboj pushpitkamboj Feb 3, 2026

Choose a reason for hiding this comment

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

yes 3rd point has been implemented, the changeling will be generated regardless of PR title, but I will also add a test case which covers this case for safe side.

  • if I understand point 2 correctly you want the log to be generated automatically without mentioning @openwisp-bot changelog if the PR title contains feature, fix, change ?

  • I will have to fix the logic to follow point 1, that was a nice catch!

freezegun
pytest
pytest-mock
google-genai
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

This is still here even though setup.py has been updated.

runtests.py Outdated
pytest.main(
[
os.path.join("openwisp_utils/releaser/tests"),
os.path.join(".github/actions/changelog-generator"),
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

anything specific which you want to me to look in #562 in relation to runtests.py file?
If not, yes I am reading the changes to understand the PR in general

@nemesifier nemesifier added the github_actions Pull requests that update GitHub Actions code label Feb 3, 2026
@github-project-automation github-project-automation bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Feb 3, 2026
@nemesifier nemesifier added the releaser Related to the OpenWISP Release Tool label Feb 3, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/actions/changelog-generator/generate_changelog.py:
- Around line 66-78: The PR body returned by get_pr_details can be null which
later causes get_linked_issues to crash when calling .lower(); update
get_pr_details to coalesce the PR body to an empty string (e.g., use
pr_data.get("body") or "") when constructing the returned dict so "body" is
never None, and optionally add a defensive check in get_linked_issues to handle
empty or non-string bodies before calling .lower(); reference get_pr_details and
get_linked_issues when applying the change.
- Around line 175-247: The prompt in build_prompt does not supply a canonical
repository/PR URL so the LLM may hallucinate links; update build_prompt to read
the PR URL from pr_details (e.g., pr_details['html_url'] or a repo_url derived
from pr_details) and inject that into the prompt so the RST link rule references
the real URL variable, and update the RULE 4 example/reference to use that
variable (e.g., replace <repo_url> with the injected pr_html_url) so the model
always uses the provided canonical PR URL when rendering `#<number> <...>`
links.

In @.github/workflows/reusable-changelog-bot.yml:
- Around line 39-55: Add a review-approval gate to the existing check-permission
step: use the GitHub API to list PR reviews for the pull request (e.g., via
github.rest.pulls.listReviews using context.repo and context.issue.number),
compute a boolean hasApproval by checking if any review.state === 'APPROVED',
and require hasApproval along with hasAccess before proceeding; if not approved
call core.setFailed('Pull request must be approved to trigger changelog
generation') (retain existing hasAccess logic and variable names like hasAccess,
hasApproval, and the step id check-permission to locate where to insert the
check).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38a62ed and 5c11cbc.

📒 Files selected for processing (8)
  • .github/actions/changelog-generator/action.yml
  • .github/actions/changelog-generator/generate_changelog.py
  • .github/actions/changelog-generator/test_generate_changelog.py
  • .github/workflows/changelog-bot.yml
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/changelog-bot.rst
  • requirements-test.txt
  • runtests.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/developer/changelog-bot.rst
  • .github/workflows/changelog-bot.yml
  • runtests.py
🧰 Additional context used
🧬 Code graph analysis (1)
.github/actions/changelog-generator/test_generate_changelog.py (1)
.github/actions/changelog-generator/generate_changelog.py (9)
  • build_prompt (175-247)
  • call_gemini (146-172)
  • get_env_or_exit (37-43)
  • get_linked_issues (114-143)
  • get_pr_commits (102-111)
  • get_pr_details (66-78)
  • get_pr_diff (81-99)
  • post_github_comment (250-265)
  • main (268-288)
🪛 Ruff (0.14.14)
.github/actions/changelog-generator/generate_changelog.py

[error] 54-54: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 56-56: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 89-89: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 91-91: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 140-142: try-except-continue detected, consider logging the exception

(S112)


[warning] 140-140: Do not catch blind exception: Exception

(BLE001)


[warning] 166-166: Abstract raise to an inner function

(TRY301)


[warning] 166-166: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 168-168: Consider moving this statement to an else block

(TRY300)


[warning] 170-170: Do not catch blind exception: Exception

(BLE001)


[error] 260-260: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 261-261: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 288-288: Avoid specifying long messages outside the exception class

(TRY003)

.github/actions/changelog-generator/test_generate_changelog.py

[warning] 10-10: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


[warning] 11-11: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


[warning] 13-13: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Python==3.10 | django~=5.0.0
  • 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.0.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
🔇 Additional comments (19)
requirements-test.txt (1)

1-6: LGTM.
Adds the test dependency needed by the new generator tests.

.github/actions/changelog-generator/generate_changelog.py (8)

37-43: LGTM.
Straightforward env validation and exit behavior.


46-63: LGTM.
Clear error handling for GitHub API failures.


81-99: LGTM.
Diff truncation keeps prompt size bounded without failing the run.


102-111: LGTM.
Commit summary and 10‑item cap are sensible.


114-143: LGTM.
Issue extraction is clear and appropriately capped.


146-173: LGTM.
Good handling for empty responses and API errors.


250-265: LGTM.
Posting logic is straightforward and fails fast on non‑201 responses.


268-288: LGTM.
Clear orchestration of the end‑to‑end flow.

.github/actions/changelog-generator/action.yml (1)

1-45: LGTM.
Inputs are mapped cleanly to the generator’s expected environment variables.

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

56-84: LGTM.
Sparse checkout and the reaction acknowledgment look good.

.github/actions/changelog-generator/test_generate_changelog.py (8)

25-44: LGTM.
Covers success and failure paths for env handling.


46-81: LGTM.
Good coverage for PR details defaults and parsing.


83-113: LGTM.
Diff retrieval and truncation behavior are well tested.


115-142: LGTM.
Commit parsing and the 10‑item cap are validated.


144-184: LGTM.
Linked‑issue extraction cases are covered, including limits.


186-283: LGTM.
Gemini call behavior is thoroughly mocked and asserted.


285-347: LGTM.
Prompt construction tests cover labels, commits, issues, and truncation.


348-379: LGTM.
Posting behavior and non‑201 failures are validated.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@nemesifier nemesifier changed the title [feature] Add changelog bot to generate RST entries using Gemini AI. [feature/bots] Add changelog bot to generate RST entries using Gemini AI. Feb 3, 2026
@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from 5c11cbc to 0ac1017 Compare February 4, 2026 07:04
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 4, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/actions/changelog-generator/test_generate_changelog.py:
- Around line 4-22: Remove the unused "# noqa: E402" suppressions in the test
file: delete the E402 noqa markers on the unittest, unittest.mock, and
generate_changelog import lines (the top sys.path manipulation makes them
unnecessary). Update the imports to be plain (e.g., "import unittest", "from
unittest.mock import ...", and "from generate_changelog import ...") so Ruff no
longer reports RUF100; if you prefer keeping a suppression, instead add a
per-file Ruff ignore for E402 at the top of the file.
🧹 Nitpick comments (1)
.github/actions/changelog-generator/generate_changelog.py (1)

114-143: Make linked-issue selection deterministic.
Using a set makes issue selection order unpredictable; sorting before slicing keeps prompts stable.

♻️ Proposed fix
-    for issue_num in list(issue_numbers)[:3]:  # Limit to 3 issues
+    for issue_num in sorted(issue_numbers, key=int)[:3]:  # Limit to 3 issues
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c11cbc and 0ac1017.

📒 Files selected for processing (8)
  • .github/actions/changelog-generator/action.yml
  • .github/actions/changelog-generator/generate_changelog.py
  • .github/actions/changelog-generator/test_generate_changelog.py
  • .github/workflows/changelog-bot.yml
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/changelog-bot.rst
  • requirements-test.txt
  • runtests.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/developer/changelog-bot.rst
  • .github/workflows/changelog-bot.yml
  • runtests.py
  • requirements-test.txt
  • .github/actions/changelog-generator/action.yml
🧰 Additional context used
🧬 Code graph analysis (1)
.github/actions/changelog-generator/test_generate_changelog.py (1)
.github/actions/changelog-generator/generate_changelog.py (9)
  • build_prompt (175-250)
  • call_gemini (146-172)
  • get_env_or_exit (37-43)
  • get_linked_issues (114-143)
  • get_pr_commits (102-111)
  • get_pr_details (66-78)
  • get_pr_diff (81-99)
  • post_github_comment (253-268)
  • main (271-291)
🪛 Ruff (0.14.14)
.github/actions/changelog-generator/generate_changelog.py

[error] 54-54: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 56-56: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 89-89: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 91-91: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 140-142: try-except-continue detected, consider logging the exception

(S112)


[warning] 140-140: Do not catch blind exception: Exception

(BLE001)


[warning] 166-166: Abstract raise to an inner function

(TRY301)


[warning] 166-166: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 168-168: Consider moving this statement to an else block

(TRY300)


[warning] 170-170: Do not catch blind exception: Exception

(BLE001)


[error] 263-263: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 264-264: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 291-291: Avoid specifying long messages outside the exception class

(TRY003)

.github/actions/changelog-generator/test_generate_changelog.py

[warning] 10-10: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


[warning] 11-11: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


[warning] 13-13: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | 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.10 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=4.2.0
🔇 Additional comments (25)
.github/actions/changelog-generator/generate_changelog.py (11)

1-24: Clear module documentation.
Nicely documents usage, env vars, and changelog format expectations.


37-43: Solid env guard.
Straightforward validation and exit path.


46-63: API wrapper looks good.
Error handling and timeouts are clear.


66-78: PR details normalization is tidy.
Reasonable defaults and defensive coalescing.


81-99: Diff fetch with truncation is sensible.
Keeps prompt size bounded without hard-failing.


102-111: Commit parsing is clean.
Limits and message extraction are straightforward.


146-172: Response validation and error handling look fine.
Nice guard for empty responses.


175-250: Prompt assembly is well structured.
Clear sections and rules for consistent output.


253-268: Comment posting flow is straightforward.
Simple and clear response validation.


271-291: Main flow wiring looks good.
Good end-to-end sequence.


294-295: Entry-point hook is fine.
Standard and clear.

.github/actions/changelog-generator/test_generate_changelog.py (9)

25-44: Env handling tests are solid.
Covers presence, missing, and empty values.


46-81: PR detail tests look comprehensive.
Covers both populated and missing fields.


83-113: Diff tests cover truncation well.
Good validation of large diff handling.


115-142: Commit parsing tests are clear.
Limits and message extraction are validated.


144-184: Linked-issue extraction tests are thorough.
Good coverage of patterns and limits.


186-283: Gemini call tests are well mocked.
Nice coverage of config, errors, and outputs.


285-347: Prompt construction tests are clear.
Checks labels, commits, issues, and body truncation.


348-376: Comment posting tests look good.
Covers success and non‑201 failures.


378-379: Test runner hook is fine.
Standard unittest entry point.

.github/workflows/reusable-changelog-bot.yml (5)

1-20: Inputs and secrets are clearly defined.
Defaults and required secret look good.


21-33: Job guard and permissions look appropriate.
Clear trigger gating and scoped permissions.


35-37: PR number extraction is straightforward.
Simple and readable.


39-99: Pre-checks are comprehensive.
Covers merge status, recent changes, permissions, and approvals.


100-128: Checkout/reaction/generation sequence looks good.
Flow is clear and easy to follow.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

- Changed default LLM model to gemini-2.5-flash-lite across all files
- Updated workflow references to use openwisp/openwisp-utils@bots branch
- Fixed pip install to use git+https with github_actions extra
- Changed default LLM model to gemini-2.5-flash-lite across all files
- Updated workflow references to use openwisp/openwisp-utils@bots branch
- Fixed pip install to use git+https with github_actions extra
@pushpitkamboj
Copy link
Contributor Author

I have made some changes

  • The reasoning is 2.5-flash-lite model by gemini is cheaper and was accessible within free tier.
  • I have self referenced the code in the PR with bots branch (if u plan something else then the PR will have to be updated accordingly)

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

Can you expand on what you mean with:

I have self referenced the code in the PR with bots branch (if u plan something else then the PR will have to be updated accordingly)

client = genai.Client(api_key=api_key)

try:
response = client.models.generate_content(
Copy link
Member

Choose a reason for hiding this comment

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

is this retried if it fails? Or would the github action fail?

@pushpitkamboj
Copy link
Contributor Author

The reusable workflow fetches the action from openwisp/openwisp-utils@bots. Ensure this PR is merged to the bots branch for the workflow to work. (see reusable-changelog-bot.yml L55-63)

@pushpitkamboj
Copy link
Contributor Author

Now I have added retry options. attempts=3, max_delay=30. It will use default values (coming from SDK) for other options:
initial_delay = 1, exp_base = 2, jitter = 1, http_status_code = (408, 429, and 5xx)

@pushpitkamboj
Copy link
Contributor Author

@Adityashandilya555
can you review my changes, Your suggestions in previous review helped me spotting bugs.

@atif09
Copy link
Contributor

atif09 commented Feb 21, 2026

CRITICAL Bugs

1. Missing secret declarations in reusable-changelog-bot.yml

OPENWISP_BOT_APP_ID and OPENWISP_BOT_PRIVATE_KEY are used in the "Generate App Token" step (lines 52–53) but are not declared in on.workflow_call.secrets (lines 11–14), which only lists GEMINI_API_KEY.

In GitHub Actions reusable workflows, secrets are only accessible to the callee if declared in workflow_call.secrets — undeclared secrets resolve to empty strings. As a result, actions/create-github-app-token@v1 will always receive empty app-id and private-key and fail on every run.

Fix: Add the two missing entries to workflow_call.secrets:

OPENWISP_BOT_APP_ID:
  description: "OpenWISP Bot GitHub App ID"
  required: true
OPENWISP_BOT_PRIVATE_KEY:
  description: "OpenWISP Bot GitHub App private key"
  required: true

2. test_successful_call assertion is wrong and will fail (tests.py:201)

mock_genai.Client.assert_called_once_with(api_key="api_key")

The actual call in call_gemini (lines 183–190) passes both api_key and http_options. This test only mocks genai, not types, so types.HttpOptions(...) produces a real object that gets passed to the constructor. assert_called_once_with is a strict equality check — it raises AssertionError because http_options is absent from the assertion.

Fix: Use assert_called_once() and verify api_key separately via call_args, or add http_options=mock.ANY to the assertion.


3. Duplicate comments posted on every approval (generate_changelog.py:main)

post_github_comment is called unconditionally. Since the workflow fires on every pull_request_review approval event, a PR with 3 approvers gets 3 identical changelog bot comments — there is no check for whether the bot has already commented.

Fix: Before posting, fetch existing PR comments and skip if a prior bot comment (e.g. one whose body starts with ```rst or ```md) already exists.


Unresolved Maintainer Requests

4. @openwisp-bot changelog manual trigger is not implemented

nemesifier's CHANGES_REQUESTED (2026-02-07) explicitly requires: "If @openwisp-bot changelog is called, execute regardless of title."

changelog-bot.yml only has pull_request_review: types: [submitted]. There is no issue_comment trigger, no comment-body parsing, and no title-filter bypass for the manual path.


5. No org-member check on the approving reviewer

nemesifier's CHANGES_REQUESTED (2026-02-03): "Approved by who? Only when approved by a member of the @ openwisp organization."

reusable-changelog-bot.yml:21 only checks github.event.review.state == 'approved'. Any GitHub user with approval rights triggers the bot — org membership is never verified.


🟡 Test Coverage Gaps

6. detect_changelog_format has zero test coverage

detect_changelog_format is not imported in tests.py and has no test class. It has three code paths (CHANGES.rst exists, CHANGES.md exists, neither exists) — all untested. Flagged by Adityashandilya555 on 2026-02-12 and still unaddressed.


7. Markdown format path in build_prompt is untested

All five TestBuildPrompt tests call build_prompt with the default changelog_format='rst'. The 'md' branch (generate_changelog.py:242–253), which produces different format_rules, a different file_name, and a Markdown-formatted example, is never exercised.


🟢 Minor Issues

8. Issue numbers sorted lexicographically, not numerically (generate_changelog.py:158)

issue_numbers is a set of strings from re.findall. sorted(["9", "10", "20"])["10", "20", "9"]. For a PR closing 4+ issues, the wrong three could be fetched and sent to the LLM.

Fix: sorted(issue_numbers, key=int)[:3]


9. Unconditional ... appended after issue body slice (generate_changelog.py:234)

issues_text += f"  Description: {issue['body'][:200]}...\n"

... is appended even when the body is shorter than 200 characters, misleading the LLM into thinking content was truncated when it wasn't.

Fix: f" Description: {issue['body'][:200]}{'...' if len(issue['body']) > 200 else ''}\n"

- Add duplicate comment detection to prevent multiple changelog
  comments on same PR; check runs early to save API calls
- Add author_association check in workflow to ensure only
  OWNER/MEMBER/COLLABORATOR approvals trigger the bot
- Fix issue number sorting to use numeric order instead of
  lexicographic (sorted with key=int)
- Fix unconditional '...' appended to issue body; now only
  shows when content is actually truncated
- Add test coverage for detect_changelog_format (all 3 paths)
- Add test coverage for build_prompt markdown format branch
- Add tests for has_existing_changelog_comment function
@pushpitkamboj
Copy link
Contributor Author

pushpitkamboj commented Feb 22, 2026

addressed all comments except 4th. As discussed with the maintainer earlier, the trigger should happen when pr is approved by member of Openwisp org automatically. Manual trigger is not needed.

@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from c218d57 to bd66974 Compare February 22, 2026 09:36
@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from d52901a to 7d9a6d1 Compare February 22, 2026 11:01
@nemesifier
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

✅ Actions performed

Review triggered.

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.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
.github/workflows/reusable-changelog-bot.yml (1)

5-70: 🧹 Nitpick | 🔵 Trivial

Pin the openwisp-utils action ref (or make it configurable).

Using ref: master can change behavior across repos over time. Consider adding an input to pin a tag/sha so consumers can lock to a tested version.

♻️ Suggested input + ref wiring
 on:
   workflow_call:
     inputs:
       llm-model:
         description: "Gemini model to use (example 'gemini-2.5-flash-lite', 'gemini-1.5-pro')"
         required: false
         type: string
         default: "gemini-2.5-flash-lite"
+      utils-ref:
+        description: "openwisp-utils ref/tag/sha for the changelog action"
+        required: false
+        type: string
+        default: "master"
@@
       - name: Checkout openwisp-utils for action
         if: steps.check-commits.outputs.has_noteworthy == 'true'
         uses: actions/checkout@v6
         with:
           repository: openwisp/openwisp-utils
-          ref: master
+          ref: ${{ inputs.utils-ref }}
           sparse-checkout: |
             .github/actions/changelog-generator
           path: openwisp-utils-action
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/reusable-changelog-bot.yml around lines 5 - 70, Add a new
workflow input (e.g., inputs.openwisp_utils_ref) with a sensible default tag/sha
and replace the hardcoded ref: master in the "Checkout openwisp-utils for
action" step with that input (use the input expression like ${{
inputs.openwisp_utils_ref }}); ensure the input name matches exactly
(openwisp_utils_ref or openwisp-utils-ref) and update any documentation/comments
so consumers can pin to a tag/sha or override the ref when invoking the reusable
workflow.
.github/workflows/changelog-bot.yml (1)

3-17: ⚠️ Potential issue | 🟠 Major

Manual @openwisp-bot changelog trigger isn’t wired in.

This workflow listens only to pull_request_review events, but the PR objectives mention an explicit comment trigger. If that requirement still stands, add an issue_comment trigger here and update the reusable workflow to handle comment events and filter the body accordingly (or clarify the requirement in docs/summary).

🛠️ Minimal trigger addition (still requires reusable workflow handling)
 on:
   # Trigger when a PR review is submitted with approval
   pull_request_review:
     types: [submitted]
+  issue_comment:
+    types: [created]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/changelog-bot.yml around lines 3 - 17, Add an
issue_comment trigger alongside pull_request_review in the workflow top-level
"on:" block so the workflow can be started by manual comments (e.g., the
explicit "@openwisp-bot changelog" trigger); then update the reusable workflow
referenced by the "uses:
openwisp/openwisp-utils/.github/workflows/reusable-changelog-bot.yml@master"
call to accept and handle comment events (or document/clarify that manual
comment triggering is not supported) by filtering the comment body for the
changelog command before proceeding. Ensure you modify the "on:" block to
include issue_comment and add logic in the reusable workflow to check
github.event.comment.body (or equivalent) for the trigger string and only run
the changelog job when it matches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/changelog-generator/generate_changelog.py:
- Around line 303-363: has_existing_changelog_comment currently only fetches the
first 30 comments and relies on a regex; change it to paginate through all
comments (loop with page=1..N, using per_page=100 and stop when no comments
returned) and search for a unique HTML marker string (use marker = "<!--
openwisp-changelog-bot -->") instead of the PR-number regex; also update the
code that builds the PR comment in main() to prepend that same marker to the
posted body so post_github_comment receives/comment contains the marker and
duplicates are reliably detected (refer to has_existing_changelog_comment and
the comment construction in main).

In `@setup.py`:
- Around line 79-82: The "github_actions" extras list currently lacks upper
version bounds; update the extras definition for "github_actions" to constrain
the packages (e.g., set "google-genai>=1.62.0,<2.0.0" and
"requests>=2.0.0,<3.0.0" or other tested upper bounds) so future major releases
won't break the package; modify the extras mapping where "github_actions" is
defined to replace the loose entries for google-genai and requests with the
chosen bounded versions.

---

Duplicate comments:
In @.github/workflows/changelog-bot.yml:
- Around line 3-17: Add an issue_comment trigger alongside pull_request_review
in the workflow top-level "on:" block so the workflow can be started by manual
comments (e.g., the explicit "@openwisp-bot changelog" trigger); then update the
reusable workflow referenced by the "uses:
openwisp/openwisp-utils/.github/workflows/reusable-changelog-bot.yml@master"
call to accept and handle comment events (or document/clarify that manual
comment triggering is not supported) by filtering the comment body for the
changelog command before proceeding. Ensure you modify the "on:" block to
include issue_comment and add logic in the reusable workflow to check
github.event.comment.body (or equivalent) for the trigger string and only run
the changelog job when it matches.

In @.github/workflows/reusable-changelog-bot.yml:
- Around line 5-70: Add a new workflow input (e.g., inputs.openwisp_utils_ref)
with a sensible default tag/sha and replace the hardcoded ref: master in the
"Checkout openwisp-utils for action" step with that input (use the input
expression like ${{ inputs.openwisp_utils_ref }}); ensure the input name matches
exactly (openwisp_utils_ref or openwisp-utils-ref) and update any
documentation/comments so consumers can pin to a tag/sha or override the ref
when invoking the reusable workflow.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38e0e4f and 7902a4c.

📒 Files selected for processing (8)
  • .github/actions/changelog-generator/action.yml
  • .github/actions/changelog-generator/generate_changelog.py
  • .github/actions/changelog-generator/tests.py
  • .github/workflows/changelog-bot.yml
  • .github/workflows/reusable-changelog-bot.yml
  • docs/developer/reusable-github-utils.rst
  • runtests.py
  • setup.py
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-02-04T07:19:40.541Z
Learnt from: pushpitkamboj
Repo: openwisp/openwisp-utils PR: 584
File: .github/actions/changelog-generator/test_generate_changelog.py:4-22
Timestamp: 2026-02-04T07:19:40.541Z
Learning: In `.github/actions/changelog-generator/test_generate_changelog.py`, the sys.path manipulation before imports and use of absolute imports is intentional and preferred for readability, even though relative imports could work.

Applied to files:

  • runtests.py
  • .github/actions/changelog-generator/tests.py
  • .github/actions/changelog-generator/generate_changelog.py
📚 Learning: 2026-02-10T20:38:27.593Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 593
File: openwisp_utils/releaser/commitizen.py:5-9
Timestamp: 2026-02-10T20:38:27.593Z
Learning: In openwisp-utils commitizen implementation, the `_TITLE_ISSUE_EXTRACT_RE` pattern intentionally matches any space-preceded `#<number>` in commit titles without anchoring to the end. This is acceptable because 99.9% of such patterns are actual issue references, and rare edge cases (like version numbers) will be handled manually if they occur.

Applied to files:

  • .github/actions/changelog-generator/generate_changelog.py
  • .github/workflows/reusable-changelog-bot.yml
🧬 Code graph analysis (2)
.github/actions/changelog-generator/tests.py (1)
.github/actions/changelog-generator/generate_changelog.py (10)
  • build_prompt (215-300)
  • call_gemini (174-212)
  • detect_changelog_format (132-143)
  • get_env_or_exit (36-42)
  • get_linked_issues (146-171)
  • get_pr_commits (110-129)
  • get_pr_details (73-85)
  • get_pr_diff (88-107)
  • post_github_comment (316-338)
  • main (341-369)
.github/actions/changelog-generator/generate_changelog.py (3)
openwisp_utils/releaser/tests/test_github.py (1)
  • raise_for_status (15-19)
openwisp_utils/releaser/commitizen.py (2)
  • message (83-99)
  • example (214-220)
openwisp_utils/utils.py (1)
  • update (12-17)
🔇 Additional comments (11)
runtests.py (1)

27-31: Test discovery now includes the changelog generator path.

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

119-160: Changelog Bot doc block is clear and aligned with the workflow.

.github/actions/changelog-generator/generate_changelog.py (5)

36-71: Env validation and GitHub API error redaction look solid.


88-129: Git-based diff/commit collection is robust.

Graceful fallbacks and line-boundary truncation are good safeguards.


132-171: Format detection and deterministic issue ordering look good.


174-213: API call path handles empty responses and redacts secrets cleanly.


215-300: Prompt assembly is structured and rule-driven.

.github/actions/changelog-generator/action.yml (1)

23-45: Composite action wiring and env mapping look consistent.

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

44-54: PR-title prefix gate matches the intended filter.

.github/actions/changelog-generator/tests.py (2)

3-21: Test module setup is clear and keeps imports explicit.


278-345: Prompt tests cover both Markdown and RST paths.

Comment on lines 303 to 363
def has_existing_changelog_comment(repo: str, pr_number: int, token: str) -> bool:
"""Check if changelog bot has already commented on this PR."""
endpoint = f"/repos/{repo}/issues/{pr_number}/comments"
comments = github_api_request(endpoint, token)
pr_ref_pattern = rf"[`\[]#{pr_number}[\s<>\]\(]"
for comment in comments:
body = comment.get("body", "").strip()
has_pr_ref = re.search(pr_ref_pattern, body) is not None
if has_pr_ref:
return True
return False


def post_github_comment(repo: str, pr_number: int, comment: str, token: str) -> None:
"""Post a comment on the PR."""
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
headers = {
"Accept": "application/vnd.github.v3+json",
"Authorization": f"Bearer {token}",
"User-Agent": "OpenWISP-Changelog-Bot",
}
try:
response = retryable_request(
method="post",
url=url,
timeout=(4, 8),
max_retries=5,
backoff_factor=3,
backoff_jitter=0.5,
status_forcelist=(429, 500, 502, 503, 504),
headers=headers,
json={"body": comment},
)
response.raise_for_status()
except RequestException as e:
raise RuntimeError(f"Failed to post comment: {e}") from e


def main():
"""Main entry point."""
# Get configuration from environment
pr_number = int(get_env_or_exit("PR_NUMBER"))
repo = get_env_or_exit("REPO_NAME")
github_token = get_env_or_exit("GITHUB_TOKEN")
if has_existing_changelog_comment(repo, pr_number, github_token):
print("Changelog comment already exists, skipping.")
return
api_key = get_env_or_exit("GEMINI_API_KEY")
model = os.environ.get("LLM_MODEL", "gemini-2.5-flash-lite")
pr_details = get_pr_details(repo, pr_number, github_token)
base_branch = pr_details["base_branch"]
diff = get_pr_diff(base_branch)
commits = get_pr_commits(base_branch)
issues = get_linked_issues(repo, pr_details["body"], github_token)
changelog_format = detect_changelog_format()

prompt = build_prompt(pr_details, diff, commits, issues, changelog_format)
changelog_entry = call_gemini(prompt, api_key, model, changelog_format)
changelog_entry = changelog_entry.strip()
comment = f"```{changelog_format}\n{changelog_entry}\n```"
try:
Copy link

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the file
find . -name "generate_changelog.py" -type f

Repository: openwisp/openwisp-utils

Length of output: 126


🏁 Script executed:

# Read the file to understand the current implementation
cat -n .github/actions/changelog-generator/generate_changelog.py | head -100

Repository: openwisp/openwisp-utils

Length of output: 3911


🏁 Script executed:

# Get more context around the functions in question
sed -n '290,380p' .github/actions/changelog-generator/generate_changelog.py

Repository: openwisp/openwisp-utils

Length of output: 3291


🏁 Script executed:

# Let's look for the github_api_request function to understand how it works
grep -n "def github_api_request" .github/actions/changelog-generator/generate_changelog.py -A 20

Repository: openwisp/openwisp-utils

Length of output: 810


🏁 Script executed:

# Check for any pagination handling in the codebase
rg "per_page|pagination" .github/actions/changelog-generator/ -A 2 -B 2

Repository: openwisp/openwisp-utils

Length of output: 49


🌐 Web query:

GitHub API comments endpoint pagination default limit

💡 Result:

GitHub’s REST API list endpoints for comments are paginated to 30 items per page by default (i.e., per_page defaults to 30, and page defaults to 1). You can usually increase per_page up to 100. [1], [2], [3], [4]

Examples (default shown in docs):

  • Issue comments: per_page default 30 [2]
  • Commit comments: per_page default 30 [3]
  • Pull request review comments: per_page default 30 [4]

Pagination guide note: GitHub explicitly states that many REST responses return only 30 items unless you request more via per_page. [1]

References:
[1] Using pagination in the REST API - GitHub Docs (traversing with pagination) (docs.github.com)
[2] REST API endpoints for issue comments - GitHub Docs (docs.github.com)
[3] REST API endpoints for commit comments - GitHub Docs (docs.github.com)
[4] REST API endpoints for pull request review comments - GitHub Docs (docs.github.com)

Citations:


🏁 Script executed:

# Check if there are any tests for the function
find . -name "*test*" -type f | xargs grep -l "has_existing_changelog_comment" 2>/dev/null || echo "No tests found"

Repository: openwisp/openwisp-utils

Length of output: 81


🏁 Script executed:

# Look for any existing pagination handling patterns in the codebase
rg "page.*=" .github/actions/changelog-generator/ -A 2 -B 2

Repository: openwisp/openwisp-utils

Length of output: 49


🏁 Script executed:

# Check if the pattern might match unrelated PR references in common comment formats
python3 << 'EOF'
import re

# Test the regex pattern
pr_number = 123
pr_ref_pattern = rf"[`\[]#{pr_number}[\s<>\]\(]"

# Test cases - should match according to pattern
test_cases = [
    f"`#{pr_number} fixed",        # backtick prefix
    f"[#{pr_number} issue",        # bracket prefix
    f"see #{pr_number} for details",  # with space
    f"Related to #{pr_number}<",   # with bracket/angle
    f"Closes #{pr_number}(",       # with paren
    # Cases that might be unrelated
    f"Issue #{pr_number} in docs",
    f"Follow-up to #{pr_number}.",
    f"version #{pr_number}",       # This would NOT match - has no required suffix char
]

print("Testing regex pattern:", repr(pr_ref_pattern))
print()
for test in test_cases:
    match = re.search(pr_ref_pattern, test)
    print(f"{'MATCH' if match else 'NO   '}: {test!r}")
EOF

Repository: openwisp/openwisp-utils

Length of output: 314


Add pagination and unique marker to dedup check.

The comments endpoint defaults to 30 items per page; without pagination, bot comments on PRs with >30 comments can be missed, allowing duplicates. Additionally, the regex pattern—while restrictive—still matches unrelated comments that happen to mention the PR number with surrounding backticks or brackets (e.g., markdown code blocks). A unique marker is more reliable than regex-based detection.

🛠️ Suggested fix (marker + pagination)
 def has_existing_changelog_comment(repo: str, pr_number: int, token: str) -> bool:
     """Check if changelog bot has already commented on this PR."""
-    endpoint = f"/repos/{repo}/issues/{pr_number}/comments"
-    comments = github_api_request(endpoint, token)
-    pr_ref_pattern = rf"[`\[]#{pr_number}[\s<>\]\(]"
-    for comment in comments:
-        body = comment.get("body", "").strip()
-        has_pr_ref = re.search(pr_ref_pattern, body) is not None
-        if has_pr_ref:
-            return True
-    return False
+    marker = "<!-- openwisp-changelog-bot -->"
+    page = 1
+    while True:
+        endpoint = (
+            f"/repos/{repo}/issues/{pr_number}/comments?per_page=100&page={page}"
+        )
+        comments = github_api_request(endpoint, token)
+        if not comments:
+            return False
+        for comment in comments:
+            body = comment.get("body", "") or ""
+            if marker in body:
+                return True
+        page += 1
-    comment = f"```{changelog_format}\n{changelog_entry}\n```"
+    marker = "<!-- openwisp-changelog-bot -->"
+    comment = f"{marker}\n```{changelog_format}\n{changelog_entry}\n```"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/changelog-generator/generate_changelog.py around lines 303 -
363, has_existing_changelog_comment currently only fetches the first 30 comments
and relies on a regex; change it to paginate through all comments (loop with
page=1..N, using per_page=100 and stop when no comments returned) and search for
a unique HTML marker string (use marker = "<!-- openwisp-changelog-bot -->")
instead of the PR-number regex; also update the code that builds the PR comment
in main() to prepend that same marker to the posted body so post_github_comment
receives/comment contains the marker and duplicates are reliably detected (refer
to has_existing_changelog_comment and the comment construction in main).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

instead of adding pagination, i have adjusted to get latest 50 comments on the PR, this will suffice. The main purpose of has_existing_changelog_comment is that if we have approval from 2 maintainers on a PR then changelog should not be generated the second time.

Copy link

Choose a reason for hiding this comment

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

@pushpitkamboj, thanks for addressing the comment limit! Increasing to 50 should cover most PRs and will handle the double-approval scenario you described.

One small note: the unique HTML marker (<!-- openwisp-changelog-bot -->) suggestion was mainly about improving reliability—even with 50 comments, the regex pattern could theoretically match unrelated comments that happen to reference the PR number in backticks or brackets. The marker would make the detection more deterministic, but if you're confident the current regex works well for your use case, that's perfectly fine.

Comment on lines 79 to 82
"github_actions": [
"google-genai>=1.62.0",
"requests",
],
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add version bounds for the new github_actions extras.

To reduce breakage risk from future major releases (and to align with other extras), consider constraining these to a tested range. Adjust bounds to what you've validated.

♻️ Suggested bounds (adjust to tested versions)
-        "github_actions": [
-            "google-genai>=1.62.0",
-            "requests",
-        ],
+        "github_actions": [
+            "google-genai>=1.62.0,<2.0.0",
+            "requests>=2.32.0,<3.0.0",
+        ],
📝 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
"github_actions": [
"google-genai>=1.62.0",
"requests",
],
"github_actions": [
"google-genai>=1.62.0,<2.0.0",
"requests>=2.32.0,<3.0.0",
],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setup.py` around lines 79 - 82, The "github_actions" extras list currently
lacks upper version bounds; update the extras definition for "github_actions" to
constrain the packages (e.g., set "google-genai>=1.62.0,<2.0.0" and
"requests>=2.0.0,<3.0.0" or other tested upper bounds) so future major releases
won't break the package; modify the extras mapping where "github_actions" is
defined to replace the loose entries for google-genai and requests with the
chosen bounded versions.

setup.py Outdated
"pypandoc-binary~=1.15",
],
"github_actions": [
"google-genai>=1.62.0",
Copy link
Member

Choose a reason for hiding this comment

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

specify the upper bound of the range here please

- Add pagination params to fetch last 50 comments (API defaults to 30)
- Use unique HTML marker instead of regex for reliable detection
@pushpitkamboj pushpitkamboj force-pushed the feature/AI_created_changelog branch from 0ab91b1 to 6a5f044 Compare February 25, 2026 12:01
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 releaser Related to the OpenWISP Release Tool

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[feature] Have AI propose change log entry / commit message

5 participants