Repository navigation
[Bug]: Advanced background activity changes fail with "Setting not saved" #17233
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Open the desktop app and select a connected environment in Settings → General.
- Under Background activity, choose Advanced.
- Turn off Pause when locked.
Observed with the default balanced profile and permission to change settings. The steps were performed by the reporter; the server failure was also reproduced independently through the real RPC authorization middleware without changing live settings.
Expected behavior
The setting is saved, Pause when locked remains off, and the advanced settings dialog stays open until dismissed.
Actual behavior
The dialog closes and an error toast says Setting not saved / Could not update [environment name]. The setting is not saved. A transport connection loss and interrupted save request are logged immediately afterward, and the app reconnects.
This fails before the settings save handler runs. It is not a denied settings permission: the isolated reproduction grants AuthSettingsWriteScope.
Impact
Major degradation or frequent failure
Advanced background activity settings cannot be saved through this path. Repeated attempts were accompanied by interruption of the environment connection.
Version or commit
Desktop: 0.0.46-nightly.20261008.2819.
Independent server-boundary reproduction: checkout based on main @ 9a3070bcf023c3c025633659addeef4cf628133a. The introducing commit has not been identified.
Environment
Windows x64, T3 Code Nightly desktop, local environment. Exact Windows release was not collected. The isolated reproduction used Node.js 24.15.0 and the checkout's installed dependencies. Provider independent.
Logs or stack traces
The retained trace contains the same error at 11:55:50, 11:55:59, and 11:57:48 local time on 2026-10-08:
SchemaError: Expected number
at ["patch"]["backgroundActivity"]["overrides"]["automaticGitFetchInterval"]
Each is followed by EnvironmentSupervisor.connectionLost with transport failure and an interrupted RpcClient.server.updateSettings request. Raw traces are omitted because they contain authentication data.
Cause: RPC has already decoded the request before invoking its authorization middleware. Schema.DurationFromMillis has therefore converted millisecond numbers into Duration objects. However, requiredScopesForSettingsUpdate calls Schema.decodeUnknownSync(SettingsUpdate)(payload) again. That second decode expects wire-format numbers and throws on a duration object.
The checkbox calls backgroundActivityOverrideSettings, which includes all resolved timer values even when changing one boolean. The first rejected timer is automaticGitFetchInterval.
An isolated RpcTest.makeClient reproduction used the actual WsRpcGroup settings method, actual RpcAuthorization.layer([AuthSettingsWriteScope]), and actual advanced-settings helper. The save handler only marked whether it ran and returned DEFAULT_SERVER_SETTINGS; it performed no filesystem or live-state writes:
- Simple boolean settings patch: succeeds; handler runs.
- Background policy containing only
pauseWhenHostLocked: falseand profile/base profile: succeeds; handler runs. - Actual advanced-checkbox helper output: fails with the exact error above; handler never runs.
The exact RPC payload also encodes successfully using both the ordinary schema encoder and its JSON codec. This establishes a server authorization double-decode error rather than a client encoding error.
An independent Claude Opus 5.5 review confirmed that the framework decodes the payload before middleware runs and reproduced the same second-decode failure using the real contracts. Neither diagnostic reproduction was an end-to-end run of the shipped desktop client; the desktop symptoms and traces came from the reporter's running app.
The same helper is used by interval controls, so the code indicates a broader effect on settings patches containing durations, including the top-level Git fetch and provider refresh intervals. Those additional UI controls were not tested individually.
The dialog's open state depends on settings-write availability, and the settings route removes its children while the selected environment is disconnected. This is consistent with the observed closure after the connection loss. The exact path from the middleware defect to transport teardown was not independently reproduced.
Screenshots, recordings, or supporting files
The reporter supplied a screenshot of the toast; its text is transcribed above.
Workaround
No supported UI workaround was verified. Preset profiles do not provide the requested setting: all three presets pause when the host is locked. The isolated boolean-only request is diagnostic evidence, not a user-facing workaround.
The authorization middleware should inspect or validate the already-decoded settings type instead of reapplying the wire decoder, while preserving its settings/provider scope checks. A regression test should exercise a settings update containing a duration through the actual authorization middleware. No fix has been applied.
Duplicate search covered open and closed issues using the UI labels, exact error, duration field, and authorization function names. #15131 concerns the MCP preferences tool's missing ThreadCommandExecutor; it is a different path and cause.
Investigated by GPT-6.1-Sol through the Codex harness; independently reviewed by Claude Opus 5.5 through the Claude Agent provider.
Activity
juliusmarminge commentedon Oct 8, 2026
Note
Grok responding on behalf of Julius.
Thanks for the detailed report and the isolated reproduction. The double decode checks out on
main@a6ec88f7a.What's happening
requiredScopesForSettingsUpdaterunsSchema.decodeUnknownSync(SettingsUpdate)(payload)inside theRpcScopeAuthorizationmiddleware (apps/server/src/auth/RpcAuthorization.ts:227-240, called from:259and:266-271). By then, the RPC server has already decodedpayloadagainst the same struct, which is theserverUpdateSettingspayload schema (packages/contracts/src/rpc.ts:737-741).BackgroundActivityOverridesusesSchema.DurationFromMillisfor all five timers (packages/contracts/src/settings.ts:1128-1133). The top-levelautomaticGitFetchInterval/providerHealthRefreshIntervalinServerSettingsPatchuse it too (settings.ts:1747-1748). The second decode getsDurationvalues where it expects millisecond numbers and throws. I reproduced this with a minimal struct on effect 4.0.1 (the version in the workspace catalog): the first decode succeeds, the second throwsSchemaError: Expected number at ["patch"]["backgroundActivity"]["overrides"]["automaticGitFetchInterval"], and a boolean-only override patch decodes twice without error.backgroundActivityOverrideSettingsalways copies every resolved timer intooverrides. That's why flipping any Advanced toggle, including Pause when locked, sends Durations and hits this.Why the connection drops (likely)
The throw is synchronous inside the middleware function, not an
Effect.fail. In effect 4.0.1'sRpcServer, middleware is applied insidehandleRequest, so the throw appears to reach theEffect.catchDefectaroundwriteand is sent as a connection-levelDefectinstead of a per-request exit. On the client,RpcClienthandlesDefectwithclearEntries(Exit.die(...)), which fails every pending request and open stream on that socket. That fits theconnectionLost→ reconnect you saw. The dialog closing is probably a knock-on effect of the environment briefly disconnecting, but I didn't trace that UI path end to end.Scope
SettingsUpdatere-decode. Before that,serverUpdateSettingshad a fixed scope (AuthOrchestrationOperateScope) and its payload wasn't decoded again. The existing tests inRpcAuthorization.test.tsonly send patches without Durations, so they didn't catch it.assetsCreateUrlandgitPreparePullRequestThreaduse the same "decode the payload again in the middleware" pattern. Their schemas look idempotent at a glance, so I don't think they're affected today, but I haven't verified that.Possible fix (untested)
requiredScopesForServerSettingsPatchonly looks at which keys are defined (settings.ts:1830-1846), and the payload is already the decoded type. So the middleware could treatpayloadastypeof SettingsUpdate.Type, or check it with a type-side guard, instead of running the wire decoder again.RpcTest.makeClientcall throughRpcAuthorization.layer([AuthSettingsWriteScope])with abackgroundActivity.overridespatch that contains a Duration.Related
I didn't find a duplicate or an open fix PR. #15131 (MCP preferences tool missing
ThreadCommandExecutor) is a different path. #13235 and #12000 are about how the Git fetch and provider health intervals behave, not about saving them.Workaround until a fix lands: choosing a preset profile should still save, since it sends
overrides: {}. As you noted, though, none of them turns off Pause when locked.fix(server): background activity settings save without dropping the c…