What happened
GET /health correctly reports "ok": false and "status": "degraded" in the JSON body when one or more scanner binaries (semgrep, osv-scanner, gitleaks) are not on PATH. However, it always responds with HTTP 200 OK regardless of degraded state.
This means any automated probe — Docker HEALTHCHECK, Kubernetes liveness/readiness probe, or a load-balancer health check — will treat a broken PatchPilot instance as healthy.
Steps to reproduce
- Rename or remove
semgrep from your PATH (e.g. which semgrep && sudo mv $(which semgrep) /tmp/semgrep_bak).
- Start the backend:
uvicorn app.main:app --reload --port 8000.
curl -i http://localhost:8000/health
- Observe: HTTP status is 200 even though the body contains
"ok": false.
Expected behaviour
200 OK when all scanners are present ("ok": true).
503 Service Unavailable when any scanner is missing ("ok": false).
Actual behaviour
Always 200 OK.
Relevant code — backend/app/main.py, lines 120–134:
@app.get("/health")
def health():
scanners = {
"semgrep": shutil.which("semgrep") is not None,
"osv-scanner": shutil.which("osv-scanner") is not None,
"gitleaks": shutil.which("gitleaks") is not None,
}
healthy = all(scanners.values())
return {
"ok": healthy,
"status": "healthy" if healthy else "degraded",
"scanners": scanners,
}
The body is correct but the HTTP status code is always 200 because FastAPI defaults to 200 for sync route handlers.
Environment
| Field |
Value |
| OS |
Any |
| Python version |
3.10+ |
| PatchPilot version / commit |
main |
Logs
No error logs — this is a silent wrong status code.
Additional context
Fix: Import Response and return a JSONResponse with status_code=503 when healthy is False, or use FastAPI's Response parameter:
from fastapi.responses import JSONResponse
@app.get("/health")
def health():
...
status_code = 200 if healthy else 503
return JSONResponse(content={...}, status_code=status_code)
Acceptance criteria:
What happened
GET /healthcorrectly reports"ok": falseand"status": "degraded"in the JSON body when one or more scanner binaries (semgrep,osv-scanner,gitleaks) are not onPATH. However, it always responds with HTTP 200 OK regardless of degraded state.This means any automated probe — Docker
HEALTHCHECK, Kubernetes liveness/readiness probe, or a load-balancer health check — will treat a broken PatchPilot instance as healthy.Steps to reproduce
semgrepfrom yourPATH(e.g.which semgrep && sudo mv $(which semgrep) /tmp/semgrep_bak).uvicorn app.main:app --reload --port 8000.curl -i http://localhost:8000/health"ok": false.Expected behaviour
200 OKwhen all scanners are present ("ok": true).503 Service Unavailablewhen any scanner is missing ("ok": false).Actual behaviour
Always
200 OK.Relevant code —
backend/app/main.py, lines 120–134:The body is correct but the HTTP status code is always 200 because FastAPI defaults to 200 for sync route handlers.
Environment
mainLogs
No error logs — this is a silent wrong status code.
Additional context
Fix: Import
Responseand return aJSONResponsewithstatus_code=503whenhealthyisFalse, or use FastAPI'sResponseparameter:Acceptance criteria:
GET /healthreturns503when any scanner binary is absent.GET /healthreturns200when all scanner binaries are present.tests/test_health.pyupdated to assert the correct status code for both states.