fix(rust): an update on a running server reloads the plugin #30

Merged
whitlocktech merged 2 commits from fix/rust-plugin-reload into edge 2026-09-26 08:46:05 +00:00
Member

Found by the phase 18 walk (§34.3 step 5), in phase 18's own code.

The defect

The plugin was placed with write_atomic, which removes the old file and then renames a .tmp over it. Oxide and Carbon watch the plugins directory and reload on a change. Both saw the remove as a delete and unloaded the bridge. Both ignored the rename, so the new file was never loaded. Every update (or re-install) on a running server took the bridge down until the next boot, with no message. The move to v0.1.1 did exactly that on all three walk instances, on both frameworks:

Unloaded plugin Runic Gateway v0.1.0 by RunicGateway
                                 ← nothing after this until a restart

It also contradicted §34.2.3 ("both frameworks load a plugin the moment it lands, even on a running server").

The fix

The plugin is now overwritten in place (fs::write). That's what an operator's cp does, and both frameworks reload on it; every hand-copy on the walk rig did. write_atomic stays for install.json and the configs, where atomicity is the point. A crash mid-write leaves a file that fails to compile; doctor reports it as not the deployed file, and update writes it again.

Also: update on the same bundle no longer ends with "nothing moved" right after putting back a hand-edited plugin, which is the remedy doctor names. It now says it put back what no longer matched.

Proof

On the phase 18 rig, I hand-edited the plugin, then ran update --game rust on a running Oxide server and a running Carbon server. In both cases the plugin was replaced, compiled, loaded, and reconnected to its own sidecar, and doctor reported Everything checks out. cargo fmt, clippy -D warnings and 172 tests are green.

  • AI-assisted: Claude Code (Claude Opus 5.5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

Found by the phase 18 walk (§34.3 step 5), in phase 18's own code. ## The defect The plugin was placed with `write_atomic`, which removes the old file and then renames a `.tmp` over it. Oxide and Carbon watch the plugins directory and reload on a *change*. Both saw the remove as a delete and **unloaded the bridge**. Both ignored the rename, so **the new file was never loaded**. Every `update` (or re-install) on a running server took the bridge down until the next boot, with no message. The move to v0.1.1 did exactly that on all three walk instances, on both frameworks: ``` Unloaded plugin Runic Gateway v0.1.0 by RunicGateway ← nothing after this until a restart ``` It also contradicted §34.2.3 ("both frameworks load a plugin the moment it lands, even on a running server"). ## The fix The plugin is now **overwritten in place** (`fs::write`). That's what an operator's `cp` does, and both frameworks reload on it; every hand-copy on the walk rig did. `write_atomic` stays for `install.json` and the configs, where atomicity is the point. A crash mid-write leaves a file that fails to compile; `doctor` reports it as not the deployed file, and `update` writes it again. Also: `update` on the same bundle no longer ends with "nothing moved" right after putting back a hand-edited plugin, which is the remedy `doctor` names. It now says it put back what no longer matched. ## Proof On the phase 18 rig, I hand-edited the plugin, then ran `update --game rust` on a **running Oxide** server and a **running Carbon** server. In both cases the plugin was replaced, compiled, loaded, and reconnected to its own sidecar, and `doctor` reported *Everything checks out*. `cargo fmt`, `clippy -D warnings` and 172 tests are green. - [x] AI-assisted: Claude Code (Claude Opus 5.5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-09-26 08:42:57 +00:00
fix(rust): an update on a running server reloads the plugin
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 52s
397e0b92e9
The plugin was placed with write_atomic: remove the old file, rename a
.tmp over it. Oxide and Carbon watch the plugins directory and reload on
a change; both saw the remove as a delete and UNLOADED the bridge, and
ignored the rename, so the new file was never loaded. Every `update` (or
re-install) on a running server took the bridge down until the next
boot, silently — the phase 18 walk found it on all three instances and
both frameworks, after the move to v0.1.1.

The plugin is now overwritten in place, which is what an operator's `cp`
does and what both frameworks reload on. write_atomic stays for the
record and the configs, where atomicity is the point.

`update` on the same bundle also no longer says "nothing moved" after it
has just put back a hand-edited plugin — the remedy `doctor` names.

Walked: hand-edit, then `update`, on a running Oxide and a running
Carbon server: replaced, reloaded, reconnected, doctor clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-09-26 08:45:35 +00:00
fix(service): say why the service account stays, truthfully for Rust too
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 54s
e5611a01eb
`uninstall --game rust --server-id beta` printed "left the runicgateway
account alone — this installer did not create it" on a host where this
installer had created it, for alpha. A Rust instance's service never owns
the shared account (the last instance's removal decides), so the reason
now reads "it is not this service's to remove" — true for both games.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Author
Member

I added a second commit from the same walk step: uninstall --game rust --server-id beta printed "left the runicgateway account alone — this installer did not create it", but this installer had created it, during alpha's install. A Rust instance's service never owns the shared account; the last instance's removal decides that. So the action was right and the stated reason was wrong. It now reads "it is not this service's to remove", which is true for both games. Linux clippy and tests are green (166).

Apart from that line, step 5's uninstall passed: beta's service and plugin were removed, its config, token and database were kept, and alpha and gamma kept running with doctor clean.

I added a second commit from the same walk step: `uninstall --game rust --server-id beta` printed *"left the runicgateway account alone — this installer did not create it"*, but this installer **had** created it, during alpha's install. A Rust instance's service never owns the shared account; the last instance's removal decides that. So the action was right and the stated reason was wrong. It now reads *"it is not this service's to remove"*, which is true for both games. Linux clippy and tests are green (166). Apart from that line, step 5's uninstall passed: beta's service and plugin were removed, its config, token and database were kept, and alpha and gamma kept running with `doctor` clean.
whitlocktech merged commit 285f7df17b into edge 2026-09-26 08:46:05 +00:00
whitlocktech deleted branch fix/rust-plugin-reload 2026-09-26 08:46:06 +00:00
Sign in to join this conversation.
No description provided.