{"record":{"id":"912e5b2e0ece2aa4","repo":"odysseus-dev/odysseus","slug":"refusing-to-signal-pid-req-pid-100-likely-sys","errorCode":null,"errorMessage":"Refusing to signal PID {req.pid} (<100, likely system process)","messagePattern":"Refusing to signal PID (.+?) \\(<100, likely system process\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/cookbook_routes.py","lineNumber":3326,"sourceCode":"        return {\"ok\": False, \"error\": nvidia_error or \"No GPU memory probe available\", \"gpus\": []}\n\n    class KillPidRequest(BaseModel):\n        pid: int\n        host: str | None = None\n        ssh_port: str | None = None\n        signal: str = \"TERM\"  # TERM (graceful) or KILL (force)\n\n    @router.post(\"/api/cookbook/kill-pid\")\n    async def kill_pid(request: Request, req: KillPidRequest):\n        \"\"\"Kill a PID that's holding GPU memory.\n\n        Admin-gated. Validates PID is positive int, signal is TERM/KILL, and\n        forbids low PIDs (<100) to avoid accidentally signalling init/system\n        daemons. Uses `kill -<sig> <pid>` locally or over SSH.\n        \"\"\"\n        require_admin(request)\n        if req.pid < 100:\n            raise HTTPException(400, f\"Refusing to signal PID {req.pid} (<100, likely system process)\")\n        sig = (req.signal or \"TERM\").upper()\n        if sig not in (\"TERM\", \"KILL\", \"INT\"):\n            raise HTTPException(400, \"signal must be TERM, KILL, or INT\")\n        host = validate_remote_host(req.host)\n        req.ssh_port = validate_ssh_port(req.ssh_port)\n        kill_cmd = f\"kill -{sig} {req.pid}\"\n        try:\n            if host:\n                pf = f\"-p {req.ssh_port} \" if req.ssh_port and req.ssh_port != \"22\" else \"\"\n                cmd = f\"ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no {pf}{host} '{kill_cmd}'\"\n                proc = await asyncio.create_subprocess_shell(\n                    cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE\n                )\n            elif IS_WINDOWS:\n                # No `kill` binary / POSIX signals on Windows. taskkill /F /T tears\n                # down the PID and its children. There's no graceful-vs-force\n                # distinction, so TERM/KILL/INT all map to the same forced kill.\n                # NB: never use os.kill(pid, 0) to probe here — on Windows that","sourceCodeStart":3308,"sourceCodeEnd":3344,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/cookbook_routes.py#L3308-L3344","documentation":"Raised (HTTP 400) by POST /api/cookbook/kill-pid (admin-gated) when req.pid < 100. Low PIDs on Unix-like systems are kernel threads and core system daemons (init is 1, kworkers and early daemons occupy the low range), so signalling them risks destabilizing or rebooting the host. The guard fires before any local kill or SSH kill command is constructed.","triggerScenarios":"POST /api/cookbook/kill-pid with body {\"pid\": 1}, {\"pid\": 42}, or any pid in 0–99. Also triggered by a UI bug that defaults the PID field to 0 or 1 when no GPU-memory-holding process was actually selected.","commonSituations":"User copies a PID column misaligned with the process list (header row or footer summary); GPU diagnostics output is parsed incorrectly and a line number is sent instead of a PID; automated cleanup script passes an unset variable that defaults to 0.","solutions":["Send the real PID of the GPU-memory-holding process (nvidia-smi / fuser output), which is virtually always >= 100.","Verify the PID before submitting: check it maps to a python/ollama/docker process you own, e.g. `ps -p <pid> -o pid,comm,user`.","If a client UI auto-fills the PID field, remove the default so the user must explicitly pick a row."],"exampleFix":"// before\nawait post('/api/cookbook/kill-pid', { pid: 0, signal: 'KILL' }); // 400\n\n// after\nawait post('/api/cookbook/kill-pid', { pid: 23841, signal: 'TERM' });","handlingStrategy":"validation","validationCode":"function assertKillablePid(pid: number) {\n  if (!Number.isInteger(pid) || pid < 100) {\n    throw new Error(`PID ${pid} is invalid or below the safe threshold (>=100 required)`);\n  }\n}","typeGuard":"const isSafePid = (pid: unknown): pid is number => typeof pid === 'number' && Number.isInteger(pid) && pid >= 100;","tryCatchPattern":"try { await post('/api/cookbook/kill-pid', { pid, signal: 'TERM' }); } catch (e) { if (e.status === 400 && /<100/.test(e.message)) { toast('Pick the real GPU process PID from nvidia-smi'); return; } throw e; }","preventionTips":["Parse nvidia-smi output structurally (per-process block), never by line index.","Show process name next to PID in the kill UI so mismatches are visible before submit."],"tags":["cookbook","gpu","process-management","validation","safety-guard"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}