Child discovery now pages through the whole session list, so one pull can
take longer than the 15s watchdog interval. Two overlapping pulls each read
the store before either commits, so both appended the same newly found child
and the directory store ended up with duplicate rows. Keep one pull per
directory in flight, mirroring the status-poll guard.
The web server read XDG_CONFIG_HOME through a redundant typeof guard on a
value that is already string|undefined, which tripped the anti-slop lint on
its own new line and diverged from the VS Code helper. Both now read the
same way. The VS Code provider test also still hard-coded ~/.config/opencode,
so it silently stopped asserting whenever XDG_CONFIG_HOME was set; it now
uses the shared constant, like the web test already does.
dev-web-full.mjs is started by node and spawned bare `bun`, so on Windows
machines where npm exposes only bun.cmd both children died with ENOENT and
`dev:web:full` never started. It now uses the resolver dev-web-hmr.mjs got,
and hides the console window like the other Node-spawned children.
The web route accepts the OpenAI Chat Completions, OpenAI Responses and
Anthropic Messages adapters, but the module doc still described the write
path as OpenAI-compatible only. The VS Code doc already names all three.
The legacy `providers` block is deleted whole when its last entry migrates
to `provider`. Nothing covered the case where other legacy entries remain,
so a regression there would silently drop unrelated providers. Adds the
case to both the web server and VS Code parity suites.
A comment went to the active session tab, and when there was none it opened
a new one, even while the user was chatting in the sidebar. That is not how
the other capture flows behave. The comment now takes the same route as
Add to Context: the active session tab when one exists, otherwise the
sidebar, revealed if it is closed.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
A comment written with no session tab open opens one, and that panel knows
its directory long before it has loaded the session list and selected its
session. The webview filed the draft on the first snapshot that had a
directory, with the session key falling back to "draft" because no session
was current yet. The panel's composer reads the session's key, so the chip
never appeared, while the editor thread saw the draft in the store snapshot
and reported it attached.
A session panel now stamps its session on every comment it delivers, and
the webview waits until it actually shows that session before filing. The
sidebar files on its current session or an open new-session draft, and no
longer falls back to "draft" merely because nothing is selected yet. The
resolver is a pure module with tests.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
Both failures showed up as "Test timed out in 30000ms" followed by "Hook
timed out in 30000ms" on loaded PR runners, in files nobody had touched.
relay host-client: the scripted client dialed its WebSocket before awaiting
its WebCrypto setup and only then attached the open/message listeners. On a
loaded runner the loopback socket opened while key generation was still
queued on the threadpool, the open event found no listener, no hello was
sent, and the client waited forever. The socket is now dialed after the key
material is ready, in the same tick as the listeners. The fake relay also
terminates leftover sockets on stop so a failure is reported once, not
twice.
walkthrough routes: each test slept 20 ms and assumed the request had
reached the route by then. The tests now wait until the service has been
asked to generate one more time than before the request, and afterEach
closes idle keep-alive connections so server.close() cannot hang on a
response a failed test never received.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
* feat(chat): render code comments as cards instead of fenced text
* fix(vscode): route Add Comment to the active session editor panel
* fix(chat): persist queued inline comments and tighten file-chip path matching
* feat(vscode): comment on code from the editor
* fix(chat): keep attached context in the message and broadcast comment removal
* fix(vscode): hold every pending comment and gate both entry points on the workspace
* fix(vscode): let only the owning surface decide its comment threads
* fix(vscode): drop a comment removed while its delivery was still in flight
* test(vscode): cover the in-flight comment removal guard
* test(vscode): cover comment removal reaching every chat surface
* fix(vscode): give up on a comment the chat never confirmed holding
* fix(vscode): retract a comment everywhere before reporting it discarded
* fix(chat): preserve queued comment cards
* fix: preserve inline comment context across send paths
* fix(chat): preserve command routing with context
* fix(chat): keep unavailable actions on normal send path
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
OpenCode's command route accepts file parts only. The server queue was
attaching captured context as text parts to POST /session/:id/command, so
a slash command queued with a comment, quote, or PR diff was rejected with
400 and retried forever.
A command queued without context still takes the command route with its
files. One queued with context now takes the prompt route the way the
composer does: the command's template is expanded with its arguments, a
skill keeps its text and gets the explicit skill-invocation instruction,
and the context rides along.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
When the server delivered the last item of a session's queue it deleted
the session entry and broadcast the update with an empty directory. The
UI keys its projection by directory, could not build the key, and dropped
the event, so the delivered message stayed in the "Queued messages" card
until the next full hydration.
The server now remembers a session's directory beyond the emptying of its
queue so that broadcast names it. The UI additionally treats an empty
session without a directory as "this session's queue is done" for every
projection keyed under that session id, which covers clients talking to
an older server.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
Thanks for extending the existing IME handling to comment inputs and documenting the browser-event checks. We will also protect the annotation Escape handlers from cancelling composition.
Thanks for fixing the cramped table columns and covering the streaming-to-settled transition. The content-sized layout and horizontal scrolling look ready to merge.
Thanks to alvins82 for tracing the truncation failure and to bashrusakh for adding bounded recovery and error precedence. We will finish the explicit Resume behavior in the same batch.
Closes#3255
Co-authored-by: alvins82 <alvins82@gmail.com>
Thanks for refining the action placement. This keeps New chat available while idle and preserves Abort while a response is running. We will finish the repository-specific cleanup in the same batch.
* fix(docker): apply patch-package in the builder stage
The deps stage installs with --ignore-scripts, so the root postinstall never
runs and patches/ghostty-web+0.4.0.patch is never applied. The web bundle in
the Docker image therefore shipped an unpatched ghostty-web renderer.
Run patch-package explicitly in the builder stage, after the full source copy,
before building the web assets.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(docker): set a UTF-8 locale in the runtime image
The oven/bun base image ships with the POSIX locale, so bash readline in the
built-in terminal treated each byte of a multibyte character separately and
garbled the echo of pasted Unicode input.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Exports the release package list so release prep stages the same versioned files as the bump script.
Validates that CHANGELOG.md contains the target release section before preparing a release.
Updates the suggested release steps to commit only release files and push the tag explicitly.
Settings → Integrations offered install cards for the Claude Code and
Cursor provider plugins. They are gone: the section, its plugin catalog,
its own i18n module and tests, the settings search entries, the page
keywords, and the two sprite icons only it used. The page now holds the
built-in GitHub and Linear cards, so it is hidden in VS Code where neither
applies; its title and description live in the settings dictionaries.
Docs follow: the Integrations page in every locale now documents GitHub
(pointing at its own page) and Linear in full, the GitHub page names
Settings → Integrations as the place to connect and covers linking an
issue or PR to a message, and the Providers pages no longer promise Claude
or Cursor subscriptions.
Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
Queueing captured only the text and files. Context chips (inline comments,
terminal selections, browser annotations, PR comments and checks, quotes,
linked issue/PR/Linear references, pending synthetic parts) stayed in the
composer and only left with the next manual send, so a queued message the
server delivered went out without them and the chips rode an unrelated
message later.
A queued message now carries what the composer would have sent: the text
with its agent mention stripped and file mentions resolved into
attachments, the attached context as structured parts, and the skill
instruction derived from the text. The server delivers those parts in the
composer's order, the VS Code auto-send does the same, and editing a queued
message puts the chips and linked references back. A failed queue restores
the composer completely. Snapshots and broadcasts omit the captured
context like attachment payloads; a take returns it.
Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
OpenCode marks an MCP server failed when it does not come up at startup
or when a live connection drops, and never retries. A new managed plugin
reconnects those servers with a per-server backoff (1s doubling to a 30s
cap), wakes early on mcp.tools.changed, and stops on dispose. Only the
failed state is retried; disabled and auth states stay untouched.
The OPENCODE_CONFIG_CONTENT merge that agent-tool and system-prompt each
carried is now one shared helper so the three managed plugins compose.
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
The send write was fixed, but the message that send echoes back arrives with
model metadata and no effort, and its model matches the one the send just
saved. That is exactly when `shouldPreserveManualModelOverride` declines to
protect the selection, so the history branch ran with no variant and recorded
"no choice" over the user's explicit `Default`. Picking Default, sending, then
switching agent and back still put the settings default in the picker.
A message carrying no effort is not evidence that the session has none. The
history branch now keeps whatever the session already recorded when the message
carries no variant, and a concrete historical effort still replaces it.
The behavior test ran the real guard through a mock that returned a fixed
answer, so the failing branch had no coverage. It now calls the real function
unless a test opts out, and the new case fails without this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
A send carries no effort both when nothing was chosen and when the user picked
"Default", so `sendMessage` could not tell the two apart and recorded the raw
value, which clears the entry. Picking "Default", sending, then switching agent
and back put the settings default back in the picker — the shape of the bug
this branch set out to fix.
`materializeOpenDraftSession` already read the live selection to keep that
distinction on the draft path. Both paths now share `resolveVariantToRecord`,
which prefers `currentVariantSelection.override` while the live selection still
describes the agent and model being sent to, and falls back to the sent value
when it does not.
Three tests go through the real `sendMessage`; two of them fail without this
change. The existing ones seeded the record directly, which is why the send
path was never covered.
Also documents the `oc.chatInput.lastDraftTarget` record in the owning sync
documentation: its three `target` values, what a pre-`target` record and a
removed project fall back to, and why a chat scratch directory is not a project
target.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
Nothing covered ModelControls, which is where the effort restore decides
between "the user chose Default" and "nothing was found". Three cases: a
concrete effort in the session history is restored, history without an effort
records no choice, and a preserved manual override keeps a recorded Default.
The middle one fails on the previous commit's parent — it recorded `null` and
then `undefined` for the same restore.
Also drops the `fetchAll` binding GitView stopped using when the post-bootstrap
refresh moved to a forced `fetchStatus`; it was failing `bun run lint:ui`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
`commitVariantSelectionForModel` turned every `undefined` into an explicit
`Default` (`null`), but it serves two kinds of caller. The picker means "the
user chose Default"; the history and manual-override restores mean "nothing
was found". Restoring a session therefore recorded a choice nobody made, and
`resolveModelVariantSelection` collapsed that `null` back to `undefined`, so
the next restore recorded it again. Because an explicit `Default` outranks the
agent and settings defaults by design, the session latched onto `Default` and
the concrete effort its own history carried could not come back.
Move the decision to the callers: the four picker paths pass `variant ?? null`,
the restore paths pass their result through, and the resolver returns the
selection store's three states instead of two. The follow-up write in the
history restore goes with it — the apply above it already recorded the same
agent and model, and a second write could only disagree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
Removing a worktree archived its sessions one SDK call at a time and
then re-rendered the whole sidebar once per streamed session.updated
echo. On a worktree with 121 sessions that meant 14.8s of main-thread
work, 121 requests, and 328 localStorage writes.
- Add POST /api/openchamber/sessions/archive: validates a batch (max
500 ids, per-request archivedAt), archives sequentially, and reports
partial failures instead of dropping the batch. VS Code serves no
such route and answers 501; the shared UI then falls back to the
per-session path.
- Plan batches from the sessions this client actually holds, live
directory stores first, so worktree-only sessions still batch.
- Claim (id, archivedAt) pairs before the request and consume the
matching session.updated echoes, so the server's own confirmations
no longer fan out into 121 store publications. Runtime-scoped, TTL
30s, released on response or fallback; non-matching updates pass.
- Make the managed-chats persistence a real trailing debounce instead
of a 50ms throttle, so a burst of publications coalesces into one
localStorage write.
Benchmark (121 sessions, production build, real Chrome): 14785ms ->
~1030ms, long tasks 100 -> 1, global store publications 236 -> 1,
persistence writes 328 -> 3.