The workflow
Reproduces on a stock install, no third-party plugins. Go to Settings → Provider retry (/settings/plugins/provider-retry), which declares one select through bb.settings.define (server.ts#L40-L49). Change Maximum automatic wait from "6 hours" to "24 hours", leave the page, and hit a subscription limit.
The retry is still capped at six hours. The dropdown moved, nothing saved it, and nothing said so — the setting only applies if you noticed Save settings at the bottom of the form. Because the effect lands in scheduling behavior rather than on screen, a missed save is discovered a turn or a day later, if at all.
Every other toggle in bb applies on the spot, including bb's own (<Switch onCheckedChange={setDimsInactiveSplits} />, SplitDimmingSetting.tsx#L18-L20) and both first-party plugins that own their settings UI. Nothing tells a user which kind of toggle they're looking at.
What happens today
PluginSettingsForm holds edits in a local drafts map and PUTs only on submit — desktop and mobile. Two consequences beyond the extra click:
- Unsaved switches are discarded on navigation, with no prompt — deliberate, and pinned by PluginSettings.test.tsx#L165.
- A plugin wanting normal behavior must leave the declarative API. Custom instructions and Keep Awake both did, and each then hand-wrote a CLI (
bb instructions, bb keep-awake) to replace the bb plugin config they gave up.
What you would expect
Settings commit when the control commits, with an inline "Saving… / Saved" status where the button is:
boolean / select / project — immediately, optimistic. The control is the commit.
string (non-secret) — debounced ~500 ms.
string + secret: true — keep an explicit commit. Write-only, "empty means unchanged", lands in a 0600 file; autosaving keystrokes persists half-typed keys.
Both patterns already exist here and are worth copying rather than reinventing: debounce + queued writes + status in custom-instructions/app.tsx#L46-L69, optimistic + revision-guarded rollback in keep-awake/app.tsx#L62-L92.
Propagation already works, unchanged. updateSettings broadcasts plugins-changed on every effective change, and the comment there already names useSettings as the consumer (plugin-service.ts#L1998-L2001); the registry invalidates the ["plugin-settings"] prefix (realtime-cache-registry.ts#L1276). A flipped switch would take effect at the moment it's flipped, with no new plumbing.
Three things needing a decision, not just a patch:
- A form mixing secrets and switches ends up with a button governing one field. Needs a UI answer.
needs-configuration plugins are reloaded on every effective change (plugin-service.ts#L2015) — with a debounced field that's a reload every 500 ms while someone types an API key.
- Two tests encode the draft model (L46, L165) and mobile's Discard loses its referent. I read those as arguments for the change, but they're deliberate and a PR should say so rather than quietly delete them.
Context and alternatives
No plugin-side workaround. Descriptors have no hidden flag and the form renders whenever plugin.hasSettings, so a settingsSection slot renders below a form still showing every control. The only escape is abandoning bb.settings.define — a bad trade for a plugin whose settings are three switches. Affects every plugin with declarative settings, desktop and mobile. Among bundled plugins that is Provider retry, Workflows, and GitHub in 0.39.0, plus the Claude Code and Codex providers on main.
Searched; not duplicates: #2191 (open, placeholder on string descriptors), #1238 (closed, multiline), #1766 (closed, settings destroyed on source replacement), #1620 (open, Usage limits surface).
One question first: #1238 was authored and closed by @smsunarto with no comment or review, and its opening line makes this same observation from the other direction — a plugin needing more than the form offers "had to abandon the descriptor API entirely and ship its own settingsSection surface". Is this form settled enough to change, or is something already planned? Happy to prototype on a fork; per CONTRIBUTING.md I won't open a PR without sign-off.
Verified against main at 8bd6acc; desktop app, bb 0.39.0, macOS 15. Every line cited was read at that commit, and bb plugin config provider-retry confirms the schema is live on 0.39.0. The rendered form itself I traced through the code rather than screenshotting, on desktop and mobile both — flag it as unverified UI if that matters for triage. Investigation thread: thr_67q64zmixq.
AGENT GENERATED: by Claude
The workflow
Reproduces on a stock install, no third-party plugins. Go to Settings → Provider retry (
/settings/plugins/provider-retry), which declares one select throughbb.settings.define(server.ts#L40-L49). Change Maximum automatic wait from "6 hours" to "24 hours", leave the page, and hit a subscription limit.The retry is still capped at six hours. The dropdown moved, nothing saved it, and nothing said so — the setting only applies if you noticed Save settings at the bottom of the form. Because the effect lands in scheduling behavior rather than on screen, a missed save is discovered a turn or a day later, if at all.
Every other toggle in bb applies on the spot, including bb's own (
<Switch onCheckedChange={setDimsInactiveSplits} />, SplitDimmingSetting.tsx#L18-L20) and both first-party plugins that own their settings UI. Nothing tells a user which kind of toggle they're looking at.What happens today
PluginSettingsFormholds edits in a localdraftsmap and PUTs only on submit — desktop and mobile. Two consequences beyond the extra click:bb instructions,bb keep-awake) to replace thebb plugin configthey gave up.What you would expect
Settings commit when the control commits, with an inline "Saving… / Saved" status where the button is:
boolean/select/project— immediately, optimistic. The control is the commit.string(non-secret) — debounced ~500 ms.string+secret: true— keep an explicit commit. Write-only, "empty means unchanged", lands in a 0600 file; autosaving keystrokes persists half-typed keys.Both patterns already exist here and are worth copying rather than reinventing: debounce + queued writes + status in custom-instructions/app.tsx#L46-L69, optimistic + revision-guarded rollback in keep-awake/app.tsx#L62-L92.
Propagation already works, unchanged.
updateSettingsbroadcastsplugins-changedon every effective change, and the comment there already namesuseSettingsas the consumer (plugin-service.ts#L1998-L2001); the registry invalidates the["plugin-settings"]prefix (realtime-cache-registry.ts#L1276). A flipped switch would take effect at the moment it's flipped, with no new plumbing.Three things needing a decision, not just a patch:
needs-configurationplugins are reloaded on every effective change (plugin-service.ts#L2015) — with a debounced field that's a reload every 500 ms while someone types an API key.Context and alternatives
No plugin-side workaround. Descriptors have no
hiddenflag and the form renders wheneverplugin.hasSettings, so asettingsSectionslot renders below a form still showing every control. The only escape is abandoningbb.settings.define— a bad trade for a plugin whose settings are three switches. Affects every plugin with declarative settings, desktop and mobile. Among bundled plugins that is Provider retry, Workflows, and GitHub in 0.39.0, plus the Claude Code and Codex providers onmain.Searched; not duplicates: #2191 (open,
placeholderon string descriptors), #1238 (closed,multiline), #1766 (closed, settings destroyed on source replacement), #1620 (open, Usage limits surface).One question first: #1238 was authored and closed by @smsunarto with no comment or review, and its opening line makes this same observation from the other direction — a plugin needing more than the form offers "had to abandon the descriptor API entirely and ship its own
settingsSectionsurface". Is this form settled enough to change, or is something already planned? Happy to prototype on a fork; per CONTRIBUTING.md I won't open a PR without sign-off.Verified against
mainat8bd6acc; desktop app, bb 0.39.0, macOS 15. Every line cited was read at that commit, andbb plugin config provider-retryconfirms the schema is live on 0.39.0. The rendered form itself I traced through the code rather than screenshotting, on desktop and mobile both — flag it as unverified UI if that matters for triage. Investigation thread:thr_67q64zmixq.