Skip to content

fix(opencode): require OpenCode 2.2.3 and adopt its image tool contract - #1209

Merged
pascalandr merged 1 commit into
devfrom
chore/qualify-opencode-2.2.3
Oct 30, 2026
Merged

pascalandr merged 1 commit into
devfrom
chore/qualify-opencode-2.2.3

Conversation

@pascalandr

@pascalandr pascalandr commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • OpenCode 2.2.3 becomes both the technical minimum and the recommended runtime. The minimum moves because upstream #60258 (shipped in 2.2.2) changed the image tool contract that CodeNomad's Images gallery, Design agent and Images settings consume: one name, experimental_image_generate, for the tool, its permission action, its job type and its completion notices' metadata.source, and the tool on by default (only experimental.image_generate: false disables it). On 2.2.2+, the gallery lost every generation entry and the Design companion fixture timed out; 2.2.1 passed.
  • Images are implemented for the 2.2.3 contract only, with no fallback to the old name: IMAGE_TOOL drives the gallery and its proof; imageGenerationSelection reads unset as on; the Design agent and skills name the new tool; the activation fixture exercises the image tool instead of falling back to webfetch; the gallery fixture represents the 2.2.3 transcript format.
  • Older daemons (2.1.x, 2.2.1, 2.2.2) go through the existing below-minimum path (native_contract). The 2.1.0 client/plugin/schema pins stay.
  • Help (all ten locales), READMEs, CONTRIBUTING.md, dev-docs (compatibility, pruning, panel extensions, settings) and the architecture skill describe the new minimum, its reason and default-on image generation. The setup minimum-reason UI string is updated in all locales.

Contract review (v2.2.1 → v2.2.2 → v2.2.3; 40 + 101 commits)

Reviewed from the exact tags v2.2.1 (3ebcf9a32c), v2.2.2 (098aa7fd6e) and v2.2.3 (082ac6281f), the published npm packages, and an authenticated OpenAPI dump from each official CLI running in its own sandbox. The changelog has entries for both 2.2.2 (29 October, highlights only) and 2.2.3 (30 October, full notes); everything below was also checked against the commit log and source.

