odysseus-dev/odysseus · error · HTTPException
Refusing to signal PID {req.pid} (<100, likely system proces
Error message
Refusing to signal PID {req.pid} (<100, likely system process) What it means
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.
Source
Thrown at routes/cookbook_routes.py:3326
return {"ok": False, "error": nvidia_error or "No GPU memory probe available", "gpus": []}
class KillPidRequest(BaseModel):
pid: int
host: str | None = None
ssh_port: str | None = None
signal: str = "TERM" # TERM (graceful) or KILL (force)
@router.post("/api/cookbook/kill-pid")
async def kill_pid(request: Request, req: KillPidRequest):
"""Kill a PID that's holding GPU memory.
Admin-gated. Validates PID is positive int, signal is TERM/KILL, and
forbids low PIDs (<100) to avoid accidentally signalling init/system
daemons. Uses `kill -<sig> <pid>` locally or over SSH.
"""
require_admin(request)
if req.pid < 100:
raise HTTPException(400, f"Refusing to signal PID {req.pid} (<100, likely system process)")
sig = (req.signal or "TERM").upper()
if sig not in ("TERM", "KILL", "INT"):
raise HTTPException(400, "signal must be TERM, KILL, or INT")
host = validate_remote_host(req.host)
req.ssh_port = validate_ssh_port(req.ssh_port)
kill_cmd = f"kill -{sig} {req.pid}"
try:
if host:
pf = f"-p {req.ssh_port} " if req.ssh_port and req.ssh_port != "22" else ""
cmd = f"ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no {pf}{host} '{kill_cmd}'"
proc = await asyncio.create_subprocess_shell(
cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
elif IS_WINDOWS:
# No `kill` binary / POSIX signals on Windows. taskkill /F /T tears
# down the PID and its children. There's no graceful-vs-force
# distinction, so TERM/KILL/INT all map to the same forced kill.
# NB: never use os.kill(pid, 0) to probe here — on Windows thatView on GitHub (pinned to f9235ebbf1)
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.
Example fix
// before
await post('/api/cookbook/kill-pid', { pid: 0, signal: 'KILL' }); // 400
// after
await post('/api/cookbook/kill-pid', { pid: 23841, signal: 'TERM' }); Defensive patterns
Strategy: validation
Validate before calling
function assertKillablePid(pid: number) {
if (!Number.isInteger(pid) || pid < 100) {
throw new Error(`PID ${pid} is invalid or below the safe threshold (>=100 required)`);
}
} Type guard
const isSafePid = (pid: unknown): pid is number => typeof pid === 'number' && Number.isInteger(pid) && pid >= 100;
Try / catch
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; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- signal must be TERM, KILL, or INT
- Invalid gpus — expected comma-separated GPU indexes
- Remote Windows Diffusers serving is not supported yet; use l
- host is required
- d && d.detail ? d.detail : ('HTTP ' + res.status)
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/912e5b2e0ece2aa4.
Report an issue: GitHub.