You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A settings write can finish persisting after its owning plugin fiber has been disposed and a replacement plugin has registered the same namespace. The stored document then contains the new section, but the replacement registration keeps the value it resolved before persistence completed. Its watchers are not notified, so the running plugin and the persisted settings can disagree indefinitely.
Reproduction
Register a namespace whose provider delays persist().
Start an update on that registration.
Dispose the owner while persistence is in flight.
Register a replacement owner for the same namespace before persistence finishes.
Await the original update.
Before the fix, describe() reports the newly stored user section while the replacement scope's get() still returns the old value and its watcher receives no commit.
Root cause
The post-persist path always refreshed the raw document cache, but committed only when the namespace was still owned by the registration that initiated the write. That correctly prevented delivery to a disposed owner, but it treated a live replacement exactly like no owner and never re-resolved the replacement from the section that had just reached storage.
Impact
This creates split-brain settings state during plugin restart/reload: configuration surfaces see the persisted value, while the replacement plugin continues operating with stale configuration until another provider publication happens. There is no guarantee that another publication will occur.
Proposed fix
After persistence succeeds, look up the namespace's current owner:
never commit to the disposed initiating registration;
if a replacement owns the namespace, advance its raw revision and resolve the stored section against the replacement's schema, base, and owner validation;
commit and notify the replacement when valid;
if the replacement rejects the section, keep its last-good resolved value, warn, and still advance the raw revision;
do not reject the old caller after successful persistence, because that would falsely report that the write did not happen.
The package README and the settings write-path Agent Note are updated in both languages, with translation records refreshed.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
A settings write can finish persisting after its owning plugin fiber has been disposed and a replacement plugin has registered the same namespace. The stored document then contains the new section, but the replacement registration keeps the value it resolved before persistence completed. Its watchers are not notified, so the running plugin and the persisted settings can disagree indefinitely.
Reproduction
persist().Before the fix,
describe()reports the newly stored user section while the replacement scope'sget()still returns the old value and its watcher receives no commit.Root cause
The post-persist path always refreshed the raw document cache, but committed only when the namespace was still owned by the registration that initiated the write. That correctly prevented delivery to a disposed owner, but it treated a live replacement exactly like no owner and never re-resolved the replacement from the section that had just reached storage.
Impact
This creates split-brain settings state during plugin restart/reload: configuration surfaces see the persisted value, while the replacement plugin continues operating with stale configuration until another provider publication happens. There is no guarantee that another publication will occur.
Proposed fix
After persistence succeeds, look up the namespace's current owner:
The package README and the settings write-path Agent Note are updated in both languages, with translation records refreshed.
Patch
Verification
light, replacementget()remainsdark.pnpm run lintpassed.pnpm run doc-sync: 28 passed, 0 failed, 0 skipped.All reactions