diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-27 22:21:42 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-27 22:21:42 +0200 |
| commit | 6c7b61a8e946b777ea1985058dbed9019bddd53a (patch) | |
| tree | 885821ea5c6cdfa1833832b9da4bc848f9cd57be /CLAUDE.md | |
| parent | a45e77df1b0024707914a446aa89d33baa223787 (diff) | |
| download | meshbay-6c7b61a8e946b777ea1985058dbed9019bddd53a.tar.gz | |
docs: the Windows node's three modes and one lifecycle, and what it cost
- MESHBAY_DESIGN.md §11.2: the node runs only while the app is open, at
sign-in, or as a boot-time service; starting and stopping have one
implementation, the CLI's; a second instance refuses before it writes
anything the running one depends on.
- packaging/win/README.md: the three modes, switching between them, upgrading
a running node, where the log is, the service task's settings.
- docs/windows-build.md: the build's smoke start of the frozen daemon, the log
location, and what an upgrade does to a running node.
- CLAUDE.md: four engineering lessons -- an upgrade that cannot stop the node
installs around it; on Windows the CLI is the process it is stopping; a
second instance must fail before it touches anything shared; a test that
redirects HOME isolates nothing on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 53 |
1 files changed, 53 insertions, 0 deletions
@@ -343,6 +343,59 @@ do. Read them before writing anything that touches the same mechanism. the page. `ask.js` draws both in the page, and `test_no_native_dialogs_in_the_spa.py` bans all three browser dialogs +- **An upgrade that cannot stop the node installs around it.** In service mode + the daemon runs in the task's S4U session, and the installer's unelevated + `taskkill` got "Access is denied" — which `main.js` had already recorded on + 2026-09-14, for itself only. It also ran in `customInstall`, which + electron-builder inserts *after* the files are copied; a test asserted that + `taskkill` under the name "stops a running daemon before overwriting it", + wrong on both counts. The locked `meshbay-node.exe` was not replaced, the + last-resort extract ignored that, and since a onedir exe embeds all the + node's Python, the 0.16 app and hub talked to the previous node: "started but + could not link", "No operator paired" (a node not yet signed in has not read + its roster, and answered `false`), and `service start` printing "started" + about a node nobody had checked. Linux never saw it — the package manager + restarts the unit. Stopping now happens in `customCheckAppRunning` + (`schtasks /end`, which the owner may do), the daemon logs to + `%LOCALAPPDATA%\meshbay\state\node.log` because Task Scheduler discards its + stderr, and the build starts the frozen daemon instead of only asking for + `--help`. **Read the installer framework's template before believing a hook + runs when its name suggests**, and **a message about a node must come from a + node that was asked** — every one of those four was a guess stated as a fact + +- **On Windows the CLI is the process it is stopping.** The frozen + `meshbay-node.exe` is the daemon *and* every CLI verb, so the forced-stop + fallback `taskkill /IM meshbay-node.exe` killed `autostart stop` and + `restart-daemon` themselves: exit 1, no output, and — for a restart — no node + afterwards. The app's Restart delegates to that verb, so it reported "the + node did not start". Every unit test passed, because `subprocess.run` was + mocked; it takes running the installed exe to see. It now spares its own pid + and its parent's (a venv's `meshbay-node.exe` is a launcher whose `python.exe` + child does the work), and `/T` takes the daemon's children with it. + `test_the_forced_stop_really_spares_its_caller` runs it for real, under an + image name of its own — under the real one it would kill the developer's node + +- **A second instance must fail before it touches anything shared.** The + daemon wrote `ui-token` and logged "Control API on …" *before* uvicorn had + bound the port. So the sign-in launcher, run while a node was up, started one + that overwrote the running node's token, failed to bind inside uvicorn's task, + and exited with the reason on a hidden console — nothing in `node.log`. The + node that stayed up then refused every graceful stop, status and restart, + because the token file named a dead process; the next upgrade had to kill it. + Linux has the same order. `bind_control_port` now takes the port first + (exclusively on Windows, where `SO_REUSEADDR` would let a second socket + share a port in use) and a refusal is logged and exits 2. **Log a claim + after it is true**: "Control API on" had always been printed about a + server that did not exist yet + +- **A test that redirects `HOME` isolates nothing on Windows.** `Path.home()` + reads `USERPROFILE` there and the node's directories come from + `LOCALAPPDATA`, so the CLI tests wrote `invite-code`, `pair-code` and + `invite-link` — `TEST-CODE`, `example.invalid` — into the developer's real + node directory on every run, and into a `~/.local/share/meshbay` created for + the purpose. The node conftest now points all four variables at a throwaway + directory for every test + - **A user unit cannot carry `User=`.** `meshbay-node.spec` installed the system template into `%{_userunitdir}`, where systemd refuses the file outright — the packaged unit could never have started, and nothing noticed because nobody had |