headroomlabs-ai/headroom · info · SystemExit

Shutting down...

Error message

Shutting down...

What it means

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.

Source

Thrown at headroom/cli/proxy.py:1659

                "Falling back to per-worker embedder.",
                err=True,
            )
            os.environ.pop("HEADROOM_EMBEDDING_SERVER_SOCKET", None)

    try:
        run_kwargs: dict[str, Any] = {}
        if workers != 1:
            run_kwargs["workers"] = workers
        if limit_concurrency != 1000:
            run_kwargs["limit_concurrency"] = limit_concurrency
        # Suppress run_server's legacy banner — the click CLI already printed
        # a richer one above. Direct `python -m headroom.proxy.server` keeps
        # the legacy banner via run_server's default.
        run_kwargs["print_banner"] = False
        run_server(config, **run_kwargs)
    except KeyboardInterrupt:
        click.echo("\nShutting down...")
        raise SystemExit(130) from None
    finally:
        if _embed_watchdog is not None:
            import asyncio as _asyncio2

            _asyncio2.run(_embed_watchdog.stop())

View on GitHub (pinned to 322425c43b)

Solutions

  1. Treat exit code 130 as clean shutdown in scripts: [ "$rc" -eq 130 ] && echo stopped
  2. Use a process manager (systemd/docker) that sends SIGINT or configure it to expect 130
  3. For graceful drains, prefer a SIGTERM-aware entrypoint or run behind uvicorn's own signal handling if you need in-flight request completion
  4. Watchdog teardown happens in finally — if you see extra errors there, fix the watchdog, not the interrupt path

Example fix

# before (deploy script treats any nonzero as crash)
headroom proxy start || exit 1

# after
headroom proxy start; rc=$?
[ $rc -eq 130 ] && rc=0  # Ctrl-C shutdown is success
exit $rc
Defensive patterns

Strategy: try-catch

Try / catch

proc = subprocess.Popen(["headroom", "proxy", "start", ...])
try:
    proc.wait()
except KeyboardInterrupt:
    proc.send_signal(signal.SIGINT)
    rc = proc.wait()
    assert rc in (0, 130)  # 130 = graceful Ctrl-C shutdown

Prevention

When it happens

Trigger: Pressing Ctrl-C in the terminal running `headroom proxy ...`, or a supervisor/container sending SIGINT to stop the process.

Common situations: 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).

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/e61b3001af69c204. Report an issue: GitHub.