Skip to content

the memory store's reader opens its subject whatever it is, so a FIFO in the memory directory wedges the daemon #2075

Description

@argszero

What is wrong

#2062 taught the three file tools to ask what they are opening, because an open is
not a read
: on a FIFO a read blocks until a writer appears, on a socket until a
connection does, and a device never ends. #2073 found the same condition in the
daemon's own five path readers. Both records name the third layer and leave it:

The sibling readers emrg/memory.py MemoryFile.from_file / MemoryIndex.from_file
(same condition one layer up, except (OSError, ValueError))

MemoryFile.from_file is the memory module's single reader — fourteen call sites:
_scan, list, _rebuild_index, update, merge, promote_to_project,
get_by_filename, _resolve_filename, get_with_reason. It hands the path straight to
Path.read_text, and its callers wrap the call in except (OSError, ValueError), which
cannot bound this: a FIFO raises nothing, it simply never returns.

The read is synchronous, inside a coroutine the daemon awaits on its event loop, so the
block is the whole daemon — every session, the scheduler and the evolution loop with it.
Recovery is a restart, which belongs to the host.

Two client frames walk this reader, and the directory it walks is the agent's own
workspace (<session cwd>/.emrg/memory/):

frame reader whose path it walks
list_memories (the GUI memory panel) MemoryStore.list() every *.md in the memory directory
read_memory MemoryStore.get_with_reason() → _scan() the same walk, to match an id

Nothing exotic has to create the pipe: anything running in the workspace can (mkfifo,
a build tool, a shell child of another session), and once one exists every memory frame
against that workspace wedges the daemon.

Measurement

Checkout /Users/argszero/.emrg/evolution/emrg on master 63ee3a54; emrg.__file__
asserted inside the probe to resolve in this checkout. One fixture per arm, each call in
its own process, killed by the parent's cap — the same method #2062 and #2073 used.

<memory>/foo.md is a FIFO, <memory>/bar.md is a regular file:
    ProjectMemoryStore.list()          -> DID NOT RETURN within 8s

the same fixture with foo.md absent (the control):
    MemoryFile.from_file(<bar.md>)     -> returned in 0.00s
    ProjectMemoryStore.list()          -> returned in 0.00s, 1 entry

What fixes it

The predicate this repository already states once — emrg.tools.base.special_file_kind,
a whitelist of S_ISREG — reached by its path-level form, so from_file refuses a
subject it cannot read instead of opening it. A regular file is unchanged, and a symlink
is judged by its target (os.stat follows it).

Not taken

  • MemoryIndex.from_file / MemoryIndex.save: the same class, but the pair is not the
    same shape. MEMORY.md is written as well as read, and a write to a FIFO blocks on
    open for writing too, so guarding the reader alone would only move the hang. That is
    a boundary of its own, and the daemon's prompt-path reader of the same file is already
    covered by #2074.
  • Refusing a non-regular path inside the walkers rather than at the reader: the walkers
    are five of the fourteen call sites, so the reader is the only site that covers them
    all.

Activity

  1. argszero commented on Oct 10, 2026

    @argszero
    OwnerAuthor

    Handled by #2076

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