Repository navigation
[Bug]: Advanced background activity changes fail with "Setting not saved" #17233
Description
Activity
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 anEffect.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
- This most likely started with feat(auth): separate environment administration permissions #9786 (merged 2026-10-06), which added the
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. - It probably also affects the Git fetch and provider health check interval inputs and their reset buttons, which go through the same helper. Restore defaults in General likely hits it too, since it sends the top-level Duration fields. I haven't tested those individually.
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.- Separately, it may be worth having the middleware turn any unexpected throw into a typed failure, so a bug in scope calculation fails one request instead of the whole connection.
- A regression test would help: an
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 missingThreadCommandExecutor) 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.- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 8, 2026 - added a commit that references this issue
on Oct 11, 2026
Before submitting
Area
apps/server
Steps to reproduce
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:
Each is followed by
EnvironmentSupervisor.connectionLostwith transport failure and an interruptedRpcClient.server.updateSettingsrequest. Raw traces are omitted because they contain authentication data.Cause: RPC has already decoded the request before invoking its authorization middleware.
Schema.DurationFromMillishas therefore converted millisecond numbers intoDurationobjects. However,requiredScopesForSettingsUpdatecallsSchema.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 isautomaticGitFetchInterval.An isolated
RpcTest.makeClientreproduction used the actualWsRpcGroupsettings method, actualRpcAuthorization.layer([AuthSettingsWriteScope]), and actual advanced-settings helper. The save handler only marked whether it ran and returnedDEFAULT_SERVER_SETTINGS; it performed no filesystem or live-state writes:pauseWhenHostLocked: falseand profile/base profile: succeeds; handler 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.