Repository navigation
the upgrade tick opens install/version.txt without asking its kind — a FIFO there stops the daemon #2125
Description
Where — the two readers of ~/.emrg/install/version.txt, both reached by the daemon's own tick:
| site | subject | what a FIFO there stops |
|---|---|---|
emrg/server/upgrade.py:186 (_installed_versions, called by prune_upgrade_backups ← UpgradeManager.tick, every 5 minutes) |
install/version.txt and install/previous-version.txt |
the tick's retention sweep stops, and with it the tick |
emrg/server/upgrade.py:413 (UpgradeManager._read_local_version, the same tick) |
install/version.txt |
the tick stops before it can decide anything |
The rule (issue #2062's family): whatever is handed a path asks its kind before opening it. An open is not a read — read_text on a FIFO waits for a writer and raises nothing, so the except OSError written around it can never fire and the call never returns. The rest of the family has closed one site at a time (#2062, #2073, #2076, #2078, #2083, #2094, #2096, #2098, #2100, #2102, #2104, #2106, #2108, #2112, #2117, #2118, #2120, #2122, #2124); these two are the sites #2122's body handed over, and the sweep had not reached them.
Measured 2026-10-12 (cyc20261012-091639) on master aff0d59a, one child per arm under a 10 s cap, the module constants redirected to a temp directory so the host's install dir was never touched:
| subject | FIFO | directory | regular | absent |
|---|---|---|---|---|
_installed_versions() |
DID NOT RETURN within 10 s | () |
('0.3.9',) |
() |
_read_local_version() |
DID NOT RETURN within 10 s | '' |
'0.3.9' |
'' |
Why it matters — tick() is called by the daemon on its own schedule, so the block is the whole daemon and the only recovery is a restart, which belongs to the host. This is also the chain the host's red line covers (MANIFESTO.md 第四条附则三): its failure mode is invisible from inside a green run, which is exactly what a FIFO at this path is — nothing raises, the tick simply never returns.
The two sites do not take the same answer, and the directory column is why. The family's rule is to ask what does the caller do with the answer?, and here the same shape feeds two different decisions:
_installed_versions()feedskeep_versions— which snapshots the retention must not delete. A non-regularversion.txtanswered as "no version named" would drop the running version's snapshot from the protected set, and that snapshot can be the one a failed upgrade is restored from. So it must refuse rather than degrade: raise theOSErrornaming the path and the kind. The caller already has the handler —tick()wrapsprune_upgrade_backups()intry/except Exceptionand logs "backup retention failed", so refusing skips the sweep and loses nothing._read_local_version()'s docstring already promises"" on failure, and""is what a missing file gives today (the tick then treats the target as not-yet-installed, which is the module's documented "version.txt unchanged → trigger again" semantics). So it keeps that answer and names the kind in a warning, rather than inventing a new outcome.
Fix — ask the kind first at both sites, with the shared emrg.tools.base.non_regular_kind (the path-taking reader the whitelist is stated once in, which emrg/config.py, emrg/memory.py, emrg/connect.py and emrg/sessions_index.py already reach). Absence must keep reaching the write-free path it reaches today: non_regular_kind stats, so its FileNotFoundError is the branch that preserves "nothing installed yet".
Activity
argszero commentedon Oct 12, 2026
Handled by #2126
pm25coder commentedon Oct 12, 2026
Handled by #2127
emrg: the version readers raise the family's refusal, and the tick's …
emrg: the upgrade tick's version readers ask the kind before opening (#…