Skip to content

[BUG] omlx serve hangs at startup banner when spawned by launchd / brew-services (works in a shell/tmux); stale server process blocks restart #1814

@igor

Description

@igor

Summary

On a headless Mac mini, omlx serve hangs immediately after the startup banner when spawned directly by launchd — via omlx start / brew services start omlx (the homebrew.mxcl.omlx job) or a hand-written LaunchAgent. The server process stays alive (~0% CPU, blocked in select), never reaches Application startup complete, and never binds the port. The same omlx serve run in an interactive shell or a tmux session starts normally (binds, loads the model, serves) within seconds.

Separately, a hung/old server process doesn't terminate cleanly: a stale LISTEN socket lingers on the port and a leftover omlx-server process makes subsequent instances hang at the banner. Only force-killing all omlx-server PIDs + freeing the port lets a fresh instance start.

Environment

  • omlx 0.4.3 (Homebrew, jundot/omlx tap)
  • macOS 26.5.1 (25F80), Mac mini Mac16,10 / Apple M4 / 24 GB
  • Headless (SSH/Screen Sharing); user is auto-logged-in at the console (a GUI session exists)
  • model_dir on an external SSD; model mlx-community/gemma-4-12B-it-qat-4bit (~10.7 GB)

Repro

  1. omlx start (or brew services start omlx, or a LaunchAgent running omlx serve).
  2. curl http://127.0.0.1:<port>/v1/models → connection refused / HTTP 000, even though lsof -iTCP:<port> -sTCP:LISTEN may show a socket.
  3. Service log shows only the banner (oMLX … Version: 0.4.3), never Application startup complete.
  4. sample <omlx-server pid> → alive, ~0% CPU, blocked in select.
  5. Same command in a shell: tmux new-session -d -s omlx 'omlx serve' → starts fully, binds, serves. ✅

Notes / hypotheses

Startup (uvicorn bind / engine-worker spawn) seems to depend on something present in an interactive/pty context but absent under a bare launchd job (controlling TTY?). Setting PYTHONUNBUFFERED, explicit PATH/HF_HOME in the LaunchAgent env, and a login-shell exec wrapper did not help. The non-clean shutdown + phantom LISTEN socket may relate to #352 (bound-but-unresponsive). Related: #833 (headless auto-start without login).

Workaround

Run omlx serve inside a tmux session (so it runs in a pty, not spawned directly by launchd), launched by a launchd agent, with an HTTP /v1/models health check (the open socket alone is unreliable) and force-kill-then-restart on failure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions