You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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:
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).
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.
A regular-file control at each site, so the arms read the kind and not "the call failed".
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
argszero commentedon Oct 11, 2026
Handled by #2118
emrg: three more readers of the runtime's own files ask their subject…