docir — design documents Graph

Documents / Issues / issue-b9800d8265f6

doctor and daemon status still crash where the daemon has nowhere to listen

Both read Settings.socket_path directly to report it, so the sandbox that made every dispatched command fall back still ends these two in a traceback.

issue-b9800d8265f6issueresolved#cli#daemon
View as Markdown◉ View in graph

What is wrong

Settings.socket_path asks tempfile.gettempdir() for somewhere to put the daemon's socket, and that raises where no temporary directory is usable — a read-only sandbox, which is the environment the daemon fallback exists for.

Every command that dispatches survives it: the executor catches it and runs in process (A daemon that cannot be started fails the command instead of running in process). The two that do not dispatch read the path directly to report it — docir doctor through its pre-dispatch environment snapshot, and docir daemon status — so both still end in a traceback.

They are the commands somebody runs to diagnose the first failure. The warning on stderr says the daemon could not be started; the report that would explain why crashes.

Measured

The reporter's original traceback was FileNotFoundError: No usable temporary directory, raised from docir get before the fallback existed. With the fallback, the same environment answers every read and docir doctor still raises from lifecycle.status, which calls settings.socket_path twice — once through is_running and once to report it.

Recorded as "Left open" when the fallback shipped, because closing it means deciding what DaemonStatus.socket_path reports when there is no path to report — a change to a field rather than a try.

What would fix it

DaemonStatus.socket_path becomes str | None, and None means a daemon cannot exist here, which is a different fact from "it is not running". daemon status says so instead of "not running", which would send the caller round a loop nothing in the environment can close, and doctor reports no-daemon-socket as a warning: every command works, in process, so --strict must not fail a setup that is merely slow.

daemon serve is the one caller that keeps raising. A client without a socket runs in process; a server with nowhere to bind is not a degraded daemon.

Resolution

FIXED 2026-09-18. lifecycle.socket_path answers None where the platform offers nowhere to put a socket, and DaemonStatus.socket_path carries that through. None means a daemon cannot exist here, which is a different fact from "it is not running" and is reported as one: daemon status says it cannot run here, and doctor raises no-daemon-socket as a warning with the two exits, TMPDIR or DOCIR_NO_DAEMON=1.

A warning because every command still answers. An error would fail --strict on a setup that works and is merely slow, which is the promotion Staleness as data (owner + verified + review cadence) argues against one tier down.

daemon serve is the one caller that keeps raising, and it now renders the refusal through run_local like every other non-dispatching command instead of escaping as a traceback — the client quotes the daemon's log when it will not come up, and a traceback there buries the sentence naming the cause.

Exercised against this repository's own 233-document corpus with tempfile.gettempdir() made to raise:

text
daemon status    cannot run here — no usable temporary directory for the socket
doctor --strict  ok: true, one no-daemon-socket warning, exit 0

and unchanged where a socket can exist: the path is reported and no finding is raised.

Four injections, each proven to fail its guard: letting the OSError through again, falling back to "not running", making the finding an error, and having serve invent a path.

What is still not survivable here

With no temporary directory at all, fastembed raises computing its own default cache directory, so query, get and search still fail — inside the library, before docir runs. check, doctor and daemon status work, which is what makes the situation diagnosable, and that is the whole of what this issue claimed.

The reported environment was milder than this: writes to TMPDIR were denied but reads answered, and --no-daemon get worked there. Pointing FASTEMBED_CACHE_PATH inside the store would close the harsher case, and is a decision about where a downloaded model lives rather than a try — not taken here.

To amend: Re-verify: