Repository navigation
fix(core): coordinate credential refreshes across locations and processes - #54023
Merged
Merged
Conversation
rekram1-node
force-pushed
the
coordinate-refresh
branch
from
October 13, 2026 01:30
d4bfd0f to
7b6078a
Compare
rekram1-node
force-pushed
the
coordinate-refresh
branch
from
October 26, 2026 05:25
53dc5c4 to
8e3231a
Compare
1 task
pascalandr
added a commit
to NeuralNomadsAI/CodeNomad
that referenced
this pull request
Oct 29, 2026
## Summary - Qualify OpenCode **2.2.1** (latest stable, 29 October) with the existing locked **2.1.0 client/plugin** dependencies, and **recommend** it. - Keep the technical **minimum at 2.1.2**: no consumed contract changed, so the minimum has no new reason to move. - 2.1.2, 2.1.4 and 2.2.0 daemons stay ready and get an optional upgrade offer. 2.2.1 is the `tested` version. Newer and custom labels keep authenticated contract recognition. - The reviewed-contract fast path lists the 2.1.2 minimum and the 2.2.1 target. - The recommended version is updated in the help (all ten locales), both READMEs, `CONTRIBUTING.md`, `dev-docs/OPENCODE_COMPATIBILITY.md` and the architecture skill. ## Contract review (v2.1.4 → v2.2.0 → v2.2.1; 661 + 134 commits) Reviewed from the exact tags `v2.1.4` (`68bc26b7da`), `v2.2.0` (`1097e2668b`) and `v2.2.1` (`3ebcf9a32c`), the published npm packages, and an authenticated OpenAPI dump from each official CLI running in its own sandbox. The [changelog](https://opencode.ai/changelog) covers 2.2.0. It had no 2.2.1 entry at review time (29 October), so 2.2.1 was reviewed from its commit log. - **Authenticated OpenAPI:** 124 → 130 → **134 paths**, 149 → 156 → **162 operations**. Each dump matches the tagged `packages/protocol/openapi.json`. - **2.2.0 adds** `GET /api/plugin/gui`; `POST /api/experimental/generate/{text,image,decisions}`; `POST`/`DELETE /api/experimental/fs/watch`; `GET /api/experimental/media/{hash}`; and `GET /api/experimental/usage/limits`. - **2.2.0 removes** `POST /api/experimental/voice/session/{sessionID}/instructions`. - **2.2.1 adds** six `/api/experimental/schedule` operations (list, create, get, delete, pause, resume), gated by `experimental.schedules`. - **Changed operations:** - `GET /api/model` gains an optional `types` filter. Without it the route lists chat models only. Image and decision models are new in 2.2.0, so the default listing matches 2.1.4. - `GET /api/event` gains an optional `types` filter. - `GET /api/vcs/diff` gains an optional `untracked` parameter. - `POST /api/experimental/generate` becomes the deprecated alias of `/generate/text`. - Nine session, project and saved-permission routes drop `LocationNotFoundError` from their 404 responses: they no longer boot a Location ([#58997](anomalyco/opencode#58997)). - `GET /api/agent` can now answer 503 while plugins settle ([#59082](anomalyco/opencode#59082)). - In 2.2.1, the session log and persistent-PTY operation IDs move under `experimental.*`. Their paths are unchanged. - **Schemas:** all 103 consumed-family schemas are byte-identical from 2.1.4 to 2.2.1. These are `V2EventEncoded`, `Session.*` (messages, parts, inbox, info), `Form.*`, `Permission.*`, `Prompt*`, `Event.*`, `Location.*` and PTY/Shell. The session prompt, environment, message, form/permission reply and RPC operations are unchanged too. The only `Config.InfoEncoded` changes are additive opt-ins: `experimental.media_blob`, `experimental.image_generate`, `experimental.schedules` and `timezone`. Model `type`, prompt-cache `cache` settings, `FileDiff.Info.more`, `Mcp.Server.title` and `ServerInfo.hostname` are additive fields. - **Admission:** every predicate CodeNomad's `compatibility/negotiate.ts` checks holds on 2.2.0 and 2.2.1. That covers the canonical API, `session_environment` and the 2.1 replay shape (`native` without `state`, compaction `metadata` without `recent`). - **Instance proxy:** none of the new routes is on the allowlist. That includes the GUI plugin bundle, generation, file watch, media blobs, usage limits and schedules. The removed voice route was never exposed. The routes CodeNomad does proxy kept their paths. - **Published declarations** (2.1.0 pins → 2.1.4 → 2.2.1): - **Client:** 143 → 147 → 185 methods. 2.2.1 mirrors the experimental methods under `client.experimental.*`. The only removal is `voice.instructions`, which CodeNomad doesn't use. 2.2.1 doesn't send the `experimental-media-uri` capability, so media reads stay inline. - **Schema:** 2.2.0 adds media refs (`MediaRef`, `Session.Message.MediaUser`, inbox `MediaUserPayload`), `generate`, `usage-limits` and `Model.Type`. 2.2.1 adds `goal` and `schedule`. The one removal since 2.1.0 is `SessionEvent.Source` (2.1.4). - **Plugin:** 2.2.0 adds the `gui/*` SDK (excluded from the host runtime) and `Host.packageID`. 2.2.1 adds `SessionIdle`/`SessionIdleBackground`. The event `subscribe` domain gains an optional `types` filter; CodeNomad's plugins don't subscribe through it. - **Typecheck:** the server and UI typecheck with **0 errors** against the 2.1.4 and 2.2.1 client/schema/plugin declarations, through scratch `paths` overrides. The pinned 2.1.0 control is also clean. - **Host modules:** CodeNomad's three daemon-loaded bundles resolve `@opencode/plugin` (`Plugin`), `/effect` (`Plugin`, `Rpc`, `Location`), `/promise/adapter` (`fromPromise`), `/rpc` (`Rpc`), `effect` and `effect/encoding` (`Base64Url`) from the host. `@opencode/schema` and `@opencode/client` are bundled from the 2.1.0 pins, and `@opencode/schema/location` imports are type-only. A probe plugin loaded in an isolated **2.2.1** daemon resolves every one of these modules and bindings, as on 2.1.4. The host Effect stays **4.0.1**, with the same lockfile hash. - **Private native services:** the pending-request reader and Missions recurrence use keyed native services: `LocationServiceMap`, `Form`, `Permission`, `Location`, `Job`, `Database`, `Session` and `Bus`. Their keys are unchanged at v2.2.1, and the members CodeNomad calls are unchanged. The changes are additive: `Permission.assertNotDenied`, `Session.*WithRefs`, an `image` Job kind, and `LocationServiceMap.canonical` normalizing paths. - **Storage** (migrations from 2.1.4 to 2.2.1): `blob`/`blob_ref` (always created; written only with `experimental.media_blob`) and `schedule` are new. `session_pending` is dropped; CodeNomad never reads it. A `session_v2.allowance` column is added and dropped within 2.2.0. - The tables CodeNomad reads directly (`session_message`, `session_v2`, `session_inbox`, `event`, `event_sequence`, `kv`) keep their shape. - The pruning plugin's `media-blob.ts` already projects `media:<sha256>` refs. - The pruning fixture enables `media_blob` explicitly, so the always-present `blob` table doesn't change its expectation. - Missions reads `session_inbox` only by count and ID in production code. - **Behavior changes checked:** - **Code Mode:** 2.2.1 moves programs onto one job queue ([#57976](anomalyco/opencode#57976)). The run-to-completion regression behind the 2.1.2 minimum passes on 2.1.4, 2.2.0 and 2.2.1: `{ total: 2000, beforeAwait: 1000, count: 1000 }`. - **Session form polling** returns `[]` without booting a cold Location ([#59083](anomalyco/opencode#59083)). Forms are in memory, so nothing pending is hidden. This matches CodeNomad's existing-only pending reader. - **Location keys and `session.create` directories** are canonical on POSIX too: trailing slash removed, `posix.normalize` ([#59327](anomalyco/opencode#59327)). Windows already normalized. This is low risk; it was reviewed in source only, since the location and ownership fixtures run on Windows and the POSIX certification covers migration, Code Mode and Git-degraded. - **Location keep-alive:** the changelog line "keep project services alive … with a 24-hour inactivity cap" ([#57274](anomalyco/opencode#57274)) doesn't match the shipped tree. That commit was superseded before 2.1.4, and no cap exists at v2.2.0 or v2.2.1. From 2.1.4 to 2.2.1, `location-activity.ts` only canonicalizes paths and generalizes Job kinds. Long-waiting Missions sessions keep their Location as on 2.1.4. - **Plugin readiness:** each runner step and tool call now waits for plugin activation ([#51133](anomalyco/opencode#51133)), and agent/provider lists wait for plugins. Presence leases stay outside the discovery root, so this adds no reload. - **Service lifecycle:** registration and config renames are retried on Windows. The managed service ignores a saved `XDG_STATE_HOME` ([#58815](anomalyco/opencode#58815)); CodeNomad never sets it. The `OPENCODE_SERVER_USERNAME` change was added and reverted within 2.2.0. Detached POSIX children are killed when OpenCode exits; service start uses its own spawn path. - **2.2.1 opt-ins:** - schedules, both the API and the agent tool, need `experimental.schedules`; - `image_generate` needs `experimental.image_generate`; - `/goal` runs only when invoked; - the subagent tool description now invites follow-ups to *running* subagents, with the same mechanics as 2.1.4. - **Other changes:** compaction sizing from the provider's count, MCP malformed-tool tolerance, OAuth refresh coordination ([#54023](anomalyco/opencode#54023)) and webfetch redirect rules. They touch no consumed contract and were reviewed in source; of these, the native suites exercise compaction only. - **Upstream #52922** (Bus routing before Location fanout) is still open: keep the discovery and event-relay mitigations. **Verdict:** no consumed contract changed from 2.1.4 to 2.2.1. - The **2.1.0 client/plugin pins can stay**. - The **2.1.2 minimum has no reason to move**. - 2.2.1 is admitted through the existing contract recognition, and nothing found blocks recommending it once the native suites pass on it. - No CodeNomad bug was found. Bumping the pins is optional. It would bring the 2.2 client's discard-tool-result-payload option and its "register running sessions missed while disconnected" fix ([#53012](anomalyco/opencode#53012)). It would also move the replay reducer, so it belongs in its own PR. The UI and server already typecheck against 2.2.1. **Not verified here:** - the full native suites against 2.2.0/2.2.1 (only the Code Mode regression and the host-module probe ran); - the TUI pruning companion under OpenTUI 0.7. It uses only `Plugin.define`, `ctx.data.session` and `ctx.data.listen`, so the 0.7 renderable changes don't apply, but this wasn't run; - POSIX canonicalization on a real Linux/macOS runtime; - the 2.2.1 release notes, which were unpublished at review time. ## Isolated Windows qualification Base: dev `157766206`; Node **25.2.1** (local; CI runs the pinned 24.20.0); exact official CLI **2.2.1**; unchanged locked client/plugin **2.1.0**. - **Build and checks:** production server and native plugin builds (pruning, automation, Missions), root typecheck. - **Native runtime:** Code Mode job ordering (`{ total: 2000, beforeAwait: 1000, count: 1000 }` on 2.2.1), plugin runtime, automation; complete pruning, history, proxy, navigation and media; Location, worktree families/management and event relay; permission receipts; Git init/degraded. - **Missions:** contract, family control, native and trajectory; **all 11 recurring Mission journeys** (`ABCDGELFWQN`). - **Sessions:** environment, fork, aside, interrupt, lifecycle, blank cleanup (`--conditions=browser`, as in CI), compaction isolation, prompt skills, provider usage/accounts/selection, service usage, web search, plugin packages, output limit, Promise-tool failure, subagent depth, temporary workspaces. - **Migrations:** history from **2.1.0, 2.1.2, 2.1.4 and 2.2.0** to 2.2.1. - **Installation:** fresh install, native upgrade and legacy replacement fixtures. - **Units after the recommendation change:** compatibility/transport/update **35/35**; full server **2,628 passed, 8 skipped, 1 cancelled** (`index.test.ts` shutdown test timed out at 5 s under full-suite load and passes **4/4** alone); ten-locale help checks pass. In CI, `latest` resolves to 2.2.1: the compatibility job runs the native suites against the minimum and latest on Windows, and the migration and Git-degraded certification runs them on Ubuntu, Windows and macOS. WSL, packaged desktop, installers/updater end-to-end and real mobile devices keep their separate release gates.
brentyates-swx
pushed a commit
to swxtchio/swx-opencode-archive
that referenced
this pull request
Nov 4, 2026
…sses (anomalyco#54023) (cherry picked from commit 9435378)
brentyates-swx
added a commit
to swxtchio/swx-opencode-archive
that referenced
this pull request
Nov 4, 2026
* chore(core): plan OAuth refresh backport 1. Cherry-pick donor 9435378 with -x; keep refresh ownership in the global Credential node, use EffectFlock, and re-read persisted credentials inside Integration.connection.resolve's lock. 2. Add producer-driven resolver regressions for shared success and failure, caller cancellation, a second process on one credential store, and bounded timeout/retry; prove changed behavior with focused removal runs. 3. Add the actual PR-number backport entry, then run package-local focused tests, typecheck, the required lint wrapper, and the crew identity gate before handing off the draft without merging. * fix(core): coordinate credential refreshes across locations and processes (anomalyco#54023) (cherry picked from commit 9435378) * test(core): bound OAuth refresh contention regression Claude-Session: ses_e5b0433c1ffe9GfcGDuV8G9vMw * test(core): align webfetch result type Claude-Session: ses_e5b0433c1ffe9GfcGDuV8G9vMw * test(core): harden OAuth refresh regressions for CI Claude-Session: ses_e5b0433c1ffe9GfcGDuV8G9vMw * test(core): bound joining refresh cancellation Claude-Session: ses_e5b0433c1ffe9GfcGDuV8G9vMw * test(core): assert shared refresh survives joining cancellation * test(core): make OAuth process fixture platform-safe * test(core): prove single refresh after callers settle * test(core): clean OAuth refresh scratch roots * test(core): harden OAuth refresh review regressions * test(core): correct OAuth refresh review proofs * test(core): allow OAuth workers more startup time --------- Co-authored-by: Aiden Cline <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Each Location builds its own
Integrationservice, so two Locations (or two processes sharing the database) could both see an expiring OAuth credential and refresh it at once. Providers that rotate refresh tokens reject the second request, and the user ends up signed out.credentials.lock(id, operation)on the globalCredential.Servicelets concurrent callers share one running refresh, including its failure. The refresh runs in the global scope, so cancelling the Session that started it doesn't cancel it for the others.EffectFlockfile lock keyed by credential ID, so other processes wait instead of replaying the same refresh token. Whoever waited re-reads the credential and finds the token the previous holder saved.Out of scope: MCP OAuth refreshes (they go through the MCP SDK), and recovering from a 401.