{"record":{"id":"33849d97f39caa55","repo":"odysseus-dev/odysseus","slug":"no-active-run-for-this-session","errorCode":null,"errorMessage":"No active run for this session","messagePattern":"No active run for this session","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"routes/chat_routes.py","lineNumber":2399,"sourceCode":"            return StreamingResponse(_safe_stream(), media_type=\"text/event-stream\")\n\n        _detached_run = agent_runs.start(session, _safe_stream())\n        return StreamingResponse(\n            agent_runs.subscribe(session, _detached_run),\n            media_type=\"text/event-stream\",\n            headers={\"X-Odysseus-Run-Id\": _detached_run.run_id},\n        )\n\n    # ------------------------------------------------------------------ #\n    # GET /api/chat/resume — reconnect to a detached run that's still going\n    # (e.g. after reopening a session whose agent kept running in the background)\n    # ------------------------------------------------------------------ #\n    @router.get(\"/api/chat/resume/{session_id}\")\n    async def chat_resume(request: Request, session_id: str) -> StreamingResponse:\n        _verify_session_owner(request, session_id)\n        _active_run = agent_runs.get_active_run(session_id)\n        if _active_run is None:\n            raise HTTPException(404, \"No active run for this session\")\n        return StreamingResponse(\n            agent_runs.subscribe(session_id, _active_run),\n            media_type=\"text/event-stream\",\n            headers={\"X-Odysseus-Run-Id\": _active_run.run_id},\n        )\n\n    # ------------------------------------------------------------------ #\n    # POST /api/chat/stop — cancel a detached run (Stop button). Closing the SSE\n    # no longer stops it (it's detached), so the Stop button must call this.\n    # ------------------------------------------------------------------ #\n    @router.post(\"/api/chat/stop/{session_id}\")\n    async def chat_stop(request: Request, session_id: str) -> Dict[str, Any]:\n        _verify_session_owner(request, session_id)\n        _expected_run_id = request.headers.get(\"X-Odysseus-Run-Id\")\n        stopped = agent_runs.stop(session_id, _expected_run_id)\n        return {\"stopped\": stopped}\n\n    # ------------------------------------------------------------------ #","sourceCodeStart":2381,"sourceCodeEnd":2417,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/chat_routes.py#L2381-L2417","documentation":"GET /api/chat/resume/{session_id} found no active detached agent run for that session and returns 404. Resume only works while a background run is registered in agent_runs; once it finishes (or never started), there is nothing to reattach to.","triggerScenarios":"Calling resume after a detached run has completed; calling resume for a session that never had a detached run; calling it after a server restart cleared in-memory run state; racing the run's final moments.","commonSituations":"Browser reconnects to a session whose agent already finished; user bookmarks/refreshes the resume URL late; client retry logic calling resume in a loop after completion.","solutions":["Treat this 404 as 'run finished or absent' — fetch the session's messages normally instead of resuming","Use GET /api/chat/stream_status/{session_id} first to check whether a detached run is active before calling resume","Only call resume when the client previously received a run ID (X-Odysseus-Run-Id header) for an ongoing run"],"exampleFix":"// before\nconst es = new EventSource(`/api/chat/resume/${sessionId}`);\n// after\nconst st = await fetch(`/api/chat/stream_status/${sessionId}`).then(r=>r.json());\nconst es = st.status === 'streaming'\n  ? new EventSource(st.detached ? `/api/chat/resume/${sessionId}` : `/api/chat/stream/${sessionId}`)\n  : null; // fall back to loading history","handlingStrategy":"validation","validationCode":"const st = await fetch(`/api/chat/stream_status/${sessionId}`).then(r => r.ok ? r.json() : null);\nif (st?.status === 'streaming' && st.detached) {\n  const es = new EventSource(`/api/chat/resume/${sessionId}`);\n} else { loadHistory(sessionId); }","typeGuard":null,"tryCatchPattern":"try { await attachResume(sessionId); } catch (e) { if (e.status === 404) await loadHistory(sessionId); else throw e; }","preventionTips":["Probe stream_status before resume instead of assuming a run is live","Only expose resume UI when a run ID was previously issued"],"tags":["session","sse","agent-runs","not-found"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}