Repository navigation
the memory index's reader and writer open a path of any kind: a FIFO beside the memories wedges MemoryIndex #2078
Description
The memory index's reader and writer open a path of any kind, so a FIFO beside the memories wedges them too
What is wrong
#2062 taught the three file tools to ask what they are opening, because an open is not a read.
#2073 found the same condition in the daemon's own five path readers, and #2076 (merged as
123d0abc) in the memory store's reader. Both #2076's issue (#2075) and this class of defect name
the layer below: emrg/memory.py's MemoryIndex pair.
MemoryIndex.from_file (emrg/memory.py) -> path.exists() then path.read_text(...)
MemoryIndex.save (emrg/memory.py) -> path.stat() then path.write_text(...)
Neither asks the subject's kind. On a FIFO both opens block with no bound and raise nothing: a read
waits for a writer, a write waits for a reader. The callers' except (OSError, ValueError) cannot
bound that, because nothing is raised — the call simply never returns.
Measurement
Checkout /Users/argszero/.emrg/evolution/emrg, detached worktree at #2076's head b831a745,
emrg.__file__ printed inside every child and resolving in that worktree. One fixture, each call in its
own process under the parent's 25 s cap:
the subject is a FIFO (<memory dir>/MEMORY.md):
MemoryIndex.from_file(<fifo>) -> DID NOT RETURN within 25s
MemoryIndex().save(<fifo>) -> DID NOT RETURN within 25s
control, the same calls with the FIFO replaced by a regular file:
MemoryIndex.from_file(<file>) -> RETURNED (0.0s)
MemoryIndex().save(<file>) -> RETURNED (0.0s)
control, the same calls with the path absent:
MemoryIndex.from_file(<absent>) -> RETURNED (0.0s)
MemoryIndex().save(<absent>) -> RETURNED (0.0s)
Both arms hang, which is why #2075 left the pair: guarding the reader alone only moves the hang to
the writer.
Reachability — weaker than #2075's, and stated as such
#2075's two frames (list_memories, read_memory) walk *.md and skip MEMORY.md, so no daemon
frame reads the index today. What reaches the pair is the store's own public API:
| caller | path |
|---|---|
MemoryStore.create(), update(), merge(), rebuild_index(), delete() |
_load_index() → MemoryIndex.from_file |
the same, plus promote_to_project() |
_save_index() → MemoryIndex.save |
so this is an in-process API boundary, not a client-reachable one: anything that runs in the daemon's
process or imports the module (a script, a skill, a future frame) can reach it, and nothing today does.
Filed rather than fixed-with-#2076 so the boundary #2075's "Not taken" section named is tracked
somewhere a merged PR cannot hide it — the note it lived in closes with the issue.
What fixes it
- The reader:
non_regular_kind(added by#2076, on master) beforeread_text, exactly the shape
MemoryFile.from_filenow has. - The writer: the same predicate before
write_text, but the answer has to be decided rather than
copied —_save_index's callers return normally today, so a refusal raised there would surface as a
new exception increate/update/merge/promote_to_project, where the honest behaviour is more
likely to skip the index write and report it (the shape_unreadable_index_noticealready gives the
daemon's carriers of the same file).
A control for both arms is in the measurement above; the tests belong beside #2076's, in
tests/test_memory.py.
Activity
argszero commentedon Oct 10, 2026
Handled by #2080 — branch
fix/the-index-reader-and-writer-ask-the-kind, commit7c2f57b9. Both halves of the pair ask the kind now; the writer's answer is a refusal rather than a skipped write, with the reason in the PR body.