Skip to content

three more readers of the runtime's own files under ~/.emrg/ open their subject without asking its kind — a FIFO wedges a client's connect, every git_cmd, and the daemon's exit record #2115

Closed

Description

@argszero

Three more readers of the runtime's own files under ~/.emrg/ open their subject without asking
its kind — a FIFO at any of them wedges a client, the scheduler, or the daemon.

The rule (issue #2062's family): a reader handed a path must ask its kind before opening it.
An open is not a read — open(<fifo>, "rb") / read_text() waits for the other end and raises
nothing, so the except OSError written around it can never fire, and the call never returns.
Every site below runs on a client's entry path or on the daemon's own event loop, so the block is
the whole client or the whole daemon, and the only recovery is a restart, which belongs to the host.
Each subject's path is fixed and predictable, so nothing exotic is needed to plant one.

site subject what a FIFO there stops
emrg/connect.py::connect_to_server ~/.emrg/emrgd.token every Python client's connect and the scheduler's own connect_to_server (emrg/server/scheduler.py:2581), which is how a scheduled task reaches the daemon — it already catches (ConnectionRefusedError, FileNotFoundError, OSError), so the refusal lands in the handler that exists for a token it cannot read
emrg/server/git_utils.py::_cached_tool_path and _cache_tool_paths ~/.emrg/install-info.json (read, and the atomic write's install-info.json.tmp) resolve_git_gh(), which every one of the module's git_cmd call sites goes through — _cached_tool_path re-reads the cache on every call, so the daemon's github handlers stop there
emrg/server/daemon.py::_write_exit_record ~/.emrg/logs/emrgd-exit.log the daemon's own exit record: the append is an open, and opening a FIFO for writing waits for a reader

The previous log-file issue (#2107 / PR #2108) did not cover the third site: that fix guarded the
log-file loop, the run markers and usage-anchor.jsonl; the exit record is a separate writer
(emrg/server/daemon.py:7244).

How these were found, so the next sweep can start where this one stopped. A worklist read out of
the source by AST — every call that opens or writes a path, grouped by its enclosing function, with
any function that reaches a kind guard (non_regular_kind / _refuse…) excluded, directly or
through a same-file helper. It printed 14 candidates on master 17d3dadc; the three above are the
ones on a daemon or client entry path with a predictable subject. The sweep is a hypothesis
generator, not a finding: each row was read before it was believed, and rows whose subject cannot be
predicted in advance (a content-hashed image name) or that belong to the upgrade chain were left
alone.

Acceptance — the same shape the family's other fixes use:

  1. Each of the three sites answers a non-regular subject without opening it, with the kind named
    in the answer (a raised OSError for the token, whose callers already handle it; the cache and
    the exit record degrade to "not written this time" with a warning, because both are best-effort).
  2. An absent subject still behaves exactly as before at every site: the token read still raises
    FileNotFoundError, and the cache and the exit record still create their file — non_regular_kind
    stats, so absence must be answered before the kind is asked about.
  3. A regular-file control at each site, so the arms read the kind and not "the call failed".
  4. Every FIFO arm is bounded (tests/bounded_read.within), because without a bound the failure mode
    of a missing guard is a wedged pytest run rather than a red test.

Activity

  1. argszero commented on Oct 11, 2026

    @argszero
    OwnerAuthor

    Handled by #2118

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