Summary
On Windows, our desktop clients (Claude Code and Codex Desktop) are packaged in a way that redirects
%APPDATA%/%LOCALAPPDATA% to a virtualized, per-app location. Running bt login from a normal
shell writes credentials to the real store; the daemon spawned under the packaged app's process tree
reads from the virtualized store instead. This produces two distinct failure modes:
Form 1 (loud): the two stores diverge (different profile_id), and the daemon logs a rejection
for every event — visible, at least, in the log.
Form 2 (silent, worse): after a re-login, the profile id is preserved but only the real store's
tokens are updated. The virtualized store keeps the old profile id and stale tokens, the daemon
resolves it as a valid profile, and it runs on superseded credentials with no error logged at all
until the stale token eventually expires server-side.
We also independently caught form 2 live, outside of any controlled test: a background monitor
comparing both stores' secrets.json mtimes and hashes found the virtualized store a full day stale
relative to the real one, on an otherwise normal working day.
Suggested fix
Resolve credentials from a path outside the scope of app-sandboxing/virtualization, or at minimum
record the profile name (not just its id) in both stores so a mismatch can be detected without
diffing file contents by hand.
Related
Same underlying app-packaging condition also produces braintrustdata/braintrust-claude-plugin#42
(console flash) and the daemon-spawn behavior in braintrustdata/braintrust-claude-plugin#41.
Summary
On Windows, our desktop clients (Claude Code and Codex Desktop) are packaged in a way that redirects
%APPDATA%/%LOCALAPPDATA%to a virtualized, per-app location. Runningbt loginfrom a normalshell writes credentials to the real store; the daemon spawned under the packaged app's process tree
reads from the virtualized store instead. This produces two distinct failure modes:
Form 1 (loud): the two stores diverge (different
profile_id), and the daemon logs a rejectionfor every event — visible, at least, in the log.
Form 2 (silent, worse): after a re-login, the profile id is preserved but only the real store's
tokens are updated. The virtualized store keeps the old profile id and stale tokens, the daemon
resolves it as a valid profile, and it runs on superseded credentials with no error logged at all
until the stale token eventually expires server-side.
We also independently caught form 2 live, outside of any controlled test: a background monitor
comparing both stores'
secrets.jsonmtimes and hashes found the virtualized store a full day stalerelative to the real one, on an otherwise normal working day.
Suggested fix
Resolve credentials from a path outside the scope of app-sandboxing/virtualization, or at minimum
record the profile name (not just its id) in both stores so a mismatch can be detected without
diffing file contents by hand.
Related
Same underlying app-packaging condition also produces braintrustdata/braintrust-claude-plugin#42
(console flash) and the daemon-spawn behavior in braintrustdata/braintrust-claude-plugin#41.