Headline: the HTTP contract CodeNomad consumes didn't change, but 2.2.2 renamed the image tool from image_generate to experimental_image_generate and turned it on by default (#60258). CodeNomad's Images/Design features match the old name and the old default, so they break on 2.2.2 and later (see Design companion finding). Nothing else found affects CodeNomad.

  • Authenticated OpenAPI: 134 paths and 162 operations on all three, the same operation set. Schemas 355 → 350 → 351.
    • Added/removed operations: none in either release.
    • Changed operations (2.2.2): GET /api/model, GET /api/event, PUT /api/pty/{ptyID}, POST /api/shell, GET /api/shell/{id}/output, POST /api/debug/heap-dump and GET /api/experimental/migration/v1. All of these are representation-only: the Effect 4.0.6 JSON Schema output flattens nested anyOf, bounds integers with maximum: 9007199254740991 (and minimum for signed integers), and rewrites the numeric-string query pattern. The event-type and model-type enums are unchanged.
    • Changed operations (2.2.3): only the description of POST /api/credential, which can now be refused by an integration.connect policy.
    • Consumed schemas: the same family selector as the 2.2.1 review (V2EventEncoded, Session.*, SessionStats.*, Form.*, Permission.*, Prompt*, Event.*, Location.*, PTY/Shell). Re-derived from that description it selects 104 schemas at 2.2.1 (the 2.2.1 file says 103), all byte-identical from 2.1.4 to 2.2.1.
      • In 2.2.2 the seven alias copies Form.Fields_1..3 and Session.Message.Native_1..4 collapse into their base schemas, which are byte-identical to the copies, leaving 97. Of these, 71 are byte-identical and 24 differ only in generator representation.
      • Two changes are additive and optional: Session.Info.time.busy, the start of the latest execution (#59910), and the input field PromptInput.FileAttachment.source (Prompt.FileSource, inline or uri; CodeNomad doesn't send it).
      • V2EventEncoded, Session.Message.* content, Session.Inbox.*, Form.* semantics, Permission.* and Event.SeqEncoded are unchanged.
      • From 2.2.2 to 2.2.3 the family is byte-identical.
    • Config.InfoEncoded: no semantic change in 2.2.2 (representation only). 2.2.3 extends the managed-policy action enum (top-level and experimental.policies) with integration.connect, mcp.use, skill.use and plugin.use (#60315); CodeNomad writes no policies. The meaning of experimental.image_generate changed: unset now means on, and only false turns it off. The schema shape stayed the same and the OpenAPI carries no description, so no contract signal reveals it.
    • Schedules (not consumed on dev): when becomes optional; if/until conditions (shell in 2.2.2, Code Mode in 2.2.3) are added; outcomes gain checked/expired.
    • Admission: every predicate in compatibility/negotiate.ts (unchanged since the 2.2.1 review) holds on 2.2.1, 2.2.2 and 2.2.3.
    • Instance proxy: no new routes and an unchanged allowlist. Proxied routes whose representation changed: GET /api/model, PUT /api/pty/{id}, POST /api/shell and GET /api/shell/{id}/output. Integer bounds and an equivalent numeric pattern accept everything CodeNomad sends.
  • Published declarations (2.2.1 → 2.2.3):
    • Client: 185 methods on all three; the method tree is identical. 2.2.2 adds the generated types ScheduleCondition*; FormFields2 is renamed to FormFields1, which is generated-type churn. 2.2.3 adds the Promise media helpers withMediaRefs, mediaHashFromUri and MediaRequestOptions (#60116) and persistent-PTY frame helpers in solid/pty. CodeNomad imports none of these.
    • Schema: 2.2.2 adds schedule conditions (ShellCondition, CheckMode, …) and exports SessionLegacy.part. 2.2.3 adds CodeCondition* and policy.Action.
    • Plugin: 2.2.2 adds the GUI ComposerQuoteAnchor/ComposerFocusOptions. 2.2.3 has no export changes, but the optional peers @opentui/* become >=0.8.0 (#60385). The published dependencies.effect moves from 4.0.1 to 4.0.6 (2.2.2).
    • Typecheck: the server and UI typecheck with 0 errors against the 2.2.2 and 2.2.3 client/schema/plugin/effect declarations through scratch paths overrides. The pinned 2.1.0 control is also clean. The harness now also remaps effect itself; the 2.2.1 harness skipped it because effect's exports are plain .js strings.
    • Host modules: the three daemon-loaded bundles still import @opencode/plugin (Plugin), /effect (Plugin, Rpc, Location), /promise/adapter (fromPromise), /rpc (Rpc), effect and effect/encoding (Base64Url) from the host. A probe plugin in an isolated 2.2.3 daemon resolves every module and binding, as on 2.2.1. runtime-modules.ts is unchanged between the tags.
    • Host Effect: 4.0.1 → 4.0.6 (2.2.2, #59630). The 2.1.0 @opencode/schema/@opencode/client code bundled into the Missions and pruning plugins now runs on host Effect 4.0.6. The plugin-runtime, pruning, Missions-contract, permission-receipts and image-settings native suites pass on 2.2.3.
  • Private native services: the keys (LocationServiceMap, Form, Permission, Location, Job, Database, Session, Bus) and the members CodeNomad calls are unchanged. The changes are additive: Permission.Unattended (a reference that denies unanswerable asks for schedule checks), a CorrectedError.message getter, and Session deciding fork media from the parent's location.
  • Storage (three migrations): session_part (new table), session_v2.time_busy and four schedule check columns.
    • session_part (#58915): history reads now prefer session_part rows over session_message.data.content for assistant messages. The projector writes parts there only for messages that already have part rows, so new messages stay inline. Every 2.2.3 fixture DB inspected has 0 session_part rows, and upstream dev (1e28374e48, 30 October) has no writer either.
    • The tables CodeNomad reads directly keep their shape.
    • Forward risk: when a release starts writing parts, CodeNomad's direct session_message.data.content readers and the pruning UPDATE … json_set(data,'$.content',…) (session-pruning/transaction.ts) would silently miss or be shadowed by part rows. See Recommendations.
  • Behavior changes checked:
    • Image generation (2.2.2): renamed and on by default. See the dedicated section.
    • Code Mode: inner tool progress no longer replaces the execute call rows (#60480), and progress is throttled to at most one update per 100 ms (#60120). Programs see the same failure text as direct calls (#60109), fetch in Code Mode is now subject to webfetch permissions (#59954), for…in visits inherited keys (#59933) and null survives text encoding (#57639). The run-to-completion regression behind the 2.1.2 minimum passes on 2.2.3: { total: 2000, beforeAwait: 1000, count: 1000 }.
    • Permissions and MCP: permission reasons and feedback are kept in MCP tool failures (#60102). Shell permission checks moved fully off tree-sitter and now check redirects, PowerShell method calls and dynamic directory changes (#60332, #60376), so some shell commands may now ask where they didn't. The managed-policy split (integration.use vs mcp.use/skill.use/plugin.use) is a changelog "breaking change" for managed deployments only.
    • Sessions: deleting a session removes its persistent terminals (#60322), and a persistent terminal with a missing cwd is rejected (#60300). Forked and imported sessions store media as blobs when media_blob is on (#60118). The native fork suite passes on 2.2.3.
    • Providers: a built-in Claude Agent provider uses the user's Claude Code login (#58782). Plugin-supplied model variants are preserved (#60097), and the dynamic provider npm fallback runs after external SDK hooks (#60380). These are source-reviewed and touch no consumed contract.
    • Persistent PTY binary (the process daemon): 2.2.2 switches from @opencode-ai/pty 0.3.1 to @opencode/pty 0.3.3 (#60276). process-daemon-v0.3.3 contains only the release commit after 0.3.2 (a version bump of the @opencode/pty-* binaries). v0.4.0 (terminal renaming, #60324) is not shipped in CLI 2.2.3, which pins 0.3.3. CodeNomad only lists persistent PTYs (worktree deletion guard), whose schema is unchanged apart from representation.
    • Client fix not inherited: the 2.2.3 client evicts missing sessions on 404 (#60459). CodeNomad stays on the 2.1.0 client.
    • @opencode/ai breaking changes (media moved to /experimental, one tool()): CodeNomad doesn't import @opencode/ai.
    • Desktop/TUI-only changes (timeline performance, side-panel tabs, spellcheck, question sounds, OpenTUI 0.8) don't affect CodeNomad.

Verdict:

  • The 2.1.0 client/plugin pins stay. Bumping them would bring nothing CodeNomad consumes.
  • No consumed HTTP/plugin contract broke; the breaking change is the image tool contract of #60258, consumed by the Images gallery, the Design agent and the Images settings.
  • Decision applied in this PR: 2.2.3 becomes both the minimum and the recommendation, and Images are implemented for the 2.2.3 contract only (see Summary). This replaces the review's initial suggestion to keep the 2.1.2 minimum and accept both tool names.

Not verified here:

  • the full native suite matrix on 2.2.2/2.2.3; only the runs in Appendix I were performed;
  • the forward-compatibility of the session_part readers against a runtime that writes parts (no such runtime exists yet);
  • the Claude Agent provider and shell-permission changes at runtime (source only);
  • macOS/Linux.

Design companion finding

Symptom: scripts/test-design-companion-native.mjs <cli> passes on 2.2.1 and fails on 2.2.2 and 2.2.3 with Error: Timed out: series 1 generated and read. Reproduced independently in a scratch checkout of origin/dev (f39642bc4), with CODENOMAD_NATIVE_FIXTURE_PARENT outside the home:

test-design-companion-native 2.2.1  exit=0  PASS AC-16, AC-52
test-design-companion-native 2.2.2  exit=1  Timed out: series 1 generated and read
test-design-companion-native 2.2.3  exit=1  Timed out: series 1 generated and read

What the step waits for (test-design-companion-native.mjs:238-241): the fixture's loopback chat provider answers a RUN [...] turn with two tool calls { tool: "image_generate", args: { prompt, path: ".codenomad/design/icon/<name>" } }. The step then waits until the transcript holds two synthetic notices with metadata.source === "image_generate" (line 209) and two read parts of the generated files. No HTTP generate route, file watch or media blob is involved; it is the agent tool path.

Cause: an intentional upstream contract change, not an upstream bug. In 2.2.2, #60258 (d4e48f54da, "turn experimental_image_generate on by default") changed packages/core/src/tool/plugin/image-generate.ts:

  • export const name = "image_generate" → "experimental_image_generate". That one constant is the tool name, the permission action (assertions: ["experimental_image_generate", "edit", "read"]), the Job type, and the completion notice's metadata.source. session/execution/restart.ts also tags restart-recovery notices source: "experimental_image_generate".
  • The opt-in became default-on: if (!enabled || imageGenerate(entries) === false) return. An unset experimental.image_generate now offers the tool; only false removes it ("still honored for configs from when the tool was opt-in; permissions are the supported way to turn it off").
  • The 2.2.2 changelog states it: "agents can now use experimental_image_generate without additional configuration".

2.2.2 also wraps the notice text as <image id="…" state="…">\n…\n</image> and adds id to the tool output (#60098). CodeNomad's Saved image: (.+) \( parse still matches; jobID, state and path metadata are unchanged.

Evidence (fixture DBs copied read-only to evidence/):

2.2.3, original fixture (calls "image_generate"):
  seq 5  tool image_generate  error  "No tool named \"image_generate\" is currently available. Please use a tool from the available tool list."  (x2)
  -> no image job, no notice, no read: the wait times out.

2.2.3, fixture with the tool name changed (scratch copy; CodeNomad server code unmodified):
  seq 5  tool experimental_image_generate completed {path:…/icon-minimal-a, model:"fiximg/image", jobID:"image_18cfb59…"}
  seq 18 synthetic {"source":"experimental_image_generate","jobID":"image_18cfb59…","state":"completed",…}
         <image id="image_18cfb59…" state="completed"> | Image request finished. Saved image: …
  seq 20 tool read completed (x2) … seq 40/48/49 refinement
  -> files, image calls and reads all correct, then
     AssertionError: generations.length 0 !== 3   (test line 260: gallery "generation" entries)

So the product bug is real and independent of the fixture: on 2.2.2+ CodeNomad's Images gallery finds no generation entries. Images still appear through their read parts, but without prompt, model, lineage or "proven" origin, and galleryImageReference refuses them as changed.

CodeNomad code that assumes the 2.2.1 name or default (origin/dev):

Where Assumption Effect on 2.2.2+
session-pruning/assets-gallery.ts:56,139 part.name === "image_generate" no generation entries; references refused changed
session-pruning/assets-gallery-proof.ts:104,130 tool name and notice source === "image_generate" no calls or notices, so no provenance proof
opencode/image-settings.ts imageGenerationSelection(undefined) → false (doc: "Reads … as OpenCode does") unset means off Settings → Images shows Off and images.status() returns "disabled" while the tool is offered; help step "Turn on image generation" (docs/help/en/extensions.md:73) is no longer needed
opencode/design/opencode/agents/design.md, skills/design-core, design-logo, design-ui-mockup the agent is told to call image_generate the model sees only experimental_image_generate; the first call can fail with "No tool named …"
scripts/test-design-companion-native.mjs:125,209,238,301 fixture calls/filters image_generate red on 2.2.2+
scripts/test-design-activation-native.mjs:76,307 ask rule on action image_generate; tool detection by that name passes but loses coverage: askTool falls back to webfetch on 2.2.2/2.2.3 (logged)
dev-docs/SESSION_PRUNING_RPC.md, PANEL_EXTENSIONS.md, SETTINGS.md describe image_generate parts and opt-in out of date for 2.2.2+

User permission rules written for action image_generate no longer match on 2.2.2+; that is an OpenCode-side change for users. CodeNomad's help never names the permission action; the Design fixtures and agent use experimental_image_generate.

Fix in this PR (2.2.3 contract only, no fallback):

  1. One IMAGE_TOOL = "experimental_image_generate" constant drives the gallery and its proof; the gallery reads the 2.2.3 notice format.
  2. The native fixtures use the new tool; the activation fixture exercises it (default on) instead of falling back to webfetch.
  3. The Design agent and skills name experimental_image_generate.
  4. imageGenerationSelection reads an unset experimental.image_generate as on; only false disables it. Settings → Images and images.status() report it that way.

Follow-up

  • Hardening for session_part (independent of this upgrade, no 2.2.3 regression): make the direct readers and the pruning transaction fail safe when session_part has rows for the target messages.

Isolated Windows qualification

Base: dev f39642bc4; Node 25.2.1 (local; CI runs the pinned 24.20.0); exact official CLI 2.2.3; locked client/plugin 2.1.0; CODENOMAD_NATIVE_FIXTURE_PARENT outside the user profile for the Design fixtures.

  • Before the image fix (recommendation-only change): every native suite passed on 2.2.3 except the Design companion end to end, which passes on 2.2.1 and times out on 2.2.2/2.2.3 at "series 1 generated and read" (No tool named "image_generate" is currently available).
  • After this change, on 2.2.3: Design companion (3 files, 2 series, all proven, lineage), Design activation (image tool offered to Design and Build, default on), Design content, image settings (default on, Off→On hot reload), session pruning including session images/media, Code Mode jobs { total: 2000, beforeAwait: 1000, count: 1000 }.
  • Full native set on 2.2.3 (before the image fix, unaffected areas): plugin runtime, automation, Location/worktrees/event relay, permission receipts, Git init/degraded, Missions (contract, family control, native, trajectory) and all 11 recurring journeys, sessions (environment, fork, aside, interrupt, lifecycle, blank cleanup, compaction, prompt skills, providers, service usage, web search, plugin packages, output limit, Promise-tool failure, subagent depth, temporary workspaces), migrations from 2.1.2/2.2.1/2.2.2, install/upgrade/legacy replacement.
  • Units and checks after the change: root and server typecheck clean; server 2,825 passed, 9 skipped, 1 cancelled (index.test.ts shutdown test timed out under full-suite load; 4/4 alone); gallery 107/107; Design 23/23; opencode-update 62 passed / 1 skipped; touched UI units 8/8; browser opencode-setup + websearch-settings 38/38; native plugin build 2/2; ten-locale help 31/31.

Upstream anomalyco/opencode#60258 (shipped in 2.2.2) renamed the image tool to experimental_image_generate - the tool, its permission action, its job type and its completion notices' metadata.source - and turned it on by default: only experimental.image_generate: false disables it. CodeNomad's Images gallery matched the old name, so on 2.2.2+ it lost every generation entry, the Design agent asked for a tool that no longer existed, and Settings showed image generation as off while it was on.

OpenCode 2.2.3 becomes both the minimum and the recommended runtime, and Images are implemented for its contract only: one IMAGE_TOOL constant drives the gallery and its proof, imageGenerationSelection reads unset as on, the Design agent and skills name the new tool, and older daemons take the existing below-minimum path. The 2.1.0 client/plugin/schema pins stay.

Help in all ten locales, READMEs, CONTRIBUTING.md, dev-docs and the architecture skill describe the new minimum, its reason and default-on image generation; the setup minimum-reason string is updated in every locale. Fixtures follow the 2.2.3 transcript format, the activation fixture exercises the image tool instead of falling back to webfetch, and CI runs the image settings fixture the same way on both runtimes.

Validation on the exact 2.2.3 CLI: Design companion/activation/content, image settings, session pruning with images, Code Mode jobs and the full native set; typecheck; server units; gallery, Design, update and UI tests; browser setup/settings tests; ten-locale help checks.
@pascalandr

Copy link
Copy Markdown
Contributor Author

Gatekeeper review

ZERO FINDINGS (P0: 0, P1: 0, P2: 0, P3: 0).

Round 1 found no product-code defects and one P3: the PR description still carried the contract review's initial recommendation (keep the 2.1.2 minimum, accept both image tool names), contradicting the implementation. The description now states the applied decision (2.2.3 as both minimum and recommendation, 2.2.3 image contract only) and lists the fix in this PR; round 2 confirmed it matches HEAD.

Verified:

  • Below-minimum admission, update and install behavior, the compatibility fast path, the CI minimum selection and the setup minimum-reason string in all ten locales are consistent.
  • Every remaining image_generate match is the experimental.image_generate config key or a correctly renamed identifier; gallery notice parsing, provenance proof, imageGenerationSelection (unset = on), images.status(), the Settings card, the panel API, the Design agent and skills, and the fixtures follow the 2.2.3 contract.
  • Help translations are consistent and describe the current state only.
  • Focused tests (runtime support, compatibility, OpenCode update, image settings, assets gallery) and the ten-locale help check pass.

Follow-up outside this PR: fail-safe handling for session_part once a runtime writes it.

@pascalandr
pascalandr merged commit 77c2460 into dev Oct 30, 2026
28 of 57 checks passed
@pascalandr
pascalandr deleted the chore/qualify-opencode-2.2.3 branch October 30, 2026 14:45
pascalandr added a commit that referenced this pull request Oct 31, 2026
## Summary

- OpenCode **2.2.5** becomes the **recommended** (qualified) runtime.
The **2.2.3 minimum** and the 2.1.0 `@opencode/client`/`plugin`/`schema`
pins stay: neither 2.2.4 nor 2.2.5 changes a contract CodeNomad
consumes.
- `RECOMMENDED_OPENCODE_VERSION` is 2.2.5, and the reviewed-publication
shortcut in `contractProfile` lists 2.2.3 and 2.2.5; other versions,
2.2.4 included, keep negotiating their authenticated schema. The CI
`latest` lane already resolves the latest stable CLI.
- Help (all ten locales), READMEs, `CONTRIBUTING.md`,
`dev-docs/OPENCODE_COMPATIBILITY.md` and the architecture skill state
two separate versions again: minimum 2.2.3 with its reason,
recommendation 2.2.5.

## Contract review (v2.2.3 → v2.2.4 → v2.2.5; 31 + 19 commits)

Reviewed from the exact tags `v2.2.3` (`082ac6281f`), `v2.2.4`
(`45898d0f85`) and `v2.2.5` (`9bbb8ce18c`), the published npm packages,
and an authenticated OpenAPI dump from each official CLI running in its
own sandbox. The [changelog](https://opencode.ai/changelog) has full
notes for 2.2.4 and 2.2.5 (both 30 October); everything below was also
checked against the commit log and source. CodeNomad side: `origin/dev`
`605820669`.

**Headline:** neither release changes a contract CodeNomad consumes.
2.2.4 adds opt-in **artifacts** (seven `/api/experimental/artifacts`
operations, four tools, a plugin hook), **renamable persistent
terminals**, and moves background-job recovery markers from `kv` into a
new **`job` table**. 2.2.5 is a bug-fix release; its one schema change
is that `compaction.buffer` accepts a percentage such as `"20%"`. The
image tool contract adopted in #1209 (`experimental_image_generate`, on
by default) is unchanged through 2.2.5, and still nothing writes
`session_part`.

- **Authenticated OpenAPI:** 134 → **138** → 138 paths, 162 → **169** →
169 operations, schemas 351 → 362 → 363.
- **Added operations (2.2.4):** `GET
/api/experimental/artifacts/providers`, `GET|POST
/api/experimental/artifacts`, `GET|PATCH|DELETE
/api/experimental/artifacts/{artifactID}`, `GET
/api/experimental/artifacts/{artifactID}/view/*`
([#60351](anomalyco/opencode#60351)). None
removed in either release.
- **Changed operations (2.2.4):** `GET /api/event`, where the `types`
filter enum gains `persistent-pty.updated` and `artifact.updated` (94 →
96 types, none removed). `PUT /api/experimental/persistent-pty/{ptyID}`
can also rename (`title`; `null` clears it), `size` becomes optional,
and a 501 `PtyFeatureUnsupportedError` is returned while an older
terminal service can't rename
([#60329](anomalyco/opencode#60329)).
  - **Changed operations (2.2.5):** none.
- **Consumed schemas:** same family selector as the 2.2.3 review, 97
schemas at every version.
- 2.2.3 → 2.2.4: 95 byte-identical. The two that change,
`PersistentPty.Info` (+ `userTitle`, nullable, required) and
`PersistentPty.UpdateInput` (`size` optional, + `title`), are both
additive.
    - 2.2.4 → 2.2.5: all 97 byte-identical.
- `V2EventEncoded`, `Session.*`, `Form.*`, `Permission.*`, `Prompt*`,
`Event.*`, `Location.*`, PTY/Shell are byte-identical from 2.2.3 to
2.2.5.
- **`Config.InfoEncoded`:** 2.2.4 adds `experimental.artifacts`
(boolean, opt-in). 2.2.5 turns `compaction.buffer` into
`Config.Compaction.Percent | integer`
([#60647](anomalyco/opencode#60647)).
`experimental.image_generate`, `experimental.media_blob` and
`experimental.schedules` are unchanged.
- **Admission:** every predicate in `compatibility/negotiate.ts`
(unchanged on origin/dev) holds on 2.2.3, 2.2.4 and 2.2.5.
`contractProfile` lists only `2.2.3` as reviewed, so 2.2.4/2.2.5 are
admitted through authenticated contract recognition.
- **Instance proxy:** the allowlist is unchanged on origin/dev. No added
or changed route is proxied: artifacts and persistent PTYs are outside
it. The proxied `GET /api/config` returns the additive config fields
above.
- **Published declarations** (2.2.3 → 2.2.5):
- **Client:** 185 → **192** → 192 methods. 2.2.4 adds
`experimental.artifacts.{providers,list,create,get,update,delete,view}`,
the artifact and `PersistentPtyUpdated` types, and
`PtyFeatureUnsupportedError`. It removes
`createPtyClient`/`PtyConnectInput` from `@opencode/client/solid/pty`,
which CodeNomad doesn't import. 2.2.5 adds `ConfigCompactionPercent`.
- **Schema:** 2.2.4 adds the `artifact` module and
`PersistentPty.Updated`; 2.2.5 adds `config/compaction` `Percent`.
- **Plugin:** 2.2.4 adds `effect/artifact` and `promise/artifact` and
`ctx.experimental.artifact` (`transform`, `reload`). 2.2.5 has no export
changes.
- **Typecheck:** the server and UI typecheck with **0 errors** against
the 2.2.4 and 2.2.5 client/schema/plugin/effect declarations. The pinned
2.1.0 control is also clean.
- **Host modules:** the bundles' host imports are identical to the 2.2.3
review. A probe plugin in an isolated **2.2.4** and **2.2.5** daemon
resolves every module and binding. `runtime-modules.ts` is unchanged
from v2.2.3 to v2.2.5.
- **Host Effect:** stays **4.0.6** (`bun.lock` sha512 identical;
published `dependencies.effect` 4.0.6).
- **Persistent PTY binary:** the 2.2.4 and 2.2.5 CLIs ship
`@opencode/pty` **0.4.0** (string `opencode-pty 0.4.0` in the Windows
binary; 2.2.3: `0.3.3`). `process-daemon-v0.4.0` adds only terminal
renaming ([#60324](anomalyco/opencode#60324)):
an additive `rename` request and `features: ["rename"]` with an
unchanged `PROTOCOL_VERSION`. CodeNomad only lists persistent PTYs
(worktree deletion guard) and reads `status`/`cwd`.
- **Private native services:** the keys (`LocationServiceMap`, `Form`,
`Permission`, `Location`, `Job`, `Database`, `Session`, `Bus`, `Shell`)
and the members CodeNomad calls are unchanged. The changes are additive:
`Job.cancelSession` (2.2.4), `Permission.allows` (2.2.5; never asks,
used by `edit` to decide whether to quote file lines), and
`Session.remove` now calls `jobs.cancelSession`. The Missions recurrence
clock calls `Job.get/start/cancel`. Its Job metadata carries no
`sessionID`, so deleting a session doesn't cancel it.
- **Storage:** one migration, `20261030054400_job` (2.2.4,
[#60491](anomalyco/opencode#60491)). It creates
`job`, copies every `kv` row `job.background/%` into it and deletes
those `kv` rows. 2.2.5 adds none.
- CodeNomad reads only its own `kv` keys (Missions, pruning receipts and
claims), never `job.background/*`.
- Native migration 2.2.3 → 2.2.4 passes. Every 2.2.4/2.2.5 fixture DB
inspected holds the `job` table, **0** `job.background/` rows and no
leftover `schedule:leader` lease.
- **`session_part`:** still **not written**. `history.ts`/`projector.ts`
are unchanged from v2.2.3 to v2.2.5, the projector still only rewrites
existing part rows, and every inspected 2.2.4/2.2.5 fixture DB has **0**
rows. The forward risk from the 2.2.3 review stands (Recommendations).
- **Behavior changes checked** (plain-language list in [What users will
notice](#what-users-will-notice)):
- **Image tool** (#1209 contract): `image-generate.ts` is unchanged from
v2.2.3 to v2.2.5. 2.2.4 refactors restart recovery into one
`restartMessage` but keeps `metadata.source:
"experimental_image_generate"`, `jobID`, `state`, `path` and the `<image
id state>` notice text. Design companion, activation, content and image
settings pass natively on 2.2.4.
- **Schedules** (2.2.4,
[#60478](anomalyco/opencode#60478), then
`da87c475a7`): the loop now runs in every server process, including
`opencode serve`, and elects a leader through a `kv` lease
`schedule:leader` (claimed at boot, released when there are no active
schedules). Firing stays opt-in (`experimental.schedules === true`).
CodeNomad uses neither OpenCode schedules nor that key; Missions
recurrence uses its own storage and the Job service.
- **Sessions:** deleting a session now cancels its running background
jobs (shell, image, subagent) and drops their markers (2.2.4). That is
the intended upstream cleanup; CodeNomad's session deletions
(blank-session cleanup, temporary workspaces) get it automatically.
- **Events:** `persistent-pty.updated` and `artifact.updated` reach
CodeNomad's shared event relay. The relay routes generically
(`instance-events.ts`), the 2.1.0 client's SSE reader doesn't validate
types, and the UI handles neither type. The native relay suite passes on
2.2.4.
- **Tools** (2.2.5): a model call to `bash` runs as `shell` with shell
permissions, and the result carries a note
([#60687](anomalyco/opencode#60687)). CodeNomad
already groups and renders `bash` and `shell` alike
(`message-part-grouping.ts:48`, `tool-call/utils.ts`). `read`/`edit`
failure texts are richer
([#60688](anomalyco/opencode#60688),
[#60689](anomalyco/opencode#60689)).
- **Compaction** (2.2.5): the prompt is capped at the context window
when a catalog's input limit is larger
([#60660](anomalyco/opencode#60660)), and
`compaction.buffer` may be `"N%"`. CodeNomad never reads or writes
`compaction.buffer`. LiteLLM context errors now trigger compaction
([#60692](anomalyco/opencode#60692)).
- **Reasoning replay** (2.2.5,
[#60498](anomalyco/opencode#60498)): reasoning
is no longer re-sent as plain text after a model switch, and interrupted
or errored reasoning is dropped from the request. This changes provider
requests only; stored transcripts and `Session.Message.*` are
byte-identical.
- **MCP** (2.2.5): stdio messages over 10 MiB no longer disconnect a
server ([#60694](anomalyco/opencode#60694)), and
an idle Location no longer restarts its MCP servers right after shutting
down ([#60668](anomalyco/opencode#60668)).
- **Providers:** a Console-supplied provider can now be overridden by
user, project and remote config (2.2.4). The Claude Agent provider only
replaces Claude Code's `/login` hint with `claude auth login` in its
errors ([#60592](anomalyco/opencode#60592)); the
guided setup
([#60603](anomalyco/opencode#60603)) is OpenCode
Desktop only. The provider ID (`Provider.ID.claudeAgent`) and its use of
Claude Code's own login are unchanged, so CodeNomad's `claude-agent`
quota reader (#1214) is unaffected.
- **Service lifecycle:** the CLI now adds `127.0.0.1,localhost,::1` to
`NO_PROXY`/`no_proxy` for itself and its children
([#60441](anomalyco/opencode#60441)), so
loopback traffic from daemon-hosted code, CodeNomad's plugins included,
bypasses environment proxies. CodeNomad sets no proxy variables.
- **Code Mode, Forms, permission prompts, media blobs:** no source
changes from v2.2.3 to v2.2.5 (`packages/codemode` only bumps its
version). The Code Mode run-to-completion regression passes on 2.2.4: `{
total: 2000, beforeAwait: 1000, count: 1000 }`.
- **`@opencode/ai`:** one `ai` object for Promise and Effect
([#60516](anomalyco/opencode#60516); left out of
the changelog by
[#60631](anomalyco/opencode#60631)). CodeNomad
doesn't import `@opencode/ai`.
- Desktop/TUI-only changes (desktop terminals on persistent PTYs,
base-branch and worktree shortcuts, quote sending, flat inputs, palette
ordering) don't affect CodeNomad. One indirect effect follows.
- **Worktree deletion:** OpenCode Desktop terminals now live as
persistent PTYs that survive restarts
([#60346](anomalyco/opencode#60346)).
CodeNomad's guard (`routes/worktrees.ts`, "Running persistent PTY blocks
deletion: <id>") will therefore refuse more often for users who also
open terminals in OpenCode Desktop. The behavior is correct, but the
message names the PTY ID rather than the terminal's title.

**Verdict:**

- The **2.1.0 client/plugin pins can stay**. Bumping them would bring
only artifacts, PTY renaming and a config type, none of which CodeNomad
consumes.
- The **2.2.3 minimum has no reason to move**. No consumed HTTP, plugin,
private-service or storage contract broke in 2.2.4 or 2.2.5.
- **Recommend 2.2.5.** It is admitted today through contract
recognition. Recommending it is CodeNomad-side bookkeeping:
`RECOMMENDED_OPENCODE_VERSION` (`runtime-support.ts`), the
reviewed-version check in `contractProfile`
(`compatibility/runtime.ts`), the version texts in docs and help, and
CI's qualification target. This is subject to the full native suite on
the exact 2.2.5 CLI, run separately.
- **No CodeNomad bug found** in 2.2.3 → 2.2.5. Risks: the `session_part`
forward risk (unchanged) and the more frequent worktree-deletion refusal
(UX).

**Not verified here:**
- the full native suite on 2.2.5: only a host-module probe ran natively
on 2.2.5, and everything else for 2.2.5 is source and OpenAPI review
(Appendix I);
- the 2.2.4 recurring Missions journey (`test-recurring-simple-native`
ABCDGELFWQN), which was interrupted by a server restart;
- the `kv` → `job` migration with a real pending background marker: only
the empty case ran, and upstream `job.test.ts` covers the copy;
- whether a `bash` call is stored as `bash` or `shell` (source suggests
the model's name is kept; CodeNomad renders both);
- artifacts, Claude Agent and MCP stdio framing at runtime (source
only);
- macOS/Linux.

---

## What users will notice

**OpenCode 2.2.4**
- **Persistent desktop terminals:** OpenCode Desktop terminals belong to
the session, keep their scrollback across server restarts, stay in sync
with the TUI and other windows, and can be renamed. Renamed terminals
keep their names.
- **Artifacts (opt-in):** with `"experimental": { "artifacts": true }`,
agents can publish, update and serve HTML or static artifacts
(`artifact_create`, `artifact_read`, `artifact_update`,
`artifact_delete`) through a built-in local provider, which stores files
under OpenCode's data folder, or a plugin-provided one.
- **Schedules:** enabled schedules also fire under `opencode serve`, and
only once when several OpenCode processes share a database. They stay
off unless `experimental.schedules` is `true`.
- **Claude Agent:** OpenCode Desktop guides installation, `claude auth
login` and connection checking. Error messages now say `claude auth
login` instead of `/login`.
- **Config:** user, project and remote configuration can override
Console-supplied provider names, limits, variants, settings and headers.
- **Networking:** requests to the local OpenCode service no longer go
through `HTTP(S)_PROXY`.
- **Sessions:** deleting a session also stops its running background
work, such as shells, image generations and subagents.
- **Desktop/TUI:** base-branch shortcut, simpler location popover,
faster Markdown streaming and various fixes.

**OpenCode 2.2.5**
- **Compaction:** `"compaction": { "buffer": "20%" }` keeps a share of
each model's context free; token counts still work. Compaction no longer
overfills models whose catalog input limit exceeds their context window,
and LiteLLM context errors trigger compaction instead of stopping.
- **Tools:** a model calling `bash` gets `shell` (with shell
permissions) instead of an error. `read` names the missing folder, and
`edit` shows the closest lines when `oldString` isn't found.
- **Reasoning:** switching models or resuming an interrupted reply no
longer resends reasoning as plain text, which some providers rejected.
- **MCP:** large stdio messages (over 10 MiB) no longer disconnect a
server, and idle locations don't immediately restart their MCP servers.
- **Desktop/TUI:** worktree and base-branch shortcuts, a shell-mode
toggle, sending a single quote from its popup, and MCP servers removed
from the connect menu.

## Follow-ups (outside this PR)

- Hardening for `session_part` (independent of this upgrade; 2.2.5 still
writes no rows): make the direct `session_message.data.content` readers
and the pruning transaction fail safe when `session_part` has rows for
the target messages.
- Optional UX: when the worktree deletion guard finds a running
persistent PTY, name it by `userTitle ?? title` (2.2.4+) and say it may
be an OpenCode Desktop terminal.
- Any future reader of `compaction.buffer` must accept `"N%"` as well as
a token count.

## Isolated Windows qualification

Base: dev `605820669`; Node 25.2.1 (local; CI runs the pinned 24.20.0);
exact official CLI **2.2.5**; locked client/plugin **2.1.0**;
`CODENOMAD_NATIVE_FIXTURE_PARENT` outside the user profile.

- **Native suites on 2.2.5, all passing:** Code Mode jobs, plugin
runtime, automation, session pruning (history, navigation, media blobs,
proxy, session images), Location/worktrees/event relay, permission
receipts, Git init/degraded, Missions (contract, family control, native,
trajectory) and all 11 recurring journeys, sessions (environment, fork,
aside, interrupt, lifecycle, blank cleanup, compaction isolation, prompt
skills), providers (usage, accounts, account selection), service usage,
web search settings, plugin packages, output limit, Promise-tool
failure, subagent depth, temporary workspaces, Design
companion/activation/content, image settings, history migrations from
2.1.2/2.2.3/2.2.4, shared install of the 2.2.3 minimum.
- **Installer suites after the bump:** upgrade (installed `opencode
v2.2.5`) and legacy replacement (target `2.2.5`) install the new
recommendation and pass.
- **Builds and checks after the change:** server build, native plugin
builds and root typecheck clean; server **2,840 passed, 9 skipped, 1
cancelled** (`index.test.ts` shutdown test timed out under full-suite
load; **4/4** alone); runtime-support/transport/update **35/35**;
browser `opencode-setup` **34/34**; ten-locale help **31/31**.
pascalandr added a commit that referenced this pull request Oct 31, 2026
…trols (#1224)

## Problem
The opt-in `presence-native.test.ts` waits for the `mission_*` tools to
leave the native tool list after the backend's presence lease ends. That
never happens: tools register **once at plugin load, independently of
presence** (`desktop-plugin.ts`), so scheduled passages keep them
(`MISSIONS_RECURRING.md`).

The test fails identically on OpenCode 2.2.0, 2.2.1, 2.2.2 and 2.2.3,
and on every dev commit since registration moved to plugin load; it is
unrelated to #1209. `MISSIONS.md` still said tools register through
presence.

## Change (test and docs only)
- The test now observes what presence actually controls:
- the typed `codenomad.missions` RPC unregisters (only
`rpc.unavailable`/`rpc.method_not_found` count as absent) and
re-registers exactly once when the lease returns;
  - the tools stay registered;
  - the ordinary route refuses with the tools' own message;
- the real provider request carries no `mission_*` tool without
presence, and carries `mission_inspect` with it.
- `MISSIONS.md`: the actual registration, and how to run the opt-in
test.

## User impact
None. Without presence, the context hook strips every `mission_*` tool
from model requests and the executors refuse; the new test proves this
natively.

## Validation
- `presence-native` passes on isolated OpenCode 2.2.3, 2.2.2 and 2.2.0.
- Red checks: it fails with presence retention always true, with the
context hook keeping the tools, and with tools registered inside
presence.
- Server typecheck; 155 `opencode/missions` tests incl.
missions-plugin/missions-reload; `build:missions`;
`test-missions-native.mjs` on 2.2.3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant