What changed: ended() kept a turn's id (turnUUID, backKey) only until the reply that called a tool. From then on, a client that went away while the run answered the turn's tool results (a cut connection, or the user interrupting to send a message queued during the tool call) could not be rewound: letGo ended the run. The client's next request, its results sent again or the next turn, logged "tool results no run waiting", and a new Claude Code was told the whole conversation in one message, with every image in it, which the model in #1365 took for new. Now the turn's id lasts across its tool rounds, until a reply ends without calling a tool. letGo rewinds the run to the turn's user message, its tool rounds with it, and the run waits at the conversation before the turn. The next request is resumed there (nextTurn's key is backKey) and told the turn since its user message. resume logs "a turn taken back goes on in its run" after the "no run waiting" line. A run started anew for the turn, or one with a call still in the client's hands, is ended as before. Nothing else sent to Anthropic changes. Why: the issue's log showed "no run waiting" four times, then a whole- conversation replay. On current main a queued message with an image is sent beside the tool_result (checked with Claude Code 2.1.295 against a local mock) and match keeps it with the result, so it does not break matching by itself. The MCP call reaches magpie ~3ms after the tool reply's message_stop, well inside match's 500ms grace. What drops the run is a client going away during a tool-result continuation. The exact client trigger in the report is not confirmed. What was run: - New TestClaudeToolResultsGivenUpOnAreTakenBack (fake Claude Code that makes its call through magpie's MCP callback and answers rewind_conversation): the turn's results are given up on mid-reply, the run is rewound to the turn's message, and the resend goes on in the same process, told the turn and not turn 1's words. With ended() clearing the id as before, it fails: no rewind is asked. - Real Claude Code 2.1.295 against a local mock of Anthropic: rewind_conversation to a user message, sent while the reply to that turn's tool result is streaming, answers rewound:true, and the next request's messages drop the turn and its tool round. - go test -tags nogui ./internal/gateway/ (full package) under a temp HOME; -race -count=20 on the new test and the TestClaudeTurnGivenUpOn* / TestClaudeFirstTurnGivenUpOnEnds tests; gofmt -l; go vet for darwin, linux and windows (-tags nogui); GOOS=linux/windows go build. Not tried with a real Claude account or Claude Desktop.
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.