Fixes
[CODAGT-319](https://linear.app/codercom/issue/CODAGT-319/support-prompt-history-cycling-with-up-arrow).
Pressing the up-arrow key in the agent chat composer now cycles through
prior user prompts in the chat (terminal/Discord/iTerm2 style).
Down-arrow steps forward, Escape exits cycling and restores the
in-progress draft. Cycling is non-destructive: the per-message hover
**Edit** button is still the destructive truncate-and-edit path.
Replaces the previous up-arrow shortcut that immediately entered
destructive history-edit mode (and which had a regression where the
composer rendered as "editing" with an empty input box).
## Behaviour
- **Up** when composer is empty: snapshot the (empty) draft and load the
most recent user prompt; subsequent **Up** presses load older prompts.
Clamp at oldest, no wrap.
- **Up** while non-empty and not yet cycling: pass through (caret
movement preserved).
- Once cycling, **Up / Down** are intercepted unconditionally because
the cycle text fully replaces editor contents. Exit explicitly via
Escape, by sending, or by typing.
- **Down** while cycling: load the next-newer prompt, or restore the
saved draft when past newest.
- **Escape** while cycling: exit cycle and restore the saved draft. This
also applies during streaming; the same keypress is stopped before it
reaches the interrupt handler, and a second Escape interrupts as before.
- **Typing / paste / drop / attach / send / `remountKey` change**: exit
cycle mode and clear the snapshot.
- Cycling is suppressed while `isEditingHistoryMessage`,
`editingQueuedMessageID !== null`, or the input is `disabled` /
`isLoading`.
- Empty `userPromptHistory` makes Up a no-op (no destructive fallback).
## Out of scope (filed as follow-ups if needed)
- Restoring file-reference chips / attachments on cycled messages — v1
cycles plain text only, matching the existing per-message destructive
Edit's `text` payload.
- `^N` / `^P` keybindings (per Cian's note in the Linear thread).
- Per-user "enable/disable history cycling" preference (per Rowan's
note).
- Cross-chat history; cycling is per-chat.
## Tests
New Storybook play functions in `AgentChatInput.stories.tsx`:
- `PromptHistoryCycling` — Up cycles older, clamps at oldest; Down
returns to newer / draft; Escape restores draft.
- `PromptHistoryCyclingExitsOnTyping` — typing exits cycle mode;
subsequent Up snapshots the fresh empty draft and Down restores it.
- `NoPromptHistoryUpArrowIsNoOp` — empty history → Up is a no-op.
- `PromptHistorySuppressedWhileEditingHistoryMessage` — cycling does not
engage while history-editing.
- `PromptHistorySuppressedWhileDisabled` — cycling does not engage while
disabled.
- `PromptHistorySuppressedWhileLoading` — cycling does not engage while
loading.
## Implementation notes
Also rewrites `useImperativeHandle` to delegate to `internalRef.current`
lazily on every call instead of capturing it eagerly at factory time.
The old code crashed when methods were called after a remount because
the captured ref was stale; the new wrapper sees the current Lexical
instance. Behavior changes from throw-on-null to silent no-op, which
matches every other consumer of `ChatMessageInputRef`.
Verified locally:
```
pnpm format
pnpm check
pnpm test:storybook src/pages/AgentsPage/components/AgentChatInput.stories.tsx # 41 passed
pnpm lint
```
## Manual UAT
A 13-case manual UAT covering cycle entry/exit, clamping, draft
restoration, no-history no-op, suppression while editing a history
message, and the send-button enable state — all PASS. Spec lives at the
deleted artifact branch; happy to re-attach if reviewers want it.
<details>
<summary>Implementation plan and decision log</summary>
The complete plan that drove this PR, including design alternatives
considered and edge cases:
```md
# CODAGT-319 — Up-arrow prompt history cycling
## Goal
Pressing the up-arrow key in the agent chat composer should cycle through the
user's previously-sent prompts in the current chat, terminal/Discord/iTerm2
style. Down-arrow steps forward; Escape exits cycle mode and restores the
in-progress draft. Cycling is non-destructive — it only populates the
composer with text the user can choose to resend, edit, or discard.
## Today's behaviour (and the regression)
- `ChatMessageInput` is a Lexical-based plain-text editor used inside `AgentChatInput.tsx`.
- `AgentChatInput.tsx` already wires an `ArrowUp` handler. When the composer is empty and not already editing, it calls `onEditLastUserMessage`.
- `onEditLastUserMessage` puts the user into a destructive "edit history" mode that warns "Editing will delete all subsequent messages and restart the conversation here.".
- Danielle's regression report ("shows me as editing but the input box is empty") indicates the destructive flow has a bug in addition to being the wrong UX for the request. We're replacing that path on the up-arrow, not patching it. The destructive edit remains accessible via the per-message hover Edit button.
## Design
### Behaviour
- Up when composer is empty: snapshot the (empty) draft and load the most recent user prompt. Subsequent Up loads older prompts, clamping at oldest. No wrap.
- Up while non-empty and not yet cycling: pass through. Matches existing gating.
- Once cycling, Up/Down are intercepted unconditionally. Exit via Escape, send, or typing.
- Down while cycling: next-newer or restore draft past newest.
- Escape while cycling: restore draft. During streaming, stop propagation so the same keypress does not interrupt; a second Escape interrupts as before.
- Typing/paste/drop/attach/send: exit cycle.
- Suppressed while isEditingHistoryMessage, editingQueuedMessageID !== null, or disabled/isLoading.
- No history => Up is a no-op.
### State
Local to `AgentChatInput.tsx`:
- `cycleIndex: number | null` — null means not cycling. 0 = newest user prompt.
- `cycleSavedDraft: string | null` — text restored on dismiss.
No localStorage persistence — refresh is a clean exit signal and the chat already has history server-side.
### Wiring
- New prop `userPromptHistory: readonly string[]` on `AgentChatInput`, newest-first.
- Removed `onEditLastUserMessage` prop entirely (its single call-site is being replaced). Removed dead `onEditUserMessage` prop on `ChatPageInput` (no longer needed since the destructive last-message shortcut is gone; the destructive Edit button uses a separate prop chain through `ChatPageTimeline`).
- `ChatPageContent.tsx` derives `userPromptHistory` from existing message store, filtered to `role === "user"` with non-empty `getEditableUserMessagePayload(message).text.trim()`.
### Reset triggers
`cycleIndex` and `cycleSavedDraft` reset on:
1. New `remountKey` (chat change, edit start/cancel).
2. Successful send.
3. Paste, drop, file attach.
4. User typing (detected via `handleContentChange` by comparing the incoming content to `currentCycleValueRef`, the last value applied programmatically).
### Out of scope
- Chip/attachment cycling.
- ^N/^P (Cian's note).
- Per-user toggle (Rowan's note).
- Cross-chat history.
```
</details>
---
> [!NOTE]
> This PR was created on behalf of @ibetitsmike by Coder Agents.
---------
Co-authored-by: Coder Agents <noreply@coder.com>
Adds an `[!IMPORTANT]` callout under the SCIM heading in the OIDC auth
docs noting that Coder's SCIM 2.0 implementation is not a fully
certified or guaranteed implementation of the spec. It covers common
provisioning/deprovisioning flows with major IdPs (Okta, Entra ID, etc.)
but specific attributes, endpoints, or behaviors may not be supported
and may change between releases.
This matches what we say in conversations with prospects and avoids
setting an expectation we can't always meet. Background: #15830 (current
implementation is an MVP scoped to Okta cloud; `PATCH` is not RFC 7644
compliant; user updates only change status, not groups/orgs/roles).
Companion PR: coder/coder.com#738 removes the SCIM row from the pricing
comparison.
> Generated with [Coder Agents](https://coder.com/agents)
Closes#24091
Adds
`TestDeleteChatDebugDataAfterMessageIDStepLevelFieldBoundariesAndNulls`,
which complements the existing triggered-runs test for
`DeleteChatDebugDataAfterMessageID` with boundary and NULL coverage for
step-level message IDs.
The existing
`TestDeleteChatDebugDataAfterMessageIDIncludesTriggeredRuns` already
exercises the `step.assistant_message_id > @message_id` deletion path.
This test focuses on:
- Strict greater-than behavior at the cutoff for assistant and
history-tip step message IDs.
- Step-level assistant and history-tip message ID combinations.
- SQL NULL behavior for step-level message IDs.
- A mixed-step run where one matching step deletes the whole run and
cascades every step.
| Scenario | assistant_message_id | history_tip_message_id | Expected |
|----------|----------------------|------------------------|----------|
| Assistant above cutoff, history tip NULL | cutoff + 5 | NULL | Deleted
|
| Assistant above cutoff, history tip below cutoff | cutoff + 20 |
cutoff - 3 | Deleted |
| Assistant below cutoff, history tip NULL | cutoff - 3 | NULL |
Preserved |
| Assistant at cutoff boundary, history tip NULL | cutoff | NULL |
Preserved |
| Assistant NULL, history tip above cutoff | NULL | cutoff + 2 | Deleted
|
| Assistant NULL, history tip at cutoff boundary | NULL | cutoff |
Preserved |
| Both step message IDs NULL | NULL | NULL | Preserved |
> Generated by Coder Agents
<details><summary>Review notes</summary>
- Run-level message IDs are below the cutoff to isolate step-level
selection.
- The assistant-above-cutoff scenario includes a second nonmatching step
to cover mixed-step deletion.
- The test uses unique model and chat names for isolation.
- `go test -v ./coderd/database -run
TestDeleteChatDebugDataAfterMessageID -count=1` passes.
</details>
Anthropic task name responses can include valid JSON followed by a
closing fence or extra text, which made `json.Unmarshal` fail with
trailing-character errors and forced fallback naming.
This updates task name JSON extraction to accept the first JSON value
after optional fences and adds regression coverage for fenced and bare
JSON with trailing content.
Anthropic rejects inline images over 5,242,880 bytes, but our upload
endpoint accepts images up to 10 MiB — so 5–10 MiB images were
reaching the provider and failing. This adds two layers of
protection: the browser resizes oversized images before upload, and
the server rejects any that still slip through before an upstream
request is issued.
Client-side resizing uses `createImageBitmap` with
`resizeWidth`/`resizeHeight` to clamp the decoded bitmap at decode
time, then iteratively shrinks on an `OffscreenCanvas` (falling back
to `HTMLCanvasElement`) until the output fits the applicable budget.
Anthropic (and Bedrock-hosted Claude — fantasy's bedrock provider is
a thin wrapper around the Anthropic client) uses a ~5 MiB budget;
other providers use a ~10 MiB budget to stay under the server cap.
Doing the resize in the browser avoids decoding attacker-controlled
image bytes in `coderd` (image-bomb DoS surface).
Server-side, `chatFileResolver` now takes a provider string and
looks up the inline-image cap via a new
`chatprovider.InlineImageByteCap`
helper; oversized `image/*` files for capped providers are rejected
with a pre-classified `chaterror` before the SDK call. The backstop
fires for older clients, direct API callers, or any image that was
committed to the composer before the user switched to a stricter
provider.
Attachments commit to composer state synchronously with a new
`"processing"` `UploadState` so paste+Enter can't dispatch before
the resize finishes; the `"uploading"` send gate now covers both
states. Dismissed-while-resizing attachments are tracked in a
`WeakSet` so a late swap can't resurrect a removed file.
Closes CODAGT-215
Closes#24090
Enhances the existing `TestFinalizeStaleChatDebugRows` test with three
missing coverage areas:
1. **Error JSON preservation**: verifies pre-existing error payloads are
not overwritten by finalization
2. **Timestamp correctness**: verifies `updated_at` and `finished_at`
match the `@now` parameter across all finalized row paths
3. **Null error preservation**: verifies finalized steps that had no
error keep a null error column
No production code changed. Test passes against Postgres.
> 🤖 Generated by Coder Agents
<details><summary>Review notes</summary>
- Enhances existing test rather than adding a new one, the existing test
was the right place
- Covers stale, orphaned, and cascade finalization timestamp assertions
- Preserves both pre-existing error JSON and null error values during
finalization
</details>
## Description
Adds automatic key failover for passthrough routes for the Anthropic and OpenAI providers. A new `keyFailoverTransport` wraps the reverse-proxy transport: centralized requests walk the configured key pool and retry with the next key on key-specific failures (401/403/429), reusing the same key-marking semantics as the bridged routes.
BYOK passthrough requests run as a single attempt with no failover.
## Changes
- New `keypool.KeyFailoverConfig` carrying the `Pool` to walk and the provider-specific closures (`IsBYOK`, `InjectAuthKey`, `MarkKey`, `BuildExhaustedResponse`).
- New `keypool.NewKeyFailoverTransport`: wraps an inner `http.RoundTripper`. Returns `inner` unchanged when `Pool` is nil, otherwise produces a transport that buffers the request body once, walks the pool per request, and replays each attempt with the next key.
- New `Provider.KeyFailoverConfig(logger)` interface method. Anthropic injects `X-Api-Key`; OpenAI injects `Authorization: Bearer ...`; Copilot returns an empty config.
- `passthrough.go` wires `NewKeyFailoverTransport` around the existing apidump middleware, so every retry attempt is recorded.
## Related Issues
Related to: https://github.com/coder/internal/issues/1446
Related to: https://linear.app/codercom/issue/AIGOV-197/aibridge-automatic-key-failover-for-bridged-and-passthrough-routes
## Follow-up PRs
- Remove dead `Provider.InjectAuthHeader` method now that all auth is applied per-attempt by `KeyFailoverTransport`.
- Bedrock multi-key support.
- Refactor provider vs interceptor config separation.
- Record the actually-used key in the interception credential hint after failover.
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
This change uses separate http clients/transports in TestValidateToken
subtests. Previously parallel subtests of TestValidateToken shared
a http.DefaultTransport. When one subtest's httptest.Server.Close() ran in
t.Cleanup, it called http.DefaultTransport.CloseIdleConnections, which
could interrupt connection(s) used in another subtest.
## Description
Adds automatic key failover for centralized OpenAI provider, covering both chat completions and responses APIs. Same shape as the Anthropic PR: each upstream call walks the configured key pool, keys are marked **temporary** on 429 (with cooldown from `Retry-After`) and **permanent** on 401/403. Each agentic-loop iteration gets its own fresh walker so a tool-call continuation can fail over independently of the initial request.
BYOK is unchanged: BYOK requests run as a single attempt with no failover.
## Changes
- `config.OpenAI` carries a `KeyPool`. `Key` remains for BYOK Authorization Bearer set per interception.
- Chat completions blocking interceptor: walks the pool via `newChatCompletionWithKeyFailover`, marks keys on key-specific failures, returns on first success or non-failover error.
- Chat completions streaming interceptor: per-iteration walker. Pre-stream failures fail over to the next key; mid-stream errors are relayed as SSE events.
- Responses blocking interceptor: extracts `newResponseWithKeyFailover` parallel to chatcompletions.
- Responses streaming interceptor: per-iteration walker, retains the existing buffer-then-forward design.
## Related Issues
Related to: https://github.com/coder/internal/issues/1446
Related to: https://linear.app/codercom/issue/AIGOV-197/aibridge-automatic-key-failover-for-bridged-and-passthrough-routes
## Follow-up PRs
- Bedrock multi-key support.
- Refactor provider vs interceptor config separation.
- Record the actually-used key in the interception credential hint after failover.
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
## Description
Adds automatic key failover for centralized Anthropic provider. When a key pool is configured, each upstream call walks the pool and tries keys in order until one succeeds or the pool is exhausted. Keys are marked **temporary** on 429 (with cooldown from `Retry-After`) and **permanent** on 401/403. Errors that aren't key-specific don't trigger failover. Each agentic-loop iteration gets its own fresh walker, so a tool-call continuation can fail over independently of the initial request.
BYOK is unchanged: BYOK requests run as a single attempt with no failover.
## Changes
- `config.Anthropic` carries a `KeyPool`. `Key` remains for BYOK X-Api-Key set per interception.
- Blocking interceptor: walks the pool, marks keys on key-specific failures, returns on first success or non-failover error.
- Streaming interceptor: per-iteration walker. Pre-stream failures fail over to the next key; mid-stream errors are relayed as SSE events.
- New `keypool` error types: `TransientExhaustionError` (carries soonest cooldown) and `ErrPermanentExhaustion`. Replace the prior `ErrAllKeysExhausted`.
- Error responses now consistently include the outer `"type": "error"` field.
## Related Issues
Related to: https://github.com/coder/internal/issues/1446
Related to: https://linear.app/codercom/issue/AIGOV-197/aibridge-automatic-key-failover-for-bridged-and-passthrough-routes
## Follow-up PRs
- Bedrock multi-key support.
- Refactor provider vs interceptor config separation.
- Record the actually-used key in the interception credential hint after failover.
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
The async title-generation and turn-summary goroutines launched from
processChat run autocommit UPDATEs on the chat row after finishActiveChat
has set the chat to pending and signalWake has fired. If the row lock
from one of those UPDATEs is held while acquireLoop's processOnce runs,
AcquireChats's FOR UPDATE SKIP LOCKED skips the freshly-pending chat and
returns no rows. The wake is then consumed with no acquisition, and the
chat sits in pending until the next acquireTicker (default 1s).
Wake again after each UPDATE commits. The second wake covers the race
window without changing the transaction semantics.
Closescoder/internal#1500
## Summary
- switch the Ubuntu 26.04 dogfood image to Docker's jammy apt repository
so Docker 27 remains available
- pin `docker-ce` and `docker-ce-cli` to the Docker 27 line and keep
`containerd.io` pinned to `1.7.23-1`
- fold the containerd pin into the Docker preferences file, remove the
duplicate containerd preferences file, and hold the installed Docker
packages in the image
## Notes
Docker 28+ requires `containerd.io >= 1.7.27`, but sysbox /
Docker-in-Docker currently requires `containerd.io=1.7.23-1`, so the
image needs the older Docker 27 packages from the jammy repo.
## Testing
- Not run locally; verified the branch diff only.
Adds a persisted, draggable left sidebar width for the agents page. The
resize handle uses the same pointer-capture resize technique as the
existing right panel and clamps the expanded sidebar between 240px and
`min(520px, 50vw)`.
Updates the agents page skeleton to read the same stored sidebar width
and adds Storybook interaction coverage for resize clamping and
persistence.
Polishes the advisor tool card so the header uses a clearer lightbulb icon, inline pill metadata, wrapped/clamped question text, and a separate advice pill before the rendered response.
Adds Storybook coverage for a long advice plus 1.8k-character question peak state, including collapse/expand behavior.
Refs https://linear.app/codercom/issue/CODAGT-322/improve-advisor-icon-and-duplicate-loading-ui
<details>
<summary>Coder Agents disclosure</summary>
This PR was generated by Coder Agents.
</details>
Skips `TestExploreChatSendMessageCannotMutateMCPSnapshot` while the
chatd redesign is in flight. The test exposes a self-interrupt race in
`processChat`'s control-pubsub subscriber that is structurally fixed by
the redesign in #24444; skipping until then matches the existing
`TestSubscribeRelayEstablishedMidStream` skip in
`enterprise/coderd/x/chatd/chatd_test.go`.
Relates to https://github.com/coder/internal/issues/1493.
This adds Homebrew and mise to the Ubuntu dogfood images and makes mise
shims win
PATH resolution for the `coder` user. It installs Homebrew in
`/home/linuxbrew/.linuxbrew`, installs the latest mise release
(`v2026.4.19`) via
its verified GitHub release artifact, exposes mise at
`/usr/local/bin/mise`, wires
`HOMEBREW_*` and `MISE_DATA_DIR`, and adds build-time checks for both
tools. The
mise executable target lives in writable `/opt/mise/bin` so `mise
self-update`
can replace it as the `coder` user. This also adds `libc6-dev` to the Go
utility
stages so the existing CGO-backed tool installs keep building on newer
Ubuntu
bases.
The dogfood template now mounts a dedicated `/home/linuxbrew/` Docker
volume in
addition to `/home/coder/`. Fresh volumes are seeded from the
image-baked
Homebrew tree on first mount, while user-installed formulae persist
across
workspace container recreation.
I revalidated the bootstrap on jammy and resolute base images with fresh
mounted
`/home/coder` and `/home/linuxbrew` volumes. In those runs, `brew
install hello`
succeeded, `mise doctor` reported no PATH or activation problems, `mise
self-update --force --yes --no-plugins 2026.4.19` succeeded as `coder`,
and
`mise use --global github:BurntSushi/ripgrep@14.1.1` moved `rg`
resolution to
the mise shim after container recreation.
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: add `mise` and Homebrew to the dogfood Ubuntu images with
mise-first PATH
## Goal
- Make both dogfood Ubuntu images ship `brew` and `mise`.
- Ensure `mise doctor` does **not** complain about activation/PATH
ordering in the shell entrypoints we support.
- Keep the implementation robust against the persistent `/home/coder`
volume used by the dogfood template.
## Verified context
- The relevant image definitions are:
- `dogfood/coder/ubuntu-22.04/Dockerfile`
- `dogfood/coder/ubuntu-26.04/Dockerfile`
- The dogfood template mounts a persistent home volume at `/home/coder/`
in
`dogfood/coder/main.tf:840-843`, so required image-baked state should
not
live only under `/home/coder`.
- Both Dockerfiles already manipulate PATH in multiple places:
- Go appended early (`:26`)
- Cargo prepended (`ubuntu-26.04/Dockerfile:202-206`; mirrored in 22.04)
- Node via nvm prepended (`ubuntu-26.04/Dockerfile:245-255`; mirrored in
22.04)
- Final `coder` PATH prepends `/home/coder/go/bin`
(`ubuntu-26.04/Dockerfile:348-358`; mirrored in 22.04)
- `COPY files /` is already present in both Dockerfiles, so adding new
global
shell-init files is possible without Terraform changes.
- `scripts/lib.sh:94-124` uses `command -v` for dependency detection, so
PATH
order is the practical repo-level behavior we need to control.
- `.github/workflows/dogfood.yaml:99-126` builds both Ubuntu variants,
and the
22.04 image is still tagged `latest`, so both Dockerfiles must be
updated in
the same change.
## Recommended implementation
### Phase 1 — Bootstrap Homebrew and `mise` in both Ubuntu Dockerfiles
1. Update both Dockerfiles in parallel:
- `dogfood/coder/ubuntu-22.04/Dockerfile`
- `dogfood/coder/ubuntu-26.04/Dockerfile`
2. Add the minimum explicit Homebrew prerequisites that are missing from
the
current apt package set.
- The images already install `build-essential`, `curl`, `file`, and
`git`.
- Audit whether `procps` must be added explicitly for Homebrew’s Linux
requirements.
3. Install Homebrew in the supported Linux prefix:
- Prefix: `/home/linuxbrew/.linuxbrew`
- Keep the install/build logic in the Dockerfile, before `USER coder`.
- Make the resulting prefix writable by `coder` before switching users.
Prefer the smallest-diff approach that leaves `brew install ...` usable
as
`coder`.
4. Install `mise` to a stable image-owned path instead of relying on
`~/.local/bin`:
- Preferred binary path: `/usr/local/bin/mise`
- Use a pinned installation method that fits the current Dockerfile
style
(versioned release asset or otherwise explicitly pinned installer path).
5. Add defensive build-time sanity checks near the install steps so the
image
fails early if assumptions are wrong:
- `test -x /usr/local/bin/mise`
- `test -x /home/linuxbrew/.linuxbrew/bin/brew`
- `brew --version`
- `mise --version`
**Quality gate:** both Dockerfiles build locally, and the resulting
container can
run `brew --version` and `mise --version` as `coder`.
### Phase 2 — Make `mise` win PATH resolution by default
1. After `USER coder` in both Dockerfiles, define stable environment
variables
for the final shell/runtime behavior:
- `HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew`
- `MISE_DATA_DIR=/home/coder/.local/share/mise`
- `MISE_ACTIVATE_AGGRESSIVE=1` only if later shell activation proves
necessary
2. Replace the final PATH composition so it resolves in this order:
1. `mise` shims
2. Homebrew `bin`/`sbin`
3. Existing `/home/coder/go/bin`
4. Existing image/system PATH
3. Keep the current Go/Rust/Node setup intact aside from the final PATH
ordering. Add a short Dockerfile comment explaining that `mise` shims
must be
first so `mise doctor` and `command -v` resolve `mise`-managed tools
ahead of
Homebrew/system binaries.
4. Do **not** rely on image-baked `mise` state under `/home/coder` for
the
initial implementation. The goal here is binary availability and path
precedence, not preinstalling shared `mise` toolchains.
**Quality gate:** in a fresh container as `coder`, `echo "$PATH"` shows
`mise` shims before Homebrew, and `mise doctor`/`mise doctor path` show
no PATH
or activation problem in the tested shell entrypoints.
### Phase 3 — Add shell-init hardening only if smoke tests prove it is
needed
1. Start with the Dockerfile `ENV PATH` solution as the default
behavior.
2. If dogfooding shows that supported login shells still need shell
integration
beyond the final `ENV PATH`, add minimal global shell-init files under:
- `dogfood/coder/ubuntu-22.04/files/etc/profile.d/`
- `dogfood/coder/ubuntu-26.04/files/etc/profile.d/`
3. If these files are needed, keep them narrowly scoped:
- a Homebrew file that exports/evals `brew shellenv`
- a `mise` file that only reinforces the intended shims-first behavior
4. Avoid touching per-user dotfiles in `/home/coder`; they are the wrong
place
for required image behavior because of the persistent home volume.
**Quality gate:** if profile.d files are added, login-shell smoke tests
pass and
we do not introduce new PATH-order regressions versus the
Dockerfile-only path.
## Acceptance criteria
- Both Ubuntu dogfood Dockerfiles are updated in one change and still
build.
- `brew` is installed in `/home/linuxbrew/.linuxbrew` and is usable as
`coder`.
- `mise` is installed at `/usr/local/bin/mise` and is usable as `coder`.
- `mise doctor` does not report an activation/PATH-ordering problem in
the
shell entrypoints we verify.
- Final PATH precedence is:
1. `mise` shims
2. Homebrew `bin`/`sbin`
3. existing user/tool paths
4. system paths
- Existing dogfood workflows still work for Go/Rust/Node tooling after
the PATH
change.
- The change passes the dogfood image CI path in
`.github/workflows/dogfood.yaml`.
## Dogfooding and verification
1. Build both images locally:
- `dogfood/coder/ubuntu-22.04`
- `dogfood/coder/ubuntu-26.04`
2. Run each image with an empty mounted home volume at `/home/coder` to
mimic
the actual dogfood runtime constraint instead of only testing the
image’s
baked filesystem.
3. Capture a short terminal recording and screenshots for each variant
showing:
- `brew --prefix`
- `brew --version`
- `mise --version`
- `echo "$PATH"`
- `mise doctor`
- `mise doctor path`
4. Verify at least one login-shell path and one non-login-shell path, so
we can
tell whether Dockerfile `ENV PATH` is sufficient or whether
`/etc/profile.d`
hardening is required.
5. Add one tool-resolution smoke test that proves `mise` wins when
configured:
- install/use a small `mise`-managed runtime as `coder`
- run `which -a <tool>`
- run `<tool> --version`
6. Verify existing image behavior did not regress:
- `go version`
- `node --version`
- any other must-have image tools that were already on PATH
7. Preserve the artifacts from dogfooding for review:
- screenshots attached to the change summary
- a short screen recording (or terminal recording) covering the smoke
test
## Risks and decision points
- **Homebrew ownership model:** installing Homebrew during `docker
build` is not
enough by itself; the prefix must end up writable for `coder`.
- **Scope control:** the initial change should solve `mise doctor` by
fixing PATH
precedence, not by introducing a larger `mise`-managed tool bootstrap.
- **Shell-init uncertainty:** if the dogfood terminal entrypoints do not
source
`/etc/profile`, a Dockerfile `ENV PATH` fix may be sufficient and
profile.d
may be unnecessary. This should be decided by smoke tests, not by
assumption.
- **Persistent home behavior:** avoid any required implementation detail
that
only works if fresh volumes copy image-baked `/home/coder` contents.
<details>
<summary>Why this is the lowest-risk path</summary>
This plan keeps the initial implementation focused on the user’s stated
goal:
install Homebrew and `mise`, then guarantee that `mise`-controlled paths
win so
`mise doctor` stays quiet.
The main repo-specific constraint is the persistent `/home/coder`
volume. That
pushes required binaries and ownership-sensitive state out of
`/home/coder`
where possible, and it argues against relying on user dotfiles for
required
image behavior.
Starting with Dockerfile-level install steps plus a final PATH reorder
keeps the
diff small, makes behavior consistent across shells, and gives us a
clean place
to add shell-init hardening only if the smoke tests prove it is
necessary.
</details>
</details>
---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`openai:gpt-5.5` • Thinking: `xhigh`_
`launchHeartbeat` could miss a stale-threshold update during startup if
`SetStaleAfter` ran after the heartbeat ticker was created but before
the goroutine subscribed to `thresholdChan`. In that case, the heartbeat
kept the old interval until a future tick, and the mock-clock test could
time out waiting for `Ticker.Reset` without advancing time.
Subscribe to `thresholdChan` before reading the heartbeat interval so
the channel consistently invalidates the interval. The regression test
now changes the threshold while ticker creation is trapped, making the
startup race deterministic.
Closes https://github.com/coder/internal/issues/1513
`TestAdvisorChainMode_SnapshotKeepsFullHistory` was using the generic
active chatd test server, which leaves periodic pending-chat polling
enabled. That made the test inconsistent with the other OpenAI Responses
API tests and allowed stale pending pubsub notifications to interrupt
the second turn before the advisor request was observed.
Use the existing OpenAI Responses test server helper so pending-chat
acquisition is delayed and the test only starts processing after the
SendMessage pending notification has been published.
Closes https://github.com/coder/internal/issues/1510
## Summary
When a workspace build fails because the user is over their group quota,
the chat tools currently surface the failure as a bare `"workspace build
failed: insufficient quota"` string with no machine-readable error code
and no visibility into the user's current usage. Agents and the UI
cannot distinguish quota failures from any other Terraform error, so
users see an opaque message and have no clear path to recovery.
This PR tags quota failures with a typed error code at the source and
propagates it through the chat tool layer so callers can react to it
explicitly.
Relates to CODAGT-20
## Changes
**Provisioner runner**
- Add `InsufficientQuotaErrorCode = "INSUFFICIENT_QUOTA"` and set it
explicitly at the `commitQuota` failure site via a new
`failedWorkspaceBuildfCode` helper, so `provisioner_jobs.error_code` is
populated only on the genuine quota path. The substring matcher used for
externally produced sentinels (e.g. `"missing parameter"`, `"required
template variables"`) is intentionally not extended; provider errors
that happen to mention "insufficient quota" stay classified as generic
build failures.
**SDK and API contract**
- Add `JobErrorCodeInsufficientQuota` and a
`JobIsInsufficientQuotaErrorCode` helper to `codersdk`.
- Extend the swagger `enums` tag on `ProvisionerJob.ErrorCode` to
include `INSUFFICIENT_QUOTA`.
- Regenerate `coderd/apidoc`, `docs/reference/api/*`, and
`site/src/api/typesGenerated.ts`.
**chattool create_workspace / start_workspace**
- `waitForBuild` now returns a typed `*workspaceBuildError` carrying
both the message and the `JobErrorCode`, instead of a bare error string.
- New `quotaerror.go` introduces a structured `quotaErrorResult` (with
`error_code`, `title`, `message`, `build_id`, and optional `quota`) and
a best-effort `workspaceQuotaDetails` lookup that wraps owner
authorization internally and fetches `credits_consumed` and `budget`
from the database. Quota lookup failures (including authorization
failures) never block the failure payload.
- On quota-coded build failures, both `create_workspace` and
`start_workspace` now return the structured response (with the recovery
guidance inlined into `message`) instead of the bare `"insufficient
quota"` string. This applies to all three failure paths: post-creation,
an in-progress existing build, and a freshly triggered start build.
Non-quota build failures continue to use the existing
`buildToolResponse` / `newBuildError` path.
- Owner authorization is wrapped only on the call sites that need it
(the `CreateFn` and `StartFn` invocations and the quota-detail lookup),
so idempotent fast paths (already running, already in progress,
existing-workspace early returns) do not pay for an extra RBAC
round-trip or fail when role lookup is transient.
## Out of scope
- No changes to quota math, allowances, or bypass behavior.
- No automatic retries.
- No new quota-inspection tools and no changes to MCP
`coder_create_workspace` (which returns immediately and never observed
the build outcome here).
- No frontend UI changes; those will land in a follow-up PR that
consumes the new `INSUFFICIENT_QUOTA` code.
Previously, the `CreateWorkspaceBuild` toolsdk tests only exercised a
start where the workspace's prior template version was also the
template's active version, so they did not prove that a plain start
keeps using the previously built version.
Replace that tautological coverage with an isolated fixture that
advances the template's active version and asserts a start without
`TemplateVersionID` still reuses the prior build's version.
Reduces the beta badge size in the Coder Agents UI from `sm` to `xs` for
better visual balance with the logo.
## Changes
- Added `xs` size variant to `FeatureStageBadge`, styled to match the
existing `Badge` component's `xs` variant (`text-2xs`, `h-[18px]`,
`border-0`, `rounded`)
- Updated both usages in `AgentPageHeader` (mobile) and `AgentsSidebar`
(desktop) from `size="sm"` to `size="xs"`
- Added `ExtraSmallBeta` Storybook story for the new size
> Generated by Coder Agents
This pull-request looks at all (most) of our instances of `const styles
= { ... }` and attempts to smooth them down into the minimum viable
Tailwind equivalent 🙂
PromoteQueued now branches on chat status: synth tool results before
the user message on requires_action, deferred reorder + Waiting on
running so the worker's persist+auto-promote keeps partial output.
Stale heartbeat falls through to the synchronous path; GetStaleChats
picks up Waiting+queue to recover post-cleanup-crash. Endpoint
returns 202.
Closes CODAGT-119
Persists the agent-generated turn-end summary on `chats` and shows it as
the Agents sidebar subtitle when present, falling back to the model
name. Errors still take precedence.
> Mux is acting on Mike's behalf.
## What changes
**Storage.** New nullable `last_turn_summary` column on `chats`
(migration `000486`). New `UpdateChatLastTurnSummary` query normalizes
blank/whitespace input to `NULL`, preserves `updated_at` (so the chat
does not jump to the top of the sidebar on summary writes), and uses an
`expected_updated_at` stale-write guard so an older async summary cannot
overwrite a newer turn.
**Backend.** `coderd/x/chatd/chatd.go` decouples summary generation from
webpush. Generated summaries persist for completed parent turns even
when webpush is unconfigured or has no subscriptions. The same generated
text is reused as the webpush body when webpush is configured, so the
summary model is not called twice. Generic fallback push text is no
longer persisted; it clears any stale summary instead.
Error/interrupt/pending-action terminal paths clear `last_turn_summary`
for the latest turn.
**Frontend.** `AgentsSidebar.tsx` subtitle priority is now `errorReason
|| lastTurnSummary || modelName`, normalized via the existing
`asNonEmptyString` helper from `blockUtils.ts`.
## Tests
- `TestUpdateChatLastTurnSummary` (database): success,
whitespace-to-NULL, stale guard rejects, `updated_at` preserved.
- `TestUpdateLastTurnSummaryRejectsStaleWrites` (chatd internal): direct
stale-`expected_updated_at` test.
- `TestSuccessfulChatPersistsTurnSummaryWithoutWebPush`: persistence
works without webpush subscriptions.
- `TestSuccessfulChatSendsWebPushWithSummary`: same generated text
drives both DB and push body.
-
`TestSuccessfulChatSendsWebPushFallbackWithoutSummaryForEmptyAssistantText`:
fallback text is not persisted.
- `TestErroredChatClearsLastTurnSummaryAndSendsWebPush`: error path
clears the field.
- `TestInterruptChatDoesNotSendWebPushNotification`: interrupt path
clears the field, no push fires.
- `AgentsSidebar.test.tsx`: subtitle priority for summary-present,
error-wins, no-summary fallback, whitespace fallback.
- `AgentsSidebar.stories.tsx`: `ChatWithTurnSummary` and
`ChatWithTurnSummaryAndError`.
## Notes
- No backfill. Existing chats keep showing the model name until their
next turn completes.
- Parent chats only in this iteration; the field is rendered on any
`Chat` if a future change extends generation to children.
- Decoupling generation from webpush adds quickgen model calls for
completed parent turns that previously skipped generation when no
subscriptions existed. Existing parent-only, assistant-text-present,
`PushSummaryModel` configured, and bounded-timeout gates keep this
behavior bounded.
Add a new Quickstart starter template that lets users pick programming
languages, editors, and an optional Git repo to clone. The template uses
Docker under the hood but presents a developer-focused experience: pick
your tools, start coding.
## What's included
- **Languages parameter** (multi-select): Python, Node.js, Go, Rust,
Java, C/C++
- **IDEs parameter** (multi-select): VS Code (Browser), VS Code Desktop,
Cursor, JetBrains, Zed, Windsurf
- **Git repo parameter**: Optional URL to clone on workspace start
- **JetBrains filtering**: Maps selected languages to relevant IDE codes
(Python → PyCharm, Go → GoLand, etc.)
- **Docker precondition check**: Uses `data "external"` +
`terraform_data` precondition to surface a friendly error when Docker is
unavailable, before the Docker provider fails with a cryptic message
- **4 presets**: Web Development, Backend (Go), Data Science, Full Stack
- **Single install script**: All languages install in one `coder_script`
to avoid apt-get lock conflicts (agent scripts run in parallel via
`errgroup`)
<details><summary>Design decisions</summary>
- **Docker as invisible backend**: Docker is required on the Coder
server but never mentioned in the user-facing parameter UI. The
experience is entirely "pick languages, pick editors, start coding."
- **`coder_script` over startup_script**: Language installs use a
templated script file (`install-languages.sh.tftpl`) driven by the
languages parameter. A single script avoids dpkg lock contention since
`coder_script` resources execute concurrently.
- **`data "external"` for Docker check**: The external provider probes
Docker availability independently of the Docker provider. If Docker is
down, the `terraform_data` precondition fails with a human-readable
message before any `docker_*` resource is evaluated. This depends on the
Docker provider connecting lazily (at resource eval time, not at
provider init), which current behavior confirms.
- **JetBrains filtering by language**: Rather than showing all 9
JetBrains IDEs, the template computes relevant IDE codes from the
language selection (e.g. Python → PY, Go → GO) and passes them as
`default` to the JetBrains module.
- **Arch-aware Go install**: The install script detects `uname -m` to
download the correct Go binary for amd64 or arm64.
</details>
<details><summary>Screenshots and recordings from the UI</summary>
<p>
<img width="1851" height="1471" alt="Screenshot 2026-05-05 at 2 14
20 PM"
src="https://github.com/user-attachments/assets/d4c9cdc5-d311-43a5-9e2e-f90b0019eda7"
/>
<img width="1851" height="1471" alt="Screenshot 2026-05-05 at 2 15
06 PM"
src="https://github.com/user-attachments/assets/cf3023fe-b6db-4503-a6c4-eaa0ec0659f8"
/>
https://github.com/user-attachments/assets/7507fd7d-ddb5-457a-9f7d-cbf89b36eb20
</p>
</details>
> [!NOTE]
> This PR was authored by Coder Agents.
## Summary
Bumps the repository Go toolchain from 1.25.9 to 1.26.2 across local
development, CI, dogfood Docker images, and Nix builds.
## Changes
- Update `go.mod` and the shared setup-go action to Go 1.26.2.
- Update dogfood Ubuntu image Go versions and the official linux-amd64
tarball checksum.
- Move Nix Go module builds from `buildGo125Module` to
`buildGo126Module`.
- Regenerate API docs affected by Go 1.26 stdlib URL documentation
changes.
## Validation
- `./scripts/check_go_versions.sh`
- `make fmt`
- `make lint`
- `make build-slim`
- `make test TEST_SHORT=1`
- `make pre-commit`
> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