* provider: a sign-in reads as done only once the account it brings back is listed again, so the window opens it rather than saying magpie can't list it (TestClaudeTeamSeatBesidePersonal failed so on macOS in #1046's CI) Signing in brings back an account removed from magpie (ShowAccount clears its Hidden mark). Since440e535b, (*signInFlow).finish recorded the outcome and let go of the sign-in's lock before it called ShowAccount, so a poll of SignInStatus could read done while the account was still hidden. The window polls every 800ms and, seeing done, looks for the account to open; missing, it says "{user} signed in, but magpie can't list it — please report this". #1046's macOS job (run 37502933081) hit the same gap: signin_team_test.go:52: account after sign-in: false [] WaitSignIn, which the command line uses, waits for the end of finish and was not affected. finish now marks the sign-in finished first. Only the first finish records an outcome, as before: a cancel or the timeout that comes after it is refused, and so is a callback (claim). A done sign-in then calls ShowAccount, and only after that reads as done. ShowAccount's error is ignored, as before: if providers.json can't be written, the account stays hidden and the sign-in still ends done. Until then the sign-in reads as waiting; that takes longer only when the account was hidden, since only then does ShowAccount write providers.json (and sync the agents' catalogs, as it did inside finish before). Every built-in and plugin sign-in's done goes through finish, so this one change covers them all. The other places that show an account again do it before their result can be seen: PluginAPIKey returns after ShowAccount, and ImportLogins and ImportGoogleAccounts call it before they return. The command line's own plugin sign-in never showed the account again; that is a change of its own. ImportFactoryKeys shows none either, but the window offers a Factory key's import only for a Factory it lists, and a removed one is left out of the Add sheet. providers-accounts.md's sign-in row now says when a sign-in reads as done. Verified: TestSignInDoneOnceTheAccountIsBack records what the sign-in reads as at the moment providers.json shows the account again (catalog.Changed), for magpie's own callback (Codex), Claude Code's login run by magpie (Claude) and a plugin's (FakeCo, with Bun). With finish from main all three fail with "the sign-in read as done before its account was shown again: the window, seeing it done, can't list it"; with this they pass. TestSignInCanceledWhileTheAccountComesBack cancels the sign-in at that moment and checks it still ends done, the account listed; with this finish guarded on the state alone, as main's was, it fails with a "close of closed channel" panic in the callback. With a 300ms sleep where main's finish lets go of its lock, TestClaudeTeamSeatBesidePersonal fails 10 of 10 runs, each in a process of its own, with #1046's message; main as it is passes 10 of 10 here, the gap being that narrow. With the same sleep after this finish publishes done, or before its ShowAccount, it passes 10 of 10. Under a temporary HOME with Bun 1.4.2, -race -count=20 on those three tests and -race -count=2 on the package's sign-in, plugin and login tests pass, as do go vet and the darwin, GOOS=linux (nogui) and GOOS=windows builds; this commit alone builds and passes internal/provider. With the next commit, go test -tags nogui ./... passes on main at443f1cbcand at0bf9d77d. Three earlier full runs failed, each only in internal/plugin, which is unchanged and imports nothing changed here: twice TestHostJSCancellation/ready-wait at its 10-second Node deadline (signal: killed) while other sessions' tests loaded the machine, the timeout76dc5aedand #1036 also record, and once, with Bun 1.3.13, TestPluginHTTP2TakesTheProxy. The GUI suite was not run: nothing in internal/gui/assets changes, and its tests answer /api/signin from fixtures. Not tried with a real vendor's sign-in. * plugins: magpie plugin login and magpie accounts add finish a plugin's sign-in as the window does, bringing back a provider removed from magpie and taking the account's lapsed mark off The window's sign-in to a plugin's provider, once the plugin has saved the account, takes the account's lapsed mark off and shows again the agent's own account removed in magpie (clearPluginLapse), and brings the provider back if it was removed from magpie (ShowAccount): pluginDone through finish, and PluginAPIKey for a key. The command line's (pluginLogin, which magpie plugin login runs, as magpie accounts add does for a plugin's provider or a subscription moved onto one) said "✓ signed in to …" and did none of it: - a provider removed from magpie stayed out of magpie providers, the agents' catalogs and routing until it was added back (in the window, or with magpie provider add); - an account signed in again after its vendor turned the sign-in away still said "sign in again" until a request or a usage read went through it; - an agent's own account (Kiro's, Devin's, …) removed in magpie stayed hidden after it was signed in to again, though the window's sign-in shows it again (#320). PluginSignedIn now does all three for a sign-in magpie doesn't follow itself, and PluginAPIKey and pluginLogin call it. providers-accounts.md's sign-in row says so. Verified: TestAccountsAddBringsBackARemovedPlugin signs in to FakeCo, the plugin host's test plugin, with a key, removes it from magpie and runs magpie accounts add fakeco with another key. TestPluginLoginTakesTheLapseOff signs in with magpie plugin login's browser way (open and xdg-open stubbed), has a usage read mark the account lapsed as FakeCo's answer says, and signs in again. With plugins_cli.go from main they fail with "signed in again, but not listed: no provider "fakeco" — magpie providers lists them" and "signed in again, but still lapsed: [{… Lapsed:gone@fake's FakeCo sign-in has expired — sign in again …}]"; with this they pass. With PluginSignedIn's ShowAccount taken out only the first fails, and with its clearPluginLapse taken out only the second. Neither covers the own account: FakeCo has none, and no test covers that part of clearPluginLapse on main either. Under a temporary HOME with Bun 1.4.2, -race -count=5 on the two and on TestAccountsOfAPlugin pass. Like TestAccountsOfAPlugin, the two are in the root package, which has no testenv.Offline, so the plugin host they start asks models.dev for its catalog. go test -tags nogui ./... passes on main at443f1cbcand at0bf9d77d. Two earlier full runs failed only internal/plugin's TestHostJSCancellation/ready-wait at its 10-second Node deadline (signal: killed) while other sessions' tests loaded the machine, the timeout76dc5aedand #1036 also record; internal/plugin is unchanged and imports nothing changed here. Not tried with a real plugin's sign-in.
Subsystem design references
These pages describe current responsibilities, runtime paths, state, and contracts. Start with the subsystem relevant to your task, then follow its source links to verify the implementation you change.
| Subsystem | Reference |
|---|---|
| Built-in subscriptions moving to plugins | Provider and plugin ownership |
| Plugins that run on the gateway's requests and replies | Gateway middleware |
| Claude subscription turns across Claude Code runs | Claude subscription bridge |
| How the gateway picks a provider, key or account, and falls back | Gateway routing and fallback |
| Connecting an agent's config to the gateway, drift, disconnect, WSL twins | Agent wiring |
| Providers, keys, subscription accounts, refresh and allowance | Providers and accounts |
| Instructions, MCP servers, skills and RTK written into agents | Library |
Tray, panel, window, magpie web and the page's JSON API |
GUI app shell |
A subsystem can span several packages or repositories. Its reference describes the behavior those parts provide together; it does not need to list every function.
Maintain a reference
Update the owning reference in the same PR when a change alters a documented responsibility, interface, state transition, runtime path, compatibility rule, or failure behavior. An internal refactor that preserves these contracts does not require a prose change.
Keep each fact in one reference and link to it from agent instructions or other pages. Link to source files and named symbols rather than copying implementation details that change frequently. Explain field meanings and constraints that signatures alone do not express.
For a new reference, include the responsibilities and boundaries, source of truth, main runtime paths, state transitions, important constraints, and existing verification commands. Include failure and compatibility behavior where relevant. Document current behavior separately from proposed changes; the proposal belongs in its issue or PR.
Describe a PR's semantic changes
For each affected subsystem, explain the observable change with its trigger and before/after behavior. Include changes to interfaces, state, compatibility, and failure behavior when relevant. Link the affected reference, implementation, and verification evidence. State when a refactor preserves the documented behavior.
For example, a change to plugin adoption should explain which migration state and account condition trigger it, which implementation handles requests before and after adoption, and which test exercises that transition.
This is an author-provided explanation that a reviewer verifies against the diff and surrounding code. It is not an automatically proven compatibility verdict.
Review a change
- Identify the affected subsystem and the actual runtime path, including plugin or built-in ownership.
- Compare the claimed before/after behavior with the base and head implementation. Trace relevant callers and state transitions beyond the changed lines.
- Check that changed contracts appear in the owning reference and that source links still resolve.
- Check that the cited tests or runtime evidence exercise the claimed behavior. Report checks that were not run.
A documentation update alone does not prove the implementation. Existing tests and runtime evidence remain necessary for behavior changes.