{"record":{"id":"b4a736c644fe3daf","repo":"ruvnet/RuView","slug":"client-client-id-not-found","errorCode":null,"errorMessage":"Client {client_id} not found","messagePattern":"Client (.+?) not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"archive/v1/src/api/routers/stream.py","lineNumber":448,"sourceCode":"        raise HTTPException(\n            status_code=500,\n            detail=\"An internal error occurred. Please try again later.\"\n        )\n\n\n@router.delete(\"/clients/{client_id}\")\nasync def disconnect_client(\n    client_id: str,\n    current_user: Dict = Depends(require_auth)\n):\n    \"\"\"Disconnect a specific WebSocket client.\"\"\"\n    try:\n        logger.info(f\"Disconnecting client {client_id} by user: {current_user['id']}\")\n        \n        success = await connection_manager.disconnect(client_id)\n        \n        if not success:\n            raise HTTPException(\n                status_code=404,\n                detail=f\"Client {client_id} not found\"\n            )\n        \n        return {\n            \"message\": f\"Client {client_id} disconnected successfully\",\n            \"timestamp\": datetime.utcnow().isoformat()\n        }\n        \n    except HTTPException:\n        raise\n    except Exception as e:\n        logger.error(f\"Error disconnecting client: {e}\")\n        raise HTTPException(\n            status_code=500,\n            detail=\"An internal error occurred. Please try again later.\"\n        )\n","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/archive/v1/src/api/routers/stream.py#L430-L466","documentation":"Deliberate 404 raised by DELETE /stream/clients/{client_id} in archive/v1/src/api/routers/stream.py when connection_manager.disconnect(client_id) returns False — meaning no active connection matches the given id. Unlike the surrounding 500 catch-alls, this is a controlled, expected outcome of a racing or stale request, not an internal failure.","triggerScenarios":"DELETE /stream/clients/{client_id} with an id that already disconnected; using a stale id captured from an earlier GET /stream/clients listing; a double-click in an admin UI sending the delete twice, where the second call finds nothing; id formatting mismatch (whitespace, URL-encoding, wrong case) between the listing and the path parameter.","commonSituations":"Race between listing clients and disconnecting one (client drops itself in between); UI not refreshing the client list after a disconnect; copy-pasting ids from logs of a previous session instead of the current GET /stream/clients response.","solutions":["Re-fetch GET /stream/clients and confirm the id is still present before retrying","Treat 404 as success when the goal is only 'make sure this client is gone'","Copy ids verbatim from the current clients listing; never construct them by hand or from stale logs","If the id should exist, compare it byte-for-byte with the listing (encoding, whitespace, case) to rule out a formatting mismatch"],"exampleFix":"# before (caller treats 404 as an error)\nresp = requests.delete(f\"{API}/stream/clients/{client_id}\")\nresp.raise_for_status()\n\n# after (caller treats 404 as already-gone)\nresp = requests.delete(f\"{API}/stream/clients/{client_id}\")\nif resp.status_code == 404:\n    logger.info(f\"client {client_id} already disconnected\")\nelse:\n    resp.raise_for_status()","handlingStrategy":"type-guard","validationCode":"import httpx\n\nlisting = httpx.get(f\"{API}/stream/clients\", headers=auth, timeout=10).json()\nknown_ids = {c[\"id\"] for c in listing[\"clients\"]}\nif client_id not in known_ids:\n    print(f\"{client_id} not connected; nothing to disconnect\")","typeGuard":"def client_connected(client_id: str, listing: dict) -> bool:\n    return any(c.get(\"id\") == client_id for c in listing.get(\"clients\", []))","tryCatchPattern":"import httpx\ntry:\n    resp = httpx.delete(f\"{API}/stream/clients/{client_id}\", headers=auth, timeout=10)\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 404:\n        pass  # already gone — treat as success\n    else:\n        raise","preventionTips":["Always re-fetch GET /stream/clients immediately before disconnecting a listed id","Copy ids verbatim from the listing; never assemble them from logs or hand-typed fragments","Treat 404 as the idempotent-success case in admin UIs and scripts"],"tags":["fastapi","http-404","websocket","streaming","idempotency","python"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}