From 074950b594f80c41054910f46746633850803b25 Mon Sep 17 00:00:00 2001 From: Anshul1336 <140470898+Anshul1336@users.noreply.github.com> Date: Sat, 19 Sep 2026 00:43:14 +0530 Subject: [PATCH 1/3] fix job status endpoint returning 200 on failed jobs was always returning 200 no matter what, even when the job actually failed. added a check for that and it now returns 500 with the error message instead. added a test for it too. fixes #168 --- app/api/jobs_routes.py | 4 ++++ tests/test_api.py | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/app/api/jobs_routes.py b/app/api/jobs_routes.py index 44de3ec..3913611 100644 --- a/app/api/jobs_routes.py +++ b/app/api/jobs_routes.py @@ -431,6 +431,10 @@ async def create_sweep(req: SweepRequest): @router.get("/jobs/{job_id}", response_model=JobStatus) async def get_job(job_id: str): state = _get_job_status_from_redis(job_id) + if state.get("status") == "failed": + raise HTTPException( + status_code=500, detail=state.get("error", "Training job failed") + ) return JobStatus( job_id=job_id, status=state.get("status", "unknown"), diff --git a/tests/test_api.py b/tests/test_api.py index 8a3864e..571aa40 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -176,6 +176,19 @@ def test_get_job_status_payload_shape(): assert "status" in data +def test_get_job_status_returns_500_on_failed_job(monkeypatch): + from app.api import jobs_routes + + monkeypatch.setattr( + jobs_routes, + "_get_job_status_from_redis", + lambda job_id: {"status": "failed", "job_id": job_id, "error": "OOM"}, + ) + resp = client.get("/jobs/failed-job-id") + assert resp.status_code == 500 + assert resp.json()["detail"] == "OOM" + + def test_cancel_job_returns_200(): resp = client.delete("/jobs/some-job-id") assert resp.status_code == 200 From f295ba89cb515807bf4bddf9a7020960ff7e451e Mon Sep 17 00:00:00 2001 From: Anshul1336 <140470898+Anshul1336@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:23:58 +0530 Subject: [PATCH 2/3] run ruff format on the file I touched --- app/api/jobs_routes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/api/jobs_routes.py b/app/api/jobs_routes.py index 3913611..dd95ef9 100644 --- a/app/api/jobs_routes.py +++ b/app/api/jobs_routes.py @@ -432,9 +432,7 @@ async def create_sweep(req: SweepRequest): async def get_job(job_id: str): state = _get_job_status_from_redis(job_id) if state.get("status") == "failed": - raise HTTPException( - status_code=500, detail=state.get("error", "Training job failed") - ) + raise HTTPException(status_code=500, detail=state.get("error", "Training job failed")) return JobStatus( job_id=job_id, status=state.get("status", "unknown"), From 6c8b175631571654215b3f91387ac3bd31b798d8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Singh <60318530+SahilKumar75@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:38:40 +0530 Subject: [PATCH 3/3] fix(api): keep 200 for failed jobs, assert failure in body A failed training job is a valid resource state, not a server error. Returning 500 broke the dashboard poller in app/state/training_poller_state.py, which only reads 200 responses: failed jobs stayed "running" forever when the pub/sub event was missed, and the error message was never shown. Revert the 500 and replace the test with one that pins the contract: GET /jobs/{id} returns 200 with status="failed" and the error text. --- app/api/jobs_routes.py | 2 -- tests/test_api.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/api/jobs_routes.py b/app/api/jobs_routes.py index dd95ef9..44de3ec 100644 --- a/app/api/jobs_routes.py +++ b/app/api/jobs_routes.py @@ -431,8 +431,6 @@ async def create_sweep(req: SweepRequest): @router.get("/jobs/{job_id}", response_model=JobStatus) async def get_job(job_id: str): state = _get_job_status_from_redis(job_id) - if state.get("status") == "failed": - raise HTTPException(status_code=500, detail=state.get("error", "Training job failed")) return JobStatus( job_id=job_id, status=state.get("status", "unknown"), diff --git a/tests/test_api.py b/tests/test_api.py index 571aa40..490e15c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -176,7 +176,10 @@ def test_get_job_status_payload_shape(): assert "status" in data -def test_get_job_status_returns_500_on_failed_job(monkeypatch): +def test_get_job_status_reports_failed_job_in_body(monkeypatch): + # A failed job is a valid resource state, not a server error: the endpoint + # returns 200 and surfaces the failure via `status` and `error`. The UI + # poller (app/state/training_poller_state.py) relies on this contract. from app.api import jobs_routes monkeypatch.setattr( @@ -185,8 +188,10 @@ def test_get_job_status_returns_500_on_failed_job(monkeypatch): lambda job_id: {"status": "failed", "job_id": job_id, "error": "OOM"}, ) resp = client.get("/jobs/failed-job-id") - assert resp.status_code == 500 - assert resp.json()["detail"] == "OOM" + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "failed" + assert data["error"] == "OOM" def test_cancel_job_returns_200():