fix(gerrit): use action parameter for dispatch and clean up temp repos#2331
fix(gerrit): use action parameter for dispatch and clean up temp repos#2331gvago wants to merge 3 commits intoThe-PR-Agent:mainfrom
Conversation
The Gerrit server had two bugs:
1. The {action} path parameter was captured but ignored - the server
always dispatched based on item.msg content. Now the action parameter
determines the command, with item.msg only used for the "ask" action
to provide the question text. Also made item.msg optional (defaults
to empty string) since non-ask actions don't need it, and fixed
`return HTTPException` to `raise HTTPException`.
2. prepare_repo() created a temp directory via mkdtemp() that was never
cleaned up - remove_initial_comment() had shutil.rmtree commented
out. Added a cleanup() method to GerritProvider that removes the
temp repo, called from the server's finally block after each request.
Also added __del__ as a safety net, and wired remove_initial_comment()
to delegate to cleanup().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- cleanup(): catch (OSError, PermissionError) instead of broad Exception - gerrit_server finally block: catch (LookupError, RuntimeError) for starlette context access failures instead of bare Exception - Add isinstance guard for git_providers dict before iterating - Document that GerritProvider.__del__ acts as safety net when the provider is never stored in context (e.g. early failure)
…lly block remove_initial_comment() is called during the request lifecycle while the cloned repo is still needed by subsequent commands. Calling cleanup() here deletes the repo mid-execution, breaking all downstream operations. Revert to a no-op — actual cleanup happens in the server's finally block and in __del__ as a safety net.
Review Summary by QodoFix Gerrit action parameter dispatch and temp repo cleanup
WalkthroughsDescription• Use action parameter to dispatch correct command instead of ignoring it • Fix HTTPException to be raised instead of returned for proper error handling • Clean up temporary repositories after request completes via finally block • Add __del__ safety net for cleanup when provider not stored in context Diagramflowchart LR
A["Gerrit Request<br/>with action param"] -->|"action determines<br/>command"| B["PRAgent<br/>handle_request"]
B -->|"try block"| C["Process Request"]
C -->|"finally block"| D["Cleanup temp repo"]
D -->|"via cleanup()"| E["Remove temp directory"]
F["GerritProvider<br/>__del__"] -->|"safety net"| E
File Changes1. pr_agent/git_providers/gerrit_provider.py
|
Code Review by Qodo
|
| # For the "ask" action, the question must come from item.msg. | ||
| # For all other actions, use the action path parameter as the command. | ||
| if action == Action.ask: | ||
| if not item.msg: | ||
| return HTTPException( | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail="msg is required for ask command" | ||
| ) | ||
| await PRAgent().handle_request( | ||
| f"{item.project}:{item.refspec}", | ||
| f"/{item.msg.strip()}" | ||
| ) | ||
| command = f"/{action.value} {item.msg.strip()}" | ||
| else: | ||
| command = f"/{action.value}" |
There was a problem hiding this comment.
1. ask allows blank msg 📘 Rule violation ≡ Correctness
The ask flow only checks if not item.msg, so a whitespace-only msg passes validation and produces a command like /ask , which can cause unexpected behavior downstream. External inputs should be normalized/validated (e.g., strip() then empty-check) before use.
Agent Prompt
## Issue description
The `ask` action currently accepts whitespace-only `msg` values because it checks `if not item.msg` before using `item.msg.strip()`.
## Issue Context
`item.msg` is external request input; whitespace-only content should be treated as empty and return HTTP 400.
## Fix Focus Areas
- pr_agent/servers/gerrit_server.py[41-51]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| try: | ||
| git_providers = context.get("git_provider", {}) | ||
| if isinstance(git_providers, dict): | ||
| for provider in git_providers.values(): | ||
| if isinstance(provider, GerritProvider): | ||
| provider.cleanup() |
There was a problem hiding this comment.
2. Ask temp repo leak 🐞 Bug ☼ Reliability
The new finally cleanup only cleans GerritProvider instances stored in starlette_context, but the ask command path constructs its provider via get_git_provider() (not get_git_provider_with_context()), so its mkdtemp clone is not cleaned by the finally block.
Agent Prompt
### Issue description
Temp repo cleanup in `gerrit_server.handle_gerrit_request` only cleans providers stored in `starlette_context`. However, the `ask` action uses PRQuestions, which creates its GerritProvider via `get_git_provider()` and therefore bypasses the context cache, leaving the mkdtemp clone untracked and not cleaned in the request `finally`.
### Issue Context
- `handle_gerrit_request` cleans only `context.get("git_provider", {})` values.
- PRQuestions (used by `/ask`) constructs providers via `get_git_provider()(pr_url)`.
### Fix Focus Areas
- pr_agent/servers/gerrit_server.py[53-80]
- pr_agent/tools/pr_questions.py[18-25]
- pr_agent/tools/pr_line_questions.py[21-26]
- pr_agent/tools/pr_add_docs.py[20-25]
- pr_agent/tools/pr_config.py[17-22]
- pr_agent/tools/pr_generate_labels.py[27-32]
- pr_agent/tools/pr_update_changelog.py[22-27]
### Suggested change
In server/webhook execution paths, ensure tools use `get_git_provider_with_context(pr_url)` (or a helper that uses it when starlette_context is available) so the created GerritProvider instances are stored in context and reliably cleaned in the server `finally` block.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
{action}path parameter inPOST /api/v1/gerrit/{action}was captured but never used for command dispatch. Instead,item.msgwas always passed as the command. Now the action enum value determines the command (/review,/describe, etc.), withitem.msgonly appended for theaskaction. Also mademsgoptional with a default empty string since non-ask actions don't require it.return HTTPException(...)silently returns a 200 with the exception object serialized as JSON. Changed toraise HTTPException(...)so the 400 status code is actually sent.prepare_repo()creates a temp directory viamkdtemp()butremove_initial_comment()hadshutil.rmtreecommented out. Added acleanup()method toGerritProviderthat safely removes the temp directory, called from the server'sfinallyblock after each request. A__del__method serves as a safety net.remove_initial_comment()no longer callscleanup()— it's invoked during the request lifecycle while the repo is still needed. Cleanup only happens in the server'sfinallyblock and__del__.Test plan
POST /api/v1/gerrit/reviewwith emptymsgdispatches/reviewcommand (not/)POST /api/v1/gerrit/askwithmsg: "what does this do?"dispatches/ask what does this do?POST /api/v1/gerrit/askwith emptymsgreturns 400/tmp/are cleaned up after request completes (via finally block), not during the requesthandle_requestraises an exceptionReplaces #2324 (lost push access to org branch).