Fix: atomic plugins.toml writes β registry race made plugin subcommands vanish#419
Conversation
β¦ds vanish save_registry used fs::write, which truncates in place. Any fledge invocation reading the registry during a re-registration parsed partial TOML, and dispatch silently resolved every registered plugin command to 'unrecognized subcommand'. Observed in the wild: merlin's CI trust-gate failing 'fledge run augur-gate' with 'unrecognized subcommand augur' whenever a plugin registration (merlin init/update, fledge plugins install) overlapped the gate β three times in two days, each costing a misleading red build. - save_registry: write-then-rename (same-dir rename is atomic on POSIX); readers now see either the old or the new registry, never a truncated one - load_registry: one 50ms retry rides out rewrites by older (pre-atomic) writers, and persistent corruption prints a warning naming the path instead of silently resolving as an empty registry - Tests: a writer-hammer concurrency regression (400 reads against a continuous rewriter) and a corruption-surfaces-error case Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces atomic registry saving using a write-then-rename pattern to prevent concurrent readers from observing a truncated plugins.toml file. It also adds a retry mechanism when loading the registry to handle potential concurrent writes from older versions, along with corresponding unit tests. The review feedback suggests cleaning up the temporary file if the write or rename operation fails to prevent leaking temporary files.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
β Corvin says...
_
<(;\ .oO(oh no...)
|/(\
\(\\
" "\\
"Even the dumpster of code seems empty today."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β failure |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β Passed |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
There was a problem hiding this comment.
β Corvin says...
_
<(^\ .oO(Caw! ^v^)
|/(\
\(\\
" "\\
"Looking sharp! Like a beak should be."
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β Passed |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β Passed |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
β Corvin says...
_
<(^\ .oO(Caw! ^v^)
|/(\
\(\\
" "\\
"Caw! Found a shiny new spec!"
CI Summary
| Check | Status |
|---|---|
| Dependency Audit | β Passed |
| Integration (3 OS) | β Passed |
| Lint (fmt + clippy) | β Passed |
| Spec Validation | β Passed |
| Tests (3 OS) | β Passed |
Powered by corvid-pet
Summary
save_registrywroteplugins.tomlwithfs::write(truncate-in-place). A concurrent fledge invocation reading the registry mid-rewrite parsed partial TOML, and because dispatch loads the registry with.ok()?, every registered plugin command silently resolved tounrecognized subcommand.fledge run augur-gatefailed withunrecognized subcommand 'augur'three times in two days β every time a plugin registration (merlin init/update,fledge plugins install) overlapped the gate. The plugin was perfectly installed each time; the red builds were pure race.save_registry(same-directory rename is atomic on POSIX β readers see the old registry or the new one, never a torn one), plus a one-shot 50ms retry inload_registryfor rewrites by older non-atomic writers, plus a stderr warning when the registry is persistently unreadable so the failure is diagnosable instead of masquerading as a missing plugin.Test plan
fs::writeimplementation)fledge lanes run ciβ all 6 steps greenπ€ Generated with Claude Code