Area
apps/server
Steps to reproduce
-
Stop the server. In settings.json under the T3 userdata directory, add a provider instance with a sensitive variable whose value sits inline, and make sure no matching provider-env-* file exists under secrets/:
{
"providerInstances": {
"claudex": {
"driver": "claudeAgent",
"enabled": true,
"environment": [
{ "name": "ANTHROPIC_BASE_URL", "value": "http://localhost:8317", "sensitive": false },
{ "name": "ANTHROPIC_AUTH_TOKEN", "value": "sk-inline-token", "sensitive": true }
],
"config": {}
}
}
}
-
Start the server. The provider works, because load reads the inline value.
-
From any client, save that provider instance without touching the token. Toggling the enabled switch is enough. The cross-device settings sync triggers the same save.
-
Read settings.json again, then start a new thread on that provider.
Expected behavior
The token keeps its value. Either it stays in the file or the save moves it into the secret store. A save that never touched a field should not change what that field resolves to.
Actual behavior
The entry becomes "value": "", "valueRedacted": true and no provider-env-* secret file exists. Every provider session started after that gets an empty token. Sessions already running keep their old environment, so the failures look random.
The cause is in apps/server/src/serverSettings.ts. Clients only ever receive the redacted form of a sensitive variable. On save, the redacted branch of persistProviderEnvironmentSecrets writes that placeholder straight back. It never checks that the secret store holds a value, and it drops the inline value the server still has in memory for that variable. One save and the token is gone from disk.
This is a papercut more than a bug. The documented path is to enter secrets through Settings, and that path works. But writing settings.json directly is a normal thing for a coding agent to do when it sets up a provider, which is how my config got this way. I never edited the file by hand. The app accepted the config, ran with it for weeks, then destroyed it on an unrelated save with no warning.
Impact
Minor bug or occasional failure. It only affects configs written outside the UI, but when it hits, the failure is silent and shows up later as a provider auth error on whichever thread starts next, with nothing pointing back at the save.
Version or commit
0.0.39-nightly.20260903.1270. Reproduced with a server test against main at c7dc3cb.
Environment
Linux (WSL2), Node 24.11.1, Claude Code provider behind a local CLIProxyAPI.
Logs or stack traces
# proxy response for every session started after the save
401 {"error":"Invalid API key"}
# server-level repro (temporary test, not committed)
AssertionError: expected '' to equal 'sk-inline-token'
Related issues
None match. #9230, #317, and #1267 are about variables not reaching the provider at all, not about a save erasing a stored value. Closed PRs #3524 and #3061 touched the settings editor's draft rows in the browser and never changed what the server writes.
Workaround
Re-enter the value in Settings > Providers for that instance. That path writes it to the secret store and the placeholder in settings.json becomes valid.
Area
apps/server
Steps to reproduce
Stop the server. In
settings.jsonunder the T3 userdata directory, add a provider instance with a sensitive variable whose value sits inline, and make sure no matchingprovider-env-*file exists undersecrets/:{ "providerInstances": { "claudex": { "driver": "claudeAgent", "enabled": true, "environment": [ { "name": "ANTHROPIC_BASE_URL", "value": "http://localhost:8317", "sensitive": false }, { "name": "ANTHROPIC_AUTH_TOKEN", "value": "sk-inline-token", "sensitive": true } ], "config": {} } } }Start the server. The provider works, because load reads the inline value.
From any client, save that provider instance without touching the token. Toggling the enabled switch is enough. The cross-device settings sync triggers the same save.
Read
settings.jsonagain, then start a new thread on that provider.Expected behavior
The token keeps its value. Either it stays in the file or the save moves it into the secret store. A save that never touched a field should not change what that field resolves to.
Actual behavior
The entry becomes
"value": "", "valueRedacted": trueand noprovider-env-*secret file exists. Every provider session started after that gets an empty token. Sessions already running keep their old environment, so the failures look random.The cause is in
apps/server/src/serverSettings.ts. Clients only ever receive the redacted form of a sensitive variable. On save, the redacted branch ofpersistProviderEnvironmentSecretswrites that placeholder straight back. It never checks that the secret store holds a value, and it drops the inline value the server still has in memory for that variable. One save and the token is gone from disk.This is a papercut more than a bug. The documented path is to enter secrets through Settings, and that path works. But writing
settings.jsondirectly is a normal thing for a coding agent to do when it sets up a provider, which is how my config got this way. I never edited the file by hand. The app accepted the config, ran with it for weeks, then destroyed it on an unrelated save with no warning.Impact
Minor bug or occasional failure. It only affects configs written outside the UI, but when it hits, the failure is silent and shows up later as a provider auth error on whichever thread starts next, with nothing pointing back at the save.
Version or commit
0.0.39-nightly.20260903.1270. Reproduced with a server test against main at c7dc3cb.
Environment
Linux (WSL2), Node 24.11.1, Claude Code provider behind a local CLIProxyAPI.
Logs or stack traces
Related issues
None match. #9230, #317, and #1267 are about variables not reaching the provider at all, not about a save erasing a stored value. Closed PRs #3524 and #3061 touched the settings editor's draft rows in the browser and never changed what the server writes.
Workaround
Re-enter the value in Settings > Providers for that instance. That path writes it to the secret store and the placeholder in
settings.jsonbecomes valid.