fix: refactor _validate_shellcheck to async subprocess execution to unblock FastAPI event loop#986
fix: refactor _validate_shellcheck to async subprocess execution to unblock FastAPI event loop#986the404packet wants to merge 9 commits into
Conversation
|
@the404packet is attempting to deploy a commit to the rishabhmishra0510-5147's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
More reviews will be available in 53 minutes and 25 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR converts the safety validation pipeline from synchronous to asynchronous execution to prevent event loop blocking during subprocess and validation operations. The core ChangesSafety Validation & Service Layer Async Conversion
Sequence Diagram(s)sequenceDiagram
participant Caller
participant validate_rendered_output
participant run_in_executor
participant _validate_bash_ast
participant _validate_shellcheck
participant LLM_Client
Caller->>validate_rendered_output: await validate_rendered_output(content, template_name, llm_client)
validate_rendered_output->>run_in_executor: run_in_executor(_validate_bash_ast, content)
run_in_executor->>_validate_bash_ast: parse and validate AST in thread
_validate_bash_ast-->>run_in_executor: AST analysis result
validate_rendered_output->>_validate_shellcheck: await _validate_shellcheck(content)
_validate_shellcheck->>_validate_shellcheck: asyncio.create_subprocess_exec(shellcheck)
_validate_shellcheck->>_validate_shellcheck: enforce 5s timeout, parse JSON output
_validate_shellcheck-->>validate_rendered_output: SafetyViolationError or pass
validate_rendered_output->>LLM_Client: await llm_client.complete(...) if enabled
LLM_Client-->>validate_rendered_output: AI audit result
validate_rendered_output-->>Caller: safe_content or raise SafetyViolationError
sequenceDiagram
participant Caller
participant render_all
participant render
participant validate_rendered_output
Caller->>render_all: await render_all(output_filenames, context)
loop for each filename
render_all->>render: await render(filename, context)
render->>render: template.render(context)
render->>validate_rendered_output: await validate_rendered_output(content, template_name)
validate_rendered_output-->>render: safe_content
render-->>render_all: RenderResult(template_id, content, filename, size)
end
render_all-->>Caller: list of RenderResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@rishabh0510rishabh this pr is ready for review the backend and frontend test failing are due to problems introduces by previous pr. This problems should be addressed in new issue |
🔍 PR Action RequiredHi @the404packet, We detected some items on this Pull Request that require attention: ❌ Failing CI ChecksThe following check runs or commit statuses are failing (ignoring vercel): Please resolve the issues above to proceed. Last updated: Thu, 11 Jun 2026 04:37:07 GMT |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/app/services/repair_service.py (1)
171-217:⚠️ Potential issue | 🟠 MajorUpdate unit tests to await
render_repair
render_repairisasync def, butbackend/tests/unit/ai/test_repair_service.pycalls it withoutawait(e.g.,result = service.render_repair(template_id, params)), soresultwill be a coroutine and the assertions will fail. Update the affected tests toasync defandawait service.render_repair(...)(add the appropriate asyncio/pytest marker if needed).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/services/repair_service.py` around lines 171 - 217, Tests currently call the async function render_repair without awaiting it, returning a coroutine; update each affected test (in the repair service unit tests) to be async def and await service.render_repair(...), and add the appropriate pytest async support (e.g., apply `@pytest.mark.asyncio` or ensure pytest-asyncio is enabled) so assertions receive the actual result instead of a coroutine.
🧹 Nitpick comments (1)
backend/tests/unit/templates/test_safety.py (1)
319-328: ⚡ Quick winConsider asserting cleanup calls in timeout test.
The test correctly verifies graceful timeout handling (content is returned). To strengthen the test, you could also assert that
process.kill()andprocess.wait()were called to verify the zombie-prevention cleanup documented in the PR objectives.🧪 Optional enhancement to verify cleanup behavior
async def test_shellcheck_timeout_graceful(): mock_process = AsyncMock() mock_process.communicate = AsyncMock(side_effect=TimeoutError()) mock_process.kill = MagicMock() mock_process.wait = AsyncMock() with patch("asyncio.create_subprocess_exec", return_value=mock_process): content = "echo 'safe content'" assert await validate_rendered_output(content, "setup.sh") == content + mock_process.kill.assert_called_once() + mock_process.wait.assert_awaited_once()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/unit/templates/test_safety.py` around lines 319 - 328, The test test_shellcheck_timeout_graceful should also assert that the subprocess cleanup was invoked: after patching asyncio.create_subprocess_exec to return mock_process and calling validate_rendered_output, add assertions that mock_process.kill() was called (e.g., mock_process.kill.assert_called_once()) and that mock_process.wait() was awaited (e.g., awaitable assertion like mock_process.wait.assert_awaited() or mock_process.wait.assert_awaited_once()); this verifies the timeout path triggers the documented zombie-prevention cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@backend/app/services/repair_service.py`:
- Around line 171-217: Tests currently call the async function render_repair
without awaiting it, returning a coroutine; update each affected test (in the
repair service unit tests) to be async def and await service.render_repair(...),
and add the appropriate pytest async support (e.g., apply `@pytest.mark.asyncio`
or ensure pytest-asyncio is enabled) so assertions receive the actual result
instead of a coroutine.
---
Nitpick comments:
In `@backend/tests/unit/templates/test_safety.py`:
- Around line 319-328: The test test_shellcheck_timeout_graceful should also
assert that the subprocess cleanup was invoked: after patching
asyncio.create_subprocess_exec to return mock_process and calling
validate_rendered_output, add assertions that mock_process.kill() was called
(e.g., mock_process.kill.assert_called_once()) and that mock_process.wait() was
awaited (e.g., awaitable assertion like mock_process.wait.assert_awaited() or
mock_process.wait.assert_awaited_once()); this verifies the timeout path
triggers the documented zombie-prevention cleanup.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: beea1608-1e9e-46f8-b189-5c9038fd689f
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
backend/app/ai/service.pybackend/app/api/v1/diagnose.pybackend/app/services/repair_service.pybackend/app/templates/engine.pybackend/app/templates/safety.pybackend/tests/integration/test_ai_pipeline.pybackend/tests/unit/templates/test_safety.py
Description
The
_validate_shellcheckfunction was usingsubprocess.run, a synchronous blocking call, to execute the externalshellcheckbinary. Under concurrent load, this blocked the main ASGI event loop, degrading API throughput and causing request timeouts.This PR refactors the shellcheck execution to use
asyncio.create_subprocess_exec, offloads the CPU-bound_validate_bash_ast(bashlex parsing) to a thread pool viarun_in_executor, and makesvalidate_rendered_outputfully async; eliminating the event loop blocking entirely.Related Issues
Fixes #532
Changes Made
_validate_shellchecktoasyncusingasyncio.create_subprocess_execinstead ofsubprocess.runsubprocess.TimeoutExpiredhandling withasyncio.wait_fortimeout; added explicitprocess.kill()+process.wait()to prevent zombie processes on timeoutvalidate_rendered_outputasync; offloaded_validate_bash_astto thread pool vialoop.run_in_executorvalidate_rendered_output— removed the sync/async loop-detection workaround since the function is now always awaitedawait validate_rendered_output(...):backend/app/templates/engine.py—renderandrender_allmade asyncbackend/app/services/repair_service.py—render_repairmade asyncbackend/app/ai/service.py—_validate_response_safetymade async, both call sites updatedbackend/app/api/v1/diagnose.py— 3 call sites updatedasync def+awaitwithpytestmark = pytest.mark.asynciomonkeypatch(subprocess.run)in shellcheck tests withpatch(asyncio.create_subprocess_exec)usingAsyncMockVerification
pytest tests/successfullySafetyFilterDocumentation
docs/FEATURES.md(if adding a feature/profile)CHANGELOG.mdSummary by CodeRabbit
Bug Fixes
Refactor