Skip to content

the client's daemon-start diagnostics open their subject without asking its kind — a FIFO at either path stops every start attempt #2109

Description

@argszero

The same class as #2101 / #2103 / #2105 / #2107 — an open is not a read. On a FIFO the open waits for a peer and raises nothing, so no except OSError around the call can fire: the call simply never returns.

The sites (emrg/client/daemon_manager.py, master 60541e39)

site direction what a FIFO at the path does
_truncate_start_stderr (:161, open(path, "wb")) writer start_daemon opens the child's stderr before it spawns, so the open waits for a reader and every start attempt from this client stops there
_read_start_stderr (:185, path.read_bytes()) reader waits for a writer while the failure report is being built
_read_log_tail (:241, path.exists() + open(path, "rb")) reader .exists() is true for a FIFO, which is exactly the guard that lets the block through

The subjects are logs_dir()/emrgd-start.err and logs_dir()/emrgd.log (_start_stderr_path, _log_path).

Measured on master 60541e39

One child process per arm under an 8 s cap, with a regular-file control for each subject, so the axis read is the kind of the path and not its absence:

truncate   regular    -> RETURNED BufferedWriter
truncate   fifo       -> DID NOT RETURN within 8s
truncate   directory  -> RETURNED NoneType
read_start regular    -> RETURNED ''
read_start fifo       -> DID NOT RETURN within 8s
read_start directory  -> RETURNED None
log_tail   regular    -> RETURNED ''
log_tail   fifo       -> DID NOT RETURN within 8s
log_tail   directory  -> RETURNED ''

The directory column is what fixes the design: the kind one over already produces each site's answer (None for the writer, which the caller reads as "fall back to DEVNULL"; None for the stderr reader, which the report already names "could not be read"; "" for the log tail, "nothing new"). So the guard adds no new outcome — only that the existing one arrives.

The fix

Ask the subject's kind before opening it, through the tool layer's path-taking non_regular_kind — imported rather than restated, so the boundary stays one fact with one spelling — and take the answer each site already gives. _read_log_tail's exists() is replaced rather than kept as a second opinion.

Acceptance

  • each site returns its own existing answer for a non-regular subject instead of blocking;
  • a FIFO-free arm per site, on a directory, that discriminates: both trees answer the same value there, so the arm asserts the kind is named in the log with the path — which the base cannot produce, and which is the arm the Windows leg runs;
  • the regular-file controls are unchanged, and the FIFO arms are POSIX-gated (os.mkfifo) and bounded by tests/bounded_read.within;
  • uv run pytest tests/ green, with check-undefined-names, check-doc-count, check-citation-resolves, check_nonlocal and check_unbound_reads at rc 0.

Out of scope, named rather than silently under-reported: the GUI's JS client carries the same three shapes (emrg/gui/daemon_client.js:247, :262, :278 — readFileSync / openSync(file, "w") / readFileSync). This issue does not cover it.

Activity

  1. argszero commented on Oct 11, 2026

    @argszero
    OwnerAuthor

    Handled by #2112

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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