Repository navigation
emrg: a register still at its pre-rename name reads as an empty register to every in-package reader #2545
Description
What was observed
The register of agents lives at ~/.emrg/agents.yml since the rename from projects.yml, and emrg.config.agents_file() is its one derivation. emrg.config.migrate_legacy_registry() is the one call that moves a register still at the old name, and it runs only from the daemon's startup.
That leaves a state the readers do not handle. migrate_legacy_registry is documented to answer False and leave the legacy file exactly where it is when the move cannot happen — a read-only home, a permission boundary, a concurrent caller that got there first. A checkout also runs ahead of the daemon that still holds the register at the old name. In both states agents_file() names a file that does not exist, and every in-package reader that asks only agents_file() reads an empty register while the real one sits one filename away.
Measured on a host in exactly that state (~/.emrg/agents.yml absent, ~/.emrg/projects.yml holding 34 entries):
| reader | answers | what the answer costs |
|---|---|---|
SubmitRantTool._registered_agent_names() |
[] |
submit_rant's "this agent name is not registered, candidates are …" warning is gated on if registered and …, so it never fires: a mis-spelled agent name is accepted with no candidates listed |
scheduler._resolve_agent_path("emrg") |
None |
an agent name does not resolve to its path |
scheduler._load_agent_config("emrg", …) |
{} |
a task template's {{ agent.<field> }} renders empty |
file_policy.protected_paths() |
contains agents.yml, not projects.yml |
the register that is actually live is not on the sandbox's protected list |
Every one of them degrades silently: each answers "nothing there" where the truth is "the register is at the other name", and no reading distinguishes the two.
Why the existing tests do not catch it
tests/test_submit_rant_tool.py pins the candidate reader against absent, malformed, unparseable and FIFO agents.yml files, and every one of those cases creates agents.yml in a temporary directory. No test creates the legacy name, so the state above is not reachable from the suite: the requirement "a register at the old name is still read" is named nowhere.
What the fix must satisfy
- One reading of "where the register is", for readers. A reader asks for the register's read path; it is
agents.ymlwhen that file exists and the legacy name while that is the one that holds it.agents_file()itself must stay a pure path that opens and moves nothing — and the fallback must not live inside it, becausemigrate_legacy_registrytakesagents_file()as its move target: a fallback there would make the target exist (it is the legacy file), so the migration would answer "already at the new name" on every host, for ever. - Reading never moves the file. Asking where the register is must not rename, create or delete anything: the migration stays the one operation that moves it, from the one process that owns it.
- The new name governs when both exist, so a legacy file left behind by an older daemon cannot shadow the current register.
- Writers keep writing the new name. This changes readers only.
- A test names the state: with only the legacy name present, each reader above answers what the register says, and the legacy file is still at its own name afterwards.
Activity
argszero commentedon Oct 23, 2026
Handled by #2546