{"record":{"id":"e61b3001af69c204","repo":"headroomlabs-ai/headroom","slug":"shutting-down","errorCode":null,"errorMessage":"Shutting down...","messagePattern":"Shutting down\\.\\.\\.","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"info","filePath":"headroom/cli/proxy.py","lineNumber":1659,"sourceCode":"                \"Falling back to per-worker embedder.\",\n                err=True,\n            )\n            os.environ.pop(\"HEADROOM_EMBEDDING_SERVER_SOCKET\", None)\n\n    try:\n        run_kwargs: dict[str, Any] = {}\n        if workers != 1:\n            run_kwargs[\"workers\"] = workers\n        if limit_concurrency != 1000:\n            run_kwargs[\"limit_concurrency\"] = limit_concurrency\n        # Suppress run_server's legacy banner — the click CLI already printed\n        # a richer one above. Direct `python -m headroom.proxy.server` keeps\n        # the legacy banner via run_server's default.\n        run_kwargs[\"print_banner\"] = False\n        run_server(config, **run_kwargs)\n    except KeyboardInterrupt:\n        click.echo(\"\\nShutting down...\")\n        raise SystemExit(130) from None\n    finally:\n        if _embed_watchdog is not None:\n            import asyncio as _asyncio2\n\n            _asyncio2.run(_embed_watchdog.stop())\n","sourceCodeStart":1641,"sourceCodeEnd":1665,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/cli/proxy.py#L1641-L1665","documentation":"The proxy CLI runs uvicorn via run_server(config, **run_kwargs) inside a try/except KeyboardInterrupt. On Ctrl-C (SIGINT) it prints 'Shutting down...', exits with code 130 (the conventional 128+SIGINT), and stops the embed watchdog in a finally block. This is the normal, graceful shutdown path for a foreground proxy — exit 130 is expected, not a crash.","triggerScenarios":"Pressing Ctrl-C in the terminal running `headroom proxy ...`, or a supervisor/container sending SIGINT to stop the process.","commonSituations":"Operators restarting the proxy after config changes; CI wrapping the proxy and interpreting 130 as failure; systemd/docker stop sending SIGINT (note: docker stop sends SIGTERM, which is NOT caught here).","solutions":["Treat exit code 130 as clean shutdown in scripts: [ \"$rc\" -eq 130 ] && echo stopped","Use a process manager (systemd/docker) that sends SIGINT or configure it to expect 130","For graceful drains, prefer a SIGTERM-aware entrypoint or run behind uvicorn's own signal handling if you need in-flight request completion","Watchdog teardown happens in finally — if you see extra errors there, fix the watchdog, not the interrupt path"],"exampleFix":"# before (deploy script treats any nonzero as crash)\nheadroom proxy start || exit 1\n\n# after\nheadroom proxy start; rc=$?\n[ $rc -eq 130 ] && rc=0  # Ctrl-C shutdown is success\nexit $rc","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"proc = subprocess.Popen([\"headroom\", \"proxy\", \"start\", ...])\ntry:\n    proc.wait()\nexcept KeyboardInterrupt:\n    proc.send_signal(signal.SIGINT)\n    rc = proc.wait()\n    assert rc in (0, 130)  # 130 = graceful Ctrl-C shutdown","preventionTips":["Treat exit 130 as success in wrapper scripts and CI gates","Send SIGINT (not SIGKILL) to stop the proxy so the finally-block watchdog teardown runs","For docker, note `docker stop` sends SIGTERM — run under an init wrapper that forwards it as SIGINT or accept the non-graceful path"],"tags":["cli","proxy","shutdown","keyboard-interrupt","exit-code"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}