Repository navigation
Add a Profiles tab to desktop Settings - #1124
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A switch that fails after activation is resumed by the next start; sign-in commits check the profile is still absent or still on the same server at commit time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The CLI writes active_profile after the old owner is confirmed gone and before the new unit exists, so any later failure leaves no daemon and the next start's ordinary install converges; the app keeps no journal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Activation rechecks the target on the locked config and re-probes both labels for a live owner; the lane refuses every request after a switch at admission; token writes go through one locked seam that spares another profile's legacy credential. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#1093) A runtime start can race the activation interval past every service lock, so the invariant is narrowed to daemons running at the write and the attach path compares the daemon's identity to the resolution. Logout, legacy migration and the save guard join the token lock. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Migration retires a redundant legacy file instead of skipping it, runs before the transaction's forward budget, and the daemon reports its profile so attach can tell two profiles sharing a server and name apart. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…1093) TryLoadPure returns a fresh default alongside its false, so a guard or owner read that fails must refuse rather than decide on the default; the Activate timeout adds the token lock's wait to the rename's, and the manual repair paths name the daemon and unset the override. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MutateAsync publishes the callback's result over a malformed config it read as defaults, so a commit-time precondition or activation recheck evaluated there passes for the wrong reason and then wipes the file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
A save for another profile no longer deletes the active profile's legacy tokens.json. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pre-authentication read decides LoginTarget; a profile removed or repointed while the browser was open is refused inside the strict mutation, and the token save is guarded under the profile lock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…1093) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The precondition is an optional null-defaulted parameter at every hop from ReauthComposition.Build to LoginAsync, and LoginTarget.SaveGuard is built from three more, so a dropped one refuses nothing that any suite observes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`kcap use` migrates the legacy credential before it refuses, so "nothing was changed" was false once that file had moved; a profile name the token layout rejects threw out of the handler that tried to name its file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign-in dereferenced the row's server url behind no check, and the settings window's profile view model was built outside the try whose catch reports a construction failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Summary by QodoAdd race-safe profile management to desktop Settings
AI Description
Diagram
High-Level Assessment
Files changed (48)
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5e1601332
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _time, | ||
| WizardComposition.NewOperation); | ||
| WizardComposition.NewOperation, | ||
| new CommitPrecondition.ExpectServer(serverUrl)); |
There was a problem hiding this comment.
Limit the server precondition to profile-row sign-ins
When the desktop app is launched with KCAP_URL, ProfileResolver intentionally resolves a server without a profile, so the Home sign-in path passes the fallback active profile name together with the override URL. This unconditional ExpectServer then rejects the commit unless that on-disk profile already names the override URL, meaning users complete authentication but cannot save credentials for the server the app is actually using. Pass this precondition only for Settings row sign-ins, or make it nullable for the existing Home re-auth flow.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deliberate, so keeping it. Under KCAP_URL the resolution has no profile, and before this change the rail's sign-in adopted the override into the on-disk profile — server_url of work silently rewritten to whatever the shell exported, and the credential stamped for it. The precondition refuses that with profile 'work' does not name <url>; nothing saved. Signing in against an override is done with a profile that names that server (kcap setup <url>, or KCAP_PROFILE instead of KCAP_URL); the design leaves the URL-launch case out of the Profiles work.
The Settings tab order is Daemon, Profiles, Notifications, so main's new notification-access smoke test selects the Notifications tab at index 2. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Logout deletes the active profile's file and the legacy file under one hold of its lock, and a name the layout rejects still gets the legacy file deleted. A case-alias shares a token file only where the directory lists one spelling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… locks (#1093) An action judges its row against its own fresh read, since a superseded read never publishes and the shared rows may be a newer read's still in progress. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Part of #1093 — AI-3072 (the first of three PRs; the last closes the issue)
What & why
The desktop app's Settings window gains a Profiles tab that lists every profile in
config.jsonwith its credential status and offers Sign in and Remove per row. Removal is one operation in Core shared withkcap profile remove: it decides on the locked config, refuses the active profile instead of resetting the selection to an emptydefault, and deletes the credential. Every write to a token file takes the profile's cross-process lock with a guard evaluated under it; a refresh re-reads under the lock rather than reviving a file deleted while it waited; the legacytokens.jsonmoves into its owner's slot before any writer ofactive_profilechanges the selection. A sign-in commit checks the profile still names its server inside a strict config mutation, andCommittedreports whether the credential landed.Where to look
TokenStore: one lock-held write primitive and the guards under it — the config lock is never held while a token lock is taken.CommitBoundary: the precondition runs insideMutateStrictAsync, before the stamp.Verification
dotnet build Capacitor.slnx: 0 warnings;dotnet publish -c ReleasegrepIL[23][01][0-9]{2}: no output🤖 Generated with Claude Code