fix: per-request settings clone in bitbucket-server & azuredevops webhooks (#2345)#2351
fix: per-request settings clone in bitbucket-server & azuredevops webhooks (#2345)#2351alonz101 wants to merge 1 commit intoThe-PR-Agent:mainfrom
Conversation
…hooks (The-PR-Agent#2345) apply_repo_settings() mutates the module-level global_settings singleton. In long-lived webhook servers, .pr_agent.toml from a previously-reviewed repo leaked into PRs from other repos. 5 of 7 servers already cloned global_settings into context["settings"] per request - this aligns the last 2. Adds regression tests including the unknown-section case.
Review Summary by QodoFix per-request settings isolation in bitbucket-server and azuredevops webhooks
WalkthroughsDescription• Clone global_settings per-request in bitbucket-server and azuredevops webhooks • Prevents .pr_agent.toml state leakage across different repository PRs • Aligns last 2 webhook servers with existing pattern used by 5 others • Adds comprehensive regression tests for settings isolation Diagramflowchart LR
A["Webhook Request"] --> B["Clone global_settings"]
B --> C["context['settings']"]
C --> D["apply_repo_settings"]
D --> E["Isolated per-request"]
F["Next Request"] --> B
E -.->|"No leak"| F
File Changes1. pr_agent/servers/azuredevops_server_webhook.py
|
|
PR Description updated to latest commit (fc8ab42) |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨
|
Looked into the BackgroundTask concern — false positiveThe auto @app.post("/webhook")
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
data = await request.json()
context["settings"] = f"clone-for-{data['repo']}"
background_tasks.add_task(check, data["repo"])
Two sequential requests:
request 'A' → context['settings'] = 'clone-for-A' ✅
request 'B' → context['settings'] = 'clone-for-B' ✅
starlette_context uses a ContextVar and the middleware wraps the request with with request_cycle_context(...).
Starlette's BackgroundTask is awaited inside Response.__call__, before the middleware's with block exits — so
background tasks see the per-request clone, not the global singleton. Same reason the 5 existing webhook servers
using this pattern work fine.
No code change needed. |
User description
Summary
Fixes #2345 —
apply_repo_settings()leaking.pr_agent.tomlstate across PRs from different repositories.Root cause
global_settingsis a module-level Dynaconf singleton.apply_repo_settings()mutates it without resetting between requests, so settings loaded for one repo persist into subsequent reviews of unrelated repos in the same long-lived webhook process.Fix
5 of 7 webhook servers already isolate per-request settings with
context["settings"] = copy.deepcopy(global_settings)(github_app, gitlab_webhook, gitea_app, bitbucket_app, gerrit_server). This PR aligns the last 2 (bitbucket_server_webhook,azuredevops_server_webhook) with that existing pattern.get_settings()already preferscontext["settings"]overglobal_settings, so the per-request clone is sufficient.Tests
tests/unittest/test_apply_repo_settings.pycovers:[pr_reviewer]extra_instructions don't leak into a subsequent Repo B request that has no.pr_agent.toml[my_custom_repo_section]) doesn't leak eitherTest plan
PYTHONPATH=. .venv/bin/pytest tests/unittest/test_apply_repo_settings.py -v(3 passed)PYTHONPATH=. .venv/bin/pytest tests/unittest -q(297 passed, no regressions)pre-commit run --files <changed>(clean)PR Type
Bug fix, Tests
Description
Fix
.pr_agent.tomlstate leaking across PRs from different repositoriesAdd
context["settings"] = copy.deepcopy(global_settings)per-request isolation tobitbucket_server_webhookandazuredevops_server_webhookhandlersAdd regression tests covering settings isolation, unknown section leaks, and TOML application
Minor whitespace/formatting cleanup in both webhook server files
Diagram Walkthrough
File Walkthrough
azuredevops_server_webhook.py
Add per-request settings clone to Azure DevOps webhook handlerpr_agent/servers/azuredevops_server_webhook.py
import copyand importedcontextfromstarlette_contextandglobal_settingsfromconfig_loadercontext["settings"] = copy.deepcopy(global_settings)at thestart of
handle_webhook()to isolate per-request settingsfunctions)
bitbucket_server_webhook.py
Add per-request settings clone to Bitbucket Server webhook handlerpr_agent/servers/bitbucket_server_webhook.py
import copyand importedcontextfromstarlette_contextandglobal_settingsfromconfig_loadercontext["settings"] = copy.deepcopy(global_settings)at thestart of
handle_webhook()to isolate per-request settingsfunctions)
BitbucketServerProvidertest_apply_repo_settings.py
Add regression tests for cross-repo settings isolationtests/unittest/test_apply_repo_settings.py
FakeGitProviderstub andfresh_global_settingsfixture
test_repo_settings_from_toml_are_applied: verifies TOML settings areapplied within a request context
test_repo_without_toml_does_not_inherit_previous_repo_settings:verifies repo A's settings don't leak into repo B with no
.pr_agent.tomltest_unknown_section_does_not_leak_to_next_repo: verifiescustom/unknown TOML sections don't persist across requests