Skip to content

Retry user plugins that read settings during activate - #1343

Draft
mfat wants to merge 1 commit into
devfrom
cursor/defer-plugin-settings-activate-8ea4
Draft

mfat wants to merge 1 commit into
devfrom
cursor/defer-plugin-settings-activate-8ea4

Conversation

@mfat

@mfat mfat commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Summary

Enabled user plugins that call ctx.settings.get() inside activate() were dropped at startup. health, inventory-import, and tailscale all do that, and plugin loading runs before the daemon client exists, so each read raised BackendUnavailable and the loader logged the traceback from issue #1342 and skipped the plugin. The app itself kept going.

Settings still raise while the client is missing — a silent default is indistinguishable from "unset", and a later write of that default would erase stored values. The GTK loader now catches that error and runs activate() again once, immediately before app_started, when the window is bound and the client can answer. The persisted value is what the plugin reads. Any pages or event subscriptions from the failed attempt are removed first, so app_started is delivered once. If the client never connects, the plugin stays unloaded. Any other exception during activate() is still fatal. The daemon process does not retry, because it has no client to attach later.

Fixes #1342.

API and architecture

  • Not applicable: this change does not affect the frontend-neutral API or
    core/frontend ownership boundary
  • Public DTOs or client methods were updated
  • Capabilities were updated and only implemented support is advertised
  • Events and structured errors were updated
  • State, ordering, threading, cancellation, and security semantics were reviewed
  • Contract tests were updated
  • API documentation was updated
  • docs/api/CHANGELOG.md was updated
  • Compatibility and protocol-version impact were reviewed
  • Generated API artifacts and the public-surface snapshot were deliberately refreshed

Plugin settings remain daemon-owned. The only change is when the GTK loader finishes activating a user plugin that touched them too early. docs/PLUGIN_SDK.md and docs/plugins/writing-plugins.md describe the retry.

Testing

Reproduced the drop before the change: a user plugin that calls ctx.settings.get() in activate() was absent from the loaded list after the window was presented and the backend settled, with the same BackendUnavailable traceback as the issue (plugins/api.py get / _client).

After the change:

python3 -m pytest tests/test_plugin_loader.py tests/test_plugin_host.py tests/test_plugin_context_connections.py tests/test_plugin_context_power_apis.py tests/daemon/test_plugin_settings.py -o addopts= -p no:xdist

95 passed, 1 skipped. The new loader cases cover a successful retry (stored value, not the caller's default; app_started once), client selection resolving before the window is bound, a Plugins page rebuilt after the late load, a backend that never connects, a non-backend activate() error, and the daemon path still refusing to wait.

Open in Web Open in Cursor 

health, inventory-import, and tailscale call ctx.settings.get() inside
activate(), which runs before the daemon client exists. That raised
BackendUnavailable and the loader dropped the plugin (issue #1342).
Retry the activation once the client is connected, just before
app_started, so the read returns the stored value instead of abandoning
the plugin. Other activate() errors are still fatal, and the daemon
process does not retry.

Co-authored-by: newmfat <newmfat@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants