Skip to content

[Bug] GET /health always returns HTTP 200 even when scanners are missing #303

Description

@arpit2006

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

  1. Rename or remove semgrep from your PATH (e.g. which semgrep && sudo mv $(which semgrep) /tmp/semgrep_bak).
  2. Start the backend: uvicorn app.main:app --reload --port 8000.
  3. curl -i http://localhost:8000/health
  4. 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:

  • GET /health returns 503 when any scanner binary is absent.
  • GET /health returns 200 when all scanner binaries are present.
  • tests/test_health.py updated to assert the correct status code for both states.

Metadata

Metadata

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions