Closes [DOCS-256](https://linear.app/coder/issue/DOCS-256). Sibling to
[DOCS-253](https://linear.app/coder/issue/DOCS-253) (#25740).
Updates docs URL references across the non-TypeScript surface of
`coder/coder` to match the current docs site structure. Source-of-truth
for redirects is `coder/coder.com/redirects.json` (parent ticket
[DOCS-209](https://linear.app/coder/issue/DOCS-209)).
## What changed
| Area | Files | URL mapping |
|---|---|---|
| Top-level README | `README.md` | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates` ->
`/docs/admin/templates`, `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Docs source | `docs/admin/security/0001_user_apikeys_invalidation.md`
| `/docs/admin/audit-logs` -> `/docs/admin/security/audit-logs` |
| Docs source | `docs/install/cloud/azure-vm.md` |
`/docs/coder-oss/latest/install` -> `/docs/install` |
| Dogfood | `dogfood/coder/guide.md` | `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Helm | `helm/coder/values.yaml` | `/docs/admin/workspace-proxies` ->
`/docs/admin/networking/workspace-proxies` |
| Enterprise coderd | `enterprise/coderd/coderd.go` |
`/docs/admin/encryption` -> `/docs/admin/security/database-encryption`
(error message) |
| Release tooling | `scripts/release/main_internal_test.go` |
`/docs/admin/upgrade` -> `/docs/install/upgrade` (test fixture, matches
`generate_release_notes.sh`) |
| AI bridge | `aibridge/client.go` | repinned to current `main` SHA on
renamed `docs/ai-coder/ai-gateway/monitoring.md`, line range `#L47-L57`
|
| Example templates | 12 `examples/templates/*/README.md`,
`examples/parameters/*`,
`examples/parameters-dynamic-options/README.md`,
`examples/workspace-tags/README.md`, `examples/parameters/main.tf`,
`examples/examples.gen.json` (regenerated) | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates/parameters`
-> `/docs/admin/templates/extending-templates/parameters`,
`/docs/templates/dev-containers` ->
`/docs/admin/integrations/devcontainers`, `/docs/dotfiles` ->
`/docs/user-guides/workspace-dotfiles`,
`/docs/about/architecture#agents` ->
`/docs/admin/infrastructure/architecture#agents` |
| Live notification templates (DB) | New migration
`000510_fix_dormancy_notification_docs_urls.up.sql` and `.down.sql` plus
the four regenerated SMTP/webhook goldens under
`coderd/notifications/testdata/rendered-templates/` |
`/docs/templates/schedule#dormancy-threshold-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-threshold`,
`/docs/templates/schedule#dormancy-auto-deletion-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-auto-deletion`
|
The migration uses `REPLACE(body_template, ...)` scoped by template id
and `LIKE '%/docs/templates/schedule%'`, so it works regardless of which
intermediate state (`000232`, `000262`, `000305`, or `000311`) is
currently in the row.
## What did not change
Historical SQL migrations `000232`, `000262`, `000305`, and `000311` are
not modified because migrations are immutable history. The 18 remaining
stale URL references in those files are superseded at runtime by
migration `000510`. This decision matches the pattern used in the A1
sister PR (#25740).
## Verification
- `go test ./coderd/database/migrations/... -count=1` (UP+DOWN)
- `go test ./coderd/notifications/ -run TestNotificationTemplates_Golden
-update -count=1` to regenerate the four `.golden` files
- `go test ./scripts/release/ -run Test_removeMainlineBlurb -count=1`
- `make pre-commit` (gen + fmt + lint + slim build) ran clean as part of
the commit hook
I also fixed a pre-existing emdash on line 35 of
`examples/templates/azure-linux/README.md` that the lint flagged once
the file entered my diff. The line was already in `main`, but `make gen`
rewrites `examples/examples.gen.json` whenever a `README.md` changes, so
the line came back as a `+` in the diff against `origin/main` and the
`lint/emdash` step refused it.
<details>
<summary>Pre-mortem</summary>
| Risk | Mitigation |
|---|---|
| Migration overwrites future template edits | Used `REPLACE` instead of
full body overwrite. `WHERE id IN (...) AND body_template LIKE
'%/docs/templates/schedule%'` further scopes the write |
| Goldens drift from migrated body | Regenerated goldens via `-update`
after the migration was in place, so the goldens reflect the
post-migration state |
| Down migration leaves stale URLs | Down migration reverses the REPLACE
so a rollback restores the prior URLs |
| Fragment loss when redirect strips fragment | Verified the destination
`schedule.md` contains `## Dormancy threshold` and `## Dormancy
auto-deletion` anchors |
| Terraform parse breakage in `examples/parameters/main.tf` | Only
comments changed; Terraform parser is unaffected |
| Test fixtures in `scripts/release` diverging from
`generate_release_notes.sh` | Updated to match the script, which already
emits `/docs/install/upgrade` |
</details>
---
Generated by Coder Agent on behalf of @nickvigilante.
- Adds server-side and client-side validation for
CODER_CONFIGSSH_HOSTNAME_SUFFIX and CODER_SSH_CONFIG_OPTIONS.
- **Server-side breaking change:** invalid values for either of these will cause `coderd` to exit with an error.
- Client-side: `coder config-ssh` will exit with an error if it detects invalid config.
- Adds tests for the above
Local smoke-testing: ran `develop.sh --env-file <path to an env file
containing badness>`. Validated that server startup failed as expected.
> 🤖 Generated by Coder Agents with supervision from a human.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The bundled `/icon/rstudio.svg` rendered the R language logo (gray oval,
blue R), not the RStudio IDE logo, so templates using the `rstudio`
`coder_app` and the bundled URL got the wrong artwork
([#26211](https://github.com/coder/coder/issues/26211), PRODUCT-383).
This PR:
- Renames the existing `rstudio.svg` (R language logo) to `rproject.svg`
so the artwork stays available for templates that want it.
- Adds a new `rstudio.svg` containing the actual RStudio R-ball logo,
extracted from the [Wikimedia
source](https://upload.wikimedia.org/wikipedia/commons/d/d0/RStudio_logo_flat.svg)
and normalized to `viewBox="0 0 256 256"` to match the rest of the icon
set.
- Adds `rproject.svg` to `site/src/theme/icons.json` so it appears in
the icon picker and gallery alongside `rstudio.svg`.
- Switches the `coder_app "rstudio"` example in
`docs/admin/templates/extending-templates/web-ides.md` to reference
`/icon/rstudio.svg` (and corrects `display_name` to `"RStudio"`),
matching every other example on that page.
| Path | Before | After |
| --- | --- | --- |
| `/icon/rstudio.svg` | R language logo | RStudio R-ball |
| `/icon/rproject.svg` | (did not exist) | R language logo |
<table>
<tr>
<th>Old <code>rstudio.svg</code> → new
<code>rproject.svg</code></th>
<th>New <code>rstudio.svg</code></th>
</tr>
<tr>
<td align="center"><img
src="https://raw.githubusercontent.com/coder/coder/vigilante/product-383-bundled-iconrstudiosvg-appears-to-show-r-language-logo/site/static/icon/rproject.svg"
width="128" height="128"></td>
<td align="center"><img
src="https://raw.githubusercontent.com/coder/coder/vigilante/product-383-bundled-iconrstudiosvg-appears-to-show-r-language-logo/site/static/icon/rstudio.svg"
width="128" height="128"></td>
</tr>
</table>
**Breaking-change note.** Templates that referenced `/icon/rstudio.svg`
expecting the R language oval will now render the RStudio R-ball.
Templates that want the R language logo should switch to
`/icon/rproject.svg`. The Linear issue acknowledges this tradeoff.
**Client cache caveat.** `site/site.go` serves everything under `/icon/`
with `Cache-Control: public, max-age=31536000, immutable`, so any
browser that already loaded the old artwork at `/icon/rstudio.svg` can
keep displaying it for up to a year before revalidating. A hard refresh
(Ctrl/Cmd+Shift+R) clears it immediately. Cache-busting (hashed icon
URLs) is out of scope for this fix and tracked as a possible follow-up
against PRODUCT-383.
<details>
<summary>Implementation notes</summary>
- Verified geometric fidelity by rendering the new SVG and a
high-resolution crop of the Wikimedia source at 256x256 and computing
the RMS pixel difference: 1.268/255 (~0.5%, essentially antialiasing
noise).
- Picked `viewBox="0 0 256 256"` because 139 of 142 SVGs in
`site/static/icon/` already use that viewBox.
- Searched the repo for `rstudio.svg` references: the only direct one is
`site/src/theme/icons.json`. The docs file references the `rstudio`
`coder_app` slug, not the icon path, so the rename does not break any
callsite.
- R-ball geometry: source circle at (318.7, 312.9) radius 309.8 in the
original `viewBox 0 0 1784.1 625.9`. Translating by (-8.9, -3.1) and
scaling by 256/619.6 maps its bounding box onto `0 0 256 256`. Path
coordinates are pre-computed so the file ships with no transform layer.
- Pre-commit hooks passed locally, including `lint/site-icons`.
</details>
Fixes#26211
Fixes PRODUCT-383
---
_Generated by Coder Agents on behalf of @nickvigilante._
Closes
https://linear.app/codercom/issue/AIGOV-287/add-effective-group-resolution
Implements the effective AI budget resolution from the AI Governance
cost-controls RFC: for a given user, a `user_ai_budget_overrides` row
wins if present, otherwise the deployment budget policy (`highest`)
picks the largest group budget across the user's groups, ties broken
alphabetically.
For now, I keep the logic under `coderd/aibridge/budget`, but that may
change during the implementation of budget enforcement.
Fixes CODAGT-495
Provider 429 responses that mention quota or billing were classified as
non-retryable usage limits before the rate-limit rule could run, which
suppressed retries for Gemini and Azure OpenAI rate limits.
- Treats broad quota and billing prose as rate-limit retryable when the provider returns HTTP 429.
- Preserves `insufficient_quota` as a terminal usage-limit signal for OpenAI billing exhaustion.
- Includes structured provider details in usage-limit matching so response-body error codes are classified consistently.
Generated by Coder Agents on behalf of @johnstcn.
The Agents chat list scrollbar swallowed clicks on the per-row controls
(the actions menu, timestamp, and unread/shared indicators). PR #26016
added a 24px invisible hit-target to the shared `ScrollArea` thumb,
anchored to the thumb's right edge and extending left. On this sidebar
the visible scrollbar is only ~6px wide (`w-1.5`), so the hit-target
reached ~18px left of it, on top of the controls just inside the edge.
This adds an optional, default-preserving `scrollThumbClassName` prop to
the shared `ScrollArea`, threaded through `ScrollBar` to the thumb the
same way `scrollBarClassName` already is. When the prop is omitted the
output is byte-for-byte unchanged, so every other scrollbar keeps the
24px target. The Agents chat list passes `before:hidden` to drop only
the expanded hit-target: the visible 6px thumb stays draggable and the
scrollbar looks identical, so there is no visible change while the
controls beside it become clickable again.
A new `ScrollArea` story (`ThumbHitAreaOverride`) uses `type="always"`
so the thumb is guaranteed to render, then asserts the default keeps the
24px `::before` target while the override sets `display: none`.
Refs #26016
_Disclosure: produced using Opus 4.8_
I've noticed recently that some prompts are not displaying correctly.
<img width="1388" height="509" alt="image"
src="https://github.com/user-attachments/assets/fad3812f-e42d-4398-a16c-a0e7f924455d"
/>
The cause is the `mid-conversation-system-2026-04-07` beta. When
enabled, the client appends a trailing `role: "system"` message after
the user's text (e.g. an injected skills list):
```json
"messages": [
{ "role": "user", "content": [ "hey how are ya" ] },
{ "role": "system", "content": "The following skills are available..." }
]
```
Our prompt detection algo was being subverted since we only check the
last message if role=user.
Previously, fantasy's Anthropic provider adapter accepted PDF and text
FileParts but dropped the filename on the floor, so Claude (direct or
via Bedrock) saw the document bytes without any handle and could not
answer questions like "what's in foo.pdf". Other providers (OpenAI,
Gemini, OpenRouter, Vercel) already forwarded filenames.
Bumps `coder/fantasy` past
[coder/fantasy#38](https://github.com/coder/fantasy/pull/38), which
sanitizes `FilePart.Filename` and sets it as the Anthropic
`DocumentBlockParam.Title` for both `application/pdf` and `text/*`
attachments, and emits a `CallWarning` for unsupported `FilePart` media
types instead of silently dropping them.
On this side, plumbs the resolved filename through `partsToMessageParts`
so the `FilePart` literal carries it into the provider. The existing
`TestModelFromConfig_AnthropicPDFFilePartReachesProvider` regression
test is extended to assert the outbound Anthropic request includes the
sanitized title (`quarterly_report.v1.pdf` becomes `quarterly report v1
pdf`).
Closes CODAGT-545
Relates to
https://linear.app/codercom/issue/AIGOV-285/add-user-budget-overrides-table-and-crud-api
Adds audit-log support for `user_ai_budget_override` mutations. Without
it, an admin could quietly change a user's per-user spend cap (e.g. from
`$500` to `$50`), reassign it to a different group, or delete it
entirely with no record of who did it.
Both write (`create-or-update`) and delete actions now generate audit
log entries. Unlike group AI budgets, which only track `spend_limit`,
overrides also track `group_name`: an override can be reassigned to a
different attributed group, so that change needs to show up in the diff.
The raw `spend_limit_micros`, IDs, and timestamps are ignored in favor
of the human-readable `spend_limit` and `group_name`.
Depends on #25439.
## Screenshot
<img width="1343" height="514" alt="image"
src="https://github.com/user-attachments/assets/aee30f58-6e81-435e-9bca-5bc98f49d8d3"
/>
Updates hand-written documentation to use "AI Gateway" instead of "AI
Bridge" as a follow-up to the UI rename in
https://github.com/coder/coder/pull/26161#issuecomment-4660014072.
Changed files:
- `docs/ai-coder/ai-gateway/clients/codex.md` — display name and config
key (`aibridge` to `ai_gateway`) in TOML config examples
- `docs/ai-coder/ai-gateway/clients/factory.md` — display names in JSON
config examples and prose
- `docs/ai-coder/ai-gateway/monitoring.md` — structured logging
description
- `docs/ai-coder/ai-gateway/ai-gateway-proxy/setup.md` — CA cert example
filenames (`coder-aibridge-proxy-ca.pem` to
`coder-ai-gateway-proxy-ca.pem`)
- `docs/ai-coder/ai-gateway/clients/copilot.md` — CA cert example
filenames
- `docs/ai-coder/ai-gateway/clients/index.md` — CA cert example filename
Generated reference docs (`docs/reference/cli/`, `docs/reference/api/`)
and `docs/manifest.json` are generated from Go code and will update
automatically via `make gen` in the context of AIGOV-230 (API swagger
tags) and AIGOV-231 (CLI help).
Refs https://linear.app/codercom/issue/AIGOV-233
> Generated by Coder Agents on behalf of @ssncferreira
Update release calendar with this week's latest branch releases:
- v2.34.0 → v2.34.1 (Mainline/ESR)
- v2.33.6 → v2.33.7 (Stable)
- v2.32.5 → v2.32.6 (Security Support)
Also updates the ESR version link in the prose to point to v2.34.1.
Fixes: https://linear.app/codercom/issue/PLAT-321
> Generated by Coder Agents (session: plat-321-update-release-docs)
Co-authored-by: doc-check[bot] <doc-check[bot]@users.noreply.github.com>
This updates the agents right-panel tab model so Desktop is included in
the normal tab list instead of being rendered separately by
`SidebarTabView`. Desktop now appears before terminal tabs, which keeps
the built-in terminal and any newly opened terminal tabs at the right
edge. `SidebarTabView` no longer needs a Desktop-specific prop or render
path because it simply renders the ordered tabs it receives.
## Problem
Launching the system Google Chrome from the portabledesktop/VNC session
in dogfood workspaces crashes immediately (SIGTRAP, exit 133). Headless
Chrome and Playwright's bundled Chromium are unaffected, so it only
shows up when a user opens Chrome in the desktop.
Root cause traced via `strace`: Chrome `dlopen`s `libX11-xcb.so.1` when
it initializes the X11 GUI path and `IMMEDIATE_CRASH()`es when the
library is missing:
```
openat(... "/usr/lib/x86_64-linux-gnu/libX11-xcb.so.1" ...) = -1 ENOENT
... --- SIGTRAP {si_signo=SIGTRAP, si_code=SI_KERNEL} ---
```
The Google Chrome `.deb` declares `libx11-6` and `libxcb1` but **not**
`libx11-xcb1`, so apt never installs it.
## Why it regressed (~2 weeks ago)
`libx11-xcb1` used to be pulled in transitively by the build-time step:
```
pnpm dlx playwright@1.47.0 install --with-deps chromium
```
`--with-deps` makes Playwright `apt-get install` Chromium's full
system-library set, which on Ubuntu includes `libx11-xcb1`.
#25448 ("refactor: build dogfood image as base + mise oci layers")
removed that build step from both Ubuntu Dockerfiles and moved the
install to the workspace-start `install-deps` script, which runs
`playwright install chromium` / `playwright-core install --no-shell
chromium` **without** `--with-deps`. Those download only the browser
binaries, not the apt system libraries, so `libx11-xcb1` disappeared
from the image.
## Fix
Install `libx11-xcb1` explicitly in both `ubuntu-22.04` and
`ubuntu-26.04` `Dockerfile.base` apt lists, decoupling desktop Chrome's
X libraries from the Playwright install so it can't silently regress
again.
## Testing
Reproduced and verified on a running dogfood workspace (Ubuntu 22.04):
before the fix, `DISPLAY=:1 google-chrome` crashed with SIGTRAP on the
missing `libX11-xcb.so.1`; after `apt-get install -y libx11-xcb1`, a
vanilla `google-chrome` launch (as the desktop menu invokes it) starts
and stays running with a renderer process. Remaining EGL/GPU warnings
are expected (software rendering under VNC) and non-fatal.
<details>
<summary>Investigation notes</summary>
- headless Chrome and `--screenshot` worked, isolating the failure to
the X11/Ozone GUI path.
- The earlier `crashpad ... format error / Unknown ptrace scope`
messages were a red herring; the real crash was the missing-library
`dlopen`.
- `portabledesktop` ships a self-contained Xvnc server + WM runtime (its
own glibc closure) and does not provide libraries to system apps like
Chrome, so the fix belongs in the dogfood image.
</details>
---
Generated with Coder Agents.
Renames all user-visible "AI Bridge" strings to "AI Gateway" in the
browser UI as part of the AI Bridge to AI Gateway rebrand (AIGOV-233).
Changes:
- Page titles and headings ("AI Bridge Logs" to "AI Gateway Logs",
`pageTitle` calls)
- Help popover title, description, and doc link
- Setup alert title, description, and doc link
- Back-link tooltip on session threads page
- AI add-on help popover text
- License entitlement warning shown in the dashboard banner
Doc links updated from `/ai-coder/ai-bridge` to `/ai-coder/ai-gateway`
(directory already exists).
This PR intentionally does not rename internal identifiers,
file/directory names, API endpoints, routes, CLI help, deployment config
options, or generated types. Those will be tracked separately.
Related to: https://linear.app/codercom/issue/AIGOV-233
> Generated by Coder Agents on behalf of @ssncferreira
- Adds a dynamic terminal tab model to the Coder Agents right panel so
users can open, switch between, and close multiple Web Terminal sessions
for the same agent.
- Each terminal tab generates its own UUID reconnect token and is
persisted per agent in local storage, so tabs and their PTY sessions
survive reloads.
- New terminal tabs are auto-labelled (`Terminal 2`, `Terminal 3`, ...),
filling the lowest free number.
- The built-in Terminal tab is now closeable; closing it hides it and
persists that choice per agent, and it can be restored from the add-tab
control.
- Only the active terminal (and a tab that is mid-activation) mounts the
expensive xterm/WebSocket/PTY resources; a freshly hidden terminal stays
warm for 30s to keep quick tab toggles instant, then detaches to bound
resource usage instead of capping the tab count.
- Inactive terminals stay laid out but hidden, and a newly opened
terminal is not activated until it reports ready (or 100ms), avoiding
the narrow refit and blank-frame flicker when switching between terminal
canvases.
- The right-panel tab bar scrolls horizontally with chevron controls
when tabs overflow, keeping everything on a single row.
- Per-agent right-panel tab and hidden-terminal state is cleared from
local storage when a chat is archived or deleted.
- Adds unit tests for the tab utilities, persistence helpers, and the
warm/detach lifecycle hook, plus Storybook play-function coverage for
the tab and terminal components.
Relates to CODAGT-346
AI Bridge reserializes OpenAI chat-completions requests before sending
them upstream. For Gemini OpenAI-compatible routes, that OpenAI
typed-parameter round trip drops
`tool_calls[].extra_content.google.thought_signature`, so Google rejects
tool-result continuations with `Function call is missing a
thought_signature`.
This PR:
- patches the AI Bridge upstream serialization boundary for Gemini
OpenAI-compatible chat completions
- shares the Gemini thought-signature patching helpers with chatd's
OpenAI-compatible transport patch to keep behavior consistent
- treats direct Google OpenAI-compatible upstream endpoints as
Gemini-scoped even when the request model is an alias
- adds the Google fallback thought signature to every assistant tool
call in the active turn, including parallel tool calls
- covers the regression that `extra_content` is dropped before the
upstream body is patched
> Mux updated this PR description on behalf of Mike.
---------
Co-authored-by: Susana Cardoso Ferreira <susana@coder.com>
## Problem
aibridgeproxyd's HTTP transport (`proxy.Tr`) was configured with secure
TLS defaults only when an upstream proxy was set. Without one, it fell
back to [goproxy's default
transport](https://github.com/elazarl/goproxy/blob/v1.8.0/proxy.go#L152),
which has `InsecureSkipVerify: true`, leaving the connection between the
proxy and aibridge vulnerable to MITM on HTTPS deployments.
This PR moves the secure transport assignment outside the upstream proxy
branch so it applies unconditionally.
## Changes
* Apply secure TLS defaults to `proxy.Tr` unconditionally (verified
`RootCAs`, `MinVersion: TLS 1.2`).
* Add `TestProxy_AIBridgeTLSVerification` to cover the verification path
between the proxy and aibridge.
## Notes
* **Behavior change for `HTTPS_PROXY` env var**: previously, when
`UpstreamProxy` was unset, `proxy.Tr` honored `HTTP_PROXY` and
`HTTPS_PROXY` env vars. After this PR it does not, since MITM'd requests
now always go directly to aibridge. This matches the behavior when
`UpstreamProxy` is configured, which already ignored env vars.
* **HTTPS deployments with a private CA**: when `CoderAccessURL` is
HTTPS and its TLS certificate (or the load balancer's certificate
fronting it) is signed by a CA not in the system trust store, the proxy
will now fail with `x509: certificate signed by unknown authority`.
Closes
https://linear.app/codercom/issue/AIGOV-386/ai-bridge-proxy-uses-goproxy-default-with-tls-verification-disabled
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
## Description
This PR adds Prometheus metrics for aibridge's API-key failover, giving visibility into key pool health and failover behavior per provider.
The following metrics are introduced:
- **`key_pool_state`** (gauge): number of keys currently in each state (`valid`, `temporary`, `permanent`) per provider, sampled at scrape time.
- **`key_pool_state_transitions_total`** (counter): key state transitions during failover, labeled by `reason` (`rate_limited`, `unauthorized`, `forbidden`).
- **`key_pool_exhaustions_total`** (counter): times a pool ran out of usable keys, labeled by `outcome` (`rate_limited`, `auth_failed`).
- **`key_pool_failover_attempts`** (histogram): keys attempted before success or exhaustion (per interception for bridged requests, per request for passthrough).
## Changes
- Moves `MarkKeyOnStatus` and key-pool error handling onto `*keypool.Pool`.
- Attaches metrics to each provider's key pool at install time, on construction and on provider reload.
- Adds a scrape-time state collector and a `KeyPools()` accessor on the bridge pool to feed it.
- Tracks per-request key attempts in the bridged and passthrough failover paths.
- Adds test coverage for the new metrics across the keypool unit tests, the bridged intercept failover tests, and the passthrough failover test.
Closes https://github.com/coder/internal/issues/1447
Closes https://linear.app/codercom/issue/AIGOV-198/aibridge-key-failover-observability
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
Consolidates the per-interceptor key-failover tests into a single table-driven `keyfailover_test.go`, parameterized over the interceptors (`messages`, `chatcompletions`, `responses`) and modes (blocking and streaming).
It keeps the two scenarios as separate tests: `TestInterception_KeyFailover` (failover within a single interception) and `TestInterception_AgenticLoopFailover` (failover across an agentic-loop continuation). Same cases and assertions as before with far less duplication, reusing the shared `testutil` mocks (`MockUpstream`, `MockServerProxier`, fixture helpers).
Closes https://linear.app/codercom/issue/AIGOV-396/consolidate-intercept-key-failover-tests-across-modes-and-providers
Closes https://linear.app/codercom/issue/AIGOV-395/share-a-single-mockupstream-helper-between-integration-tests-and
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
Moves the shared aibridge mock test helpers (`MockUpstream`, `MockServerProxier`, `StubToolCaller`, and the `NewFixtureResponse`/`NewFixtureToolResponse` constructors) out of `aibridge/internal/integrationtest` into `aibridge/internal/testutil`, and exports them.
This lets the per-interceptor test packages (`messages`, `chatcompletions`, `responses`) reuse one set of mocks instead of each redefining its own. The symbols are exported and call sites updated, with no behavior change.
Closes: https://linear.app/codercom/issue/AIGOV-397/move-mockserverproxier-into-a-shared-testutil-package
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The agentic loop has a race between the main goroutine and the `Start`
goroutine on the shared `ResponseWriter`. When an iteration's response
contains only injected-tool events (no text to relay), `Start` may not
have called `InitiateStream` by the time main reaches the `IsStreaming`
check on the next iteration. The `IsStreaming` check then returns false,
main writes a JSON error via `writeUpstreamError`, and `Start` later
writes SSE headers and events on top, producing a malformed JSON+SSE
response:
```
{\"error\":{\"message\":\"all configured keys are rate-limited\",\"type\":\"rate_limit_error\"},\"request_id\":\"\",\"type\":\"error\"}event: message_start\n..."
```
Fix: explicitly call `events.InitiateStream(w)` at the agentic
continuation point so the SSE stream is committed before the next
iteration runs. Keeps `messages` consistent with the pattern already
used in `chatcompletions/streaming.go`. `sync.Once` makes the double
call safe.
Related: coder/internal#1524
Related: coder/coder#25654
Closes:
https://linear.app/codercom/issue/AIGOV-336/flake-teststreaminginterception-agenticloopfailoveragentic-all-keys
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
Adds the agent half of the workspace context sources RFC. The agent now
resolves instruction files, skills, and MCP configs into a typed
`Snapshot`, watches the relevant paths recursively, exposes the source
list over a workspace-agent HTTP API, and pushes each `Snapshot` to
coderd over a new `PushContextState` RPC on Agent API v2.10.
The coderd-side handler is a stub returning `Unimplemented` for now.
Real persistence to `workspace_agent_context`, chatd hydration on dirty
events, and the `KindMCPServer` MCP provider are tracked by
[CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd).
This matches the pattern used for v2.7 `ReportBoundaryLogs` in
[#21293](https://github.com/coder/coder/pull/21293), which bumped the
version and shipped a stub server so the wire and client could iterate
before the persistence layer landed.
## What ships
### agent/agentcontext (new package)
- `Source`, `Resource` (kinds `instruction_file`, `skill`, `mcp_config`,
`mcp_server` plus reserved `plugin`/`hook`/`subagent`/`command`),
`ResourceStatus`, `Snapshot`, `ComputeAggregateHash`.
- `Manager` owns the in-memory source list, performs the initial resolve
synchronously in `NewManager`, runs a re-resolve/watcher loop in `Run`,
exposes
`AddSource`/`RemoveSource`/`Sources`/`HasSource`/`Snapshot`/`SubscribeChanges`/`Resync`/`SeedSources`/`Close`.
- `Resolver` walks scan roots, classifies recognized files, enforces 64
KiB per-resource, 2 MiB aggregate, and 500-resource caps with
`StatusOversize`/`StatusExcluded`/`StatusUnreadable`/`StatusInvalid`
outcomes, skips `node_modules`/`vendor`/etc., validates symlink targets
stay inside the scan root, stamps `SourcePath` on user-derived
resources, and optionally pulls MCP server tool lists via an
`MCPProvider` interface. MCP config resources ship metadata only (size,
hash) so secrets in env blocks never leave the agent.
- `Watcher` is a recursive `fsnotify` wrapper with a 250 ms debounce,
dynamic arming of newly created directories, and an ENOSPC-tolerant
degraded mode that no-ops further syncs until the manager resyncs
explicitly.
- HTTP API for `GET/POST /sources`, `GET/DELETE /sources/{path}`, `POST
/resync` mounted at `/api/v0/context`.
- `Pusher` interface plus `RunPush` goroutine with exponential backoff
capped at 30 s. `DRPCPusher` adapts the generated `DRPCAgentClient210`
to `Pusher` and translates `drpcerr.Unimplemented` to
`ErrPushUnimplemented` so the push loop exits cleanly when talking to
coderd deployments that have not enabled the real handler.
### agent/proto (v2.10)
- New messages `ContextResource`, `PushContextStateRequest`,
`PushContextStateResponse` and the `PushContextState` RPC on `service
Agent`.
- Generated `DRPCAgentClient210` interface and
`codersdk/agentsdk.Client.ConnectRPC210` / `ConnectRPC210WithRole`.
- `tailnet/proto.CurrentMinor` bumped from `9` to `10`.
### Agent wiring
- `agent.Options.Client` declares both v2.9 and v2.10 connectors;
`run()` dials with `ConnectRPC210WithRole`.
- `apiConnRoutineManager` holds a `DRPCAgentClient210`. Existing v2.8
routines keep their narrower `DRPCAgentClient28` signature thanks to
interface embedding.
- `startAgentAPI210` is the v2.10 counterpart to `startAgentAPI` for
routines that need the new client. The push context state routine uses
it.
- A `contextManager` is constructed in `agent.init()`, seeded from the
existing `CODER_AGENT_EXP_*_DIRS` env vars, started in its own goroutine
under `gracefulCtx`, and closed in `agent.Close`.
- `handleManifest` calls `Manager.SeedSources` for sources rooted at the
manifest directory, then `Resync` after `manifest.Swap`, so the snapshot
reflects the workspace working directory immediately instead of waiting
for the next filesystem event.
- HTTP routes mounted at `/api/v0/context` when the manager is up.
### Coderd stub
`coderd/agentapi/context.go` returns `drpcerr.Unimplemented` for
`PushContextState`. The real handler that persists
`workspace_agent_context` rows, hydrates chats, and emits dirty events
lives in CODAGT-569.
## Tests
24 tests across `agent/agentcontext` cover types, paths, resolver
behavior with file caps, skill containers, MCP secret omission, symlink
target validation, the recursive watcher firing on real fsnotify events,
manager source CRUD / `Resync` / `SeedSources` / `Run` lifetime, the
HTTP API, the DRPC adapter, and the push retry / initial-flag /
unimplemented paths. Passes `go test -race -count=2`.
`TestAgent_ContextStatePushed` boots a full agent against
`agenttest.FakeAgentAPI` (which now records `PushContextState` traffic)
and asserts the seeded `AGENTS.md` appears in a snapshot push with
`schema_version = 1`.
<details>
<summary>Notes for reviewers</summary>
- Source CRUD is workspace-agent-token only; coderd is not in the path
for source mutation.
- Per-resource cap 64 KiB, aggregate 2 MiB, count cap 500; resources
past the cap ship with `StatusExcluded` and an empty payload so the
aggregate hash still detects content edits. MCP-emitted resources
enforce both a per-provider count cap and the aggregate byte cap.
- Symlinks inside the scan root are followed; symlinks pointing outside
(or broken) are rejected with `StatusExcluded` so credentials reachable
via a stray symlink stay off the wire.
- The initial push gates `lifecycle = ready` in the eventual full
design. For this PR the `SeedSources` plus `handleManifest`-driven
`Resync` keeps the snapshot fresh; the live push loop ships now and
DRPCPusher translates the coderd `Unimplemented` stub into a clean exit.
- The `PLUGIN`/`HOOK`/`SUBAGENT`/`COMMAND` kinds are reserved in proto
and Go enums but unused; the Claude Code plugin resolver ships in a
follow-up that does not need a schema migration.
- Two follow-ups remain, both tracked by
[CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd):
(1) the chatd-side handler that persists snapshots and dirties chats;
(2) the `coder exp chat context` CLI command set for
`list`/`show`/`add`/`remove`/`refresh`.
</details>
_This PR was authored by Coder Agents on Kyle Carberry's behalf._
## Summary
`X-OpenCode-Session` is only set by the OpenCode "Zen" provider. Other
providers use `x-session-affinity` instead. This change falls back to
`x-session-affinity` when `X-OpenCode-Session` is not present, so
sessions are correctly identified regardless of the provider used.
Ref: https://github.com/coder/coder/pull/26128
## Changes
- `aibridge/session.go`: Prefer `X-OpenCode-Session` (Zen), fall back to
`x-session-affinity` (other providers).
- `aibridge/session_test.go`: Add tests for precedence and fallback
behavior.
> Generated with [Coder Agents](https://coder.com) by @dannykopping
`TestResolveWorkspace/TransportError` could sometimes observe an HTTP
404 from a closing test server instead of a transport error.
Make the test deterministic by injecting a failing `http.RoundTripper`,
and add `testutil.RoundTripperFunc` for reuse.
Generated by Coder Agents.
<details>
<summary>Implementation plan</summary>
# Plan: Deterministic ResolveWorkspace transport error test
## Context
`TestResolveWorkspace/TransportError` relied on closing an
`httptest.Server` before making a request. CI showed this can race with
the request path and produce an HTTP 404 instead of a transport error.
The test should inject a transport failure directly.
## Red
1. Update the transport-error case to use a custom `http.RoundTripper`
that returns an error.
2. Confirm the test fails to compile until the reusable
`testutil.RoundTripperFunc` helper exists.
## Green
1. Add `testutil.RoundTripperFunc` in `testutil/http.go`.
2. Implement `RoundTrip` so the function type satisfies
`http.RoundTripper`.
3. Add a compile-time interface assertion for the helper.
4. Update `codersdk/workspaces_test.go` to inject a client using
`testutil.RoundTripperFunc`.
5. Keep the existing assertion that transport errors do not become
`*codersdk.Error`.
## Refactor
1. Run `gofmt` on touched Go files.
2. Check import cleanup and variable names after the implementation
compiles.
3. Keep the change limited to the reusable helper and this test.
## Verification
1. `go test ./codersdk -run TestResolveWorkspace -count=100`
2. `go test ./testutil ./codersdk -run TestResolveWorkspace -count=1`
3. `git diff --check`
</details>
Long filter values (for example diff URLs) were widening the chat search
dialog instead of staying inside the search box, and some passthrough
filters did not round-trip cleanly through the backend query parser.
This fixes the filter pills so long values truncate inside the search
box
(with the full value available via a hover tooltip), and normalizes
passthrough filters so values containing spaces or colons are quoted and
re-parse identically.
closes CODAGT-556
closes CODAGT-467
## Problem
In the agent chat timeline, wide tool-preview code blocks did not wrap
**and** had no usable horizontal scroll. Long lines were clipped and
unreachable for mouse users.
Root cause: file/code/JSON previews (`read_file`, generic & MCP tool
input/output) and markdown fenced code blocks render through
`@pierre/diffs`, whose `[data-code]` grid grows to its content width
(`align-self: flex-start`). The wrapping `ScrollArea` only rendered a
**vertical** scrollbar, so although the viewport was horizontally
scrollable, there was no scrollbar affordance and the overflow was
clipped.
## Fix
Render a horizontal scrollbar on these previews via `ScrollArea
orientation="both"`, exposing the already-scrollable viewport with a
visible 6px bar (consistent with the existing hover scrollbars). For
markdown, let `[data-code]` size to its content so the outer
`ScrollArea` owns the scroll.
Shell/log output (`execute`, `process_output`) and diffs (`write_file`,
`edit_files`) intentionally keep **wrapping**.
| Preview type | Behavior |
| --- | --- |
| `read_file`, generic/MCP input & output, markdown fenced code |
horizontal **scroll** (new) |
| `execute`, `process_output` (shell/logs) | wrap (unchanged) |
| `write_file`, `edit_files` (diffs) | wrap (unchanged) |
## Changes
- `ScrollArea`: add `horizontalScrollBarClassName` to size the
horizontal bar independently of the vertical bar (avoids a `twMerge`
width/height conflict).
- `ReadFileTool`, generic `ToolFileViewer` (`Tool.tsx`):
`orientation="both"` + thin horizontal bar.
- `Response.tsx`: wrap fenced code in a both-axis `ScrollArea`; let
`[data-code]` size to content.
- Long-line regression stories for the file viewer, generic tool, and
markdown.
- Change scrollbar color to accessible contrast ratio
- Increase hit area for scroll bars to 24px which is minimim for wcag
2.2 accessibility requirements
<details>
<summary>Implementation notes & decisions</summary>
- The `@pierre/diffs` `File` viewer only supports `overflow: "scroll" |
"wrap"`. Its `[data-code]` element is a grid with `overflow: scroll
clip` that grows to content width instead of scrolling, so the outer
container must provide the scroll affordance.
- Chosen approach: surface the **outer** `ScrollArea`'s horizontal
scrollbar (the viewport was already scrollable) rather than fighting the
library's internal per-block scroll. This yields a single, unified
horizontal scrollbar and guarantees the timeline never exceeds the
viewport (the `ScrollArea` root is `overflow: hidden`).
- Scroll vs wrap was chosen per content type: code/JSON/file structure
benefits from scrolling (wrapping breaks indentation and line-number
alignment), while shell/log output keeps wrapping. Precedent for visible
horizontal scrollbars already exists in `GitPanel`/`TaskApps`.
</details>
---
_Generated by Coder Agents on behalf of @jaaydenh._
agentssh's CommandEnv, sftpHandler, and agentproc each resolved the
session working directory on their own and had drifted: sftpHandler used
the configured directory without checking it exists and bypassed the
injected EnvInfoer, while the others stat-checked and fell back to home.
Home and shell lookups could also skip the EnvInfoer seam through the
exported usershell.HomeDir and Get.
Resolve through a single usershell.ResolveWorkingDirectory and confine
host home and shell lookups to usershell, so SSH sessions, the process
API, and tests can no longer diverge and the injected environment is
always honored. This also fixes SFTP landing in a configured directory
that no longer exists.
Refs coder/coder#26099
Newer GitHub CLI versions probe terminal colors before interactive
prompts, which can leave OSC responses in dogfood web terminal stdin and
break `gh auth login`.
Default `NO_COLOR=1` inside the dogfood `/usr/local/bin/gh` wrappers
when callers have not set it themselves. This avoids the color probe
without pinning or downgrading `gh`, and keeps the existing Coder
external-auth wrapper behavior intact.
Refs ENG-2842.
> 🤖 Generated by Coder Agents on behalf of @johnstcn.
This follows #26098 by teaching AI Bridge to read OpenCode session IDs
from the X-OpenCode-Session header, so OpenCode requests are grouped
consistently in interception logs.
Adds unit and integration test coverage for the new header.
<details>
<summary>Coder Agents generated</summary>
This pull request was generated with Coder Agents assistance.
</details>
Updates the VS Code AI Gateway client docs to reflect the Custom
Endpoint provider introduced in VS Code 1.121 (Insiders) and promoted to
Stable in 1.122.
## Changes
**`docs/ai-coder/ai-gateway/clients/vscode.md`**
- Replace the deprecated `customoai` vendor with `customendpoint`
- Add the required `apiType` field (`responses` for OpenAI, `messages`
for Anthropic)
- Add Anthropic provider setup (Messages API type, base URL
`…/aibridge/anthropic`)
- Note GitHub sign-in is no longer required — works in
air-gapped/restricted environments
- Add limitation callout: inline suggestions and NES still require
GitHub Copilot
- Reflect the UI-first API key entry flow (VS Code stores the token
securely; do not paste into JSON directly)
- Drop the Centralized/BYOK split — VS Code has no template injection
path, so both scenarios follow the same user-driven UI flow
**`docs/ai-coder/ai-gateway/clients/index.md`**
- VS Code compatibility row: Anthropic `❌ → ✅`
- Updated Notes column
<details>
<summary>Research notes</summary>
- VS Code 1.121 shipped the Custom Endpoint provider (Insiders),
replacing the legacy OpenAI Compatible (`customoai`) provider which is
now deprecated.
- VS Code 1.122 promoted Custom Endpoint to Stable and removed the
GitHub sign-in requirement for BYOK.
- Anthropic support confirmed working: `apiType: "messages"` + base URL
`…/api/v2/aibridge/anthropic` (Coder's gateway accepts the Coder session
token as `x-api-key`).
- OpenAI uses `apiType: "responses"` + base URL
`…/api/v2/aibridge/openai`.
- API keys must be entered via the Manage Language Models UI — VS Code
stores them securely and references them as
`${input:chat.lm.secret.XXXXX}` in the JSON.
</details>
> This PR was drafted by Coder Agents on behalf of @matifali.
Adds OpenCode to AI Bridge client detection so requests with user agents
like `opencode/1.16.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14`
show up as a first-class client instead of `Unknown`.
This also wires the existing OpenCode frontend asset into the AIBridge
UI, adds a Storybook story for the client icon, and updates the
monitoring docs list of supported client values.
<details>
<summary>Coder Agents generated</summary>
This pull request was generated by Coder Agents.
</details>
Surfaces the org's `default_org_member_roles` inside the org members role editor. These roles are implied, not physically assigned to any member. Just like the `member` role.
Replaces the quickstart's "Install Docker" step with a runtime-agnostic
"Install a container runtime" step, and switches the per-platform
defaults
to free, lightweight options that avoid Docker Desktop's cost and
overhead.
A callout above the OS tabs names Colima, Rancher Desktop, Podman, and
Docker Desktop as valid runtimes and tells readers to skip ahead if they
already have one running. The default install path per platform is now:
- Linux: Docker Engine (unchanged from the previous doc)
- macOS: Colima with the Docker CLI
- Windows: Podman Desktop
The "Cannot connect to the Docker daemon" troubleshooting subsections
are
updated to match the new defaults.
Closes
[DEVREL-22](https://linear.app/codercom/issue/DEVREL-22/recommend-orbstackcolimarancher-desktoppodman-as-docker-desktop).
A follow-up Linear issue will track a deeper "Docker runtime
alternatives"
reference page covering OrbStack (with its commercial-license caveat),
Rancher Desktop, and CLI Podman.
<details>
<summary>Implementation proposal and pre-mortem</summary>
# DEVREL-22 proposal: Replace "Install Docker" with "Install a container
runtime"
Linear:
[DEVREL-22](https://linear.app/codercom/issue/DEVREL-22/recommend-orbstackcolimarancher-desktoppodman-as-docker-desktop)
Repo: `coder/coder`
Branch:
`vigilante/devrel-22-recommend-orbstackcolimarancher-desktoppodman-as-docker`
Primary file: `docs/tutorials/quickstart.md`
## Summary
Rename Step 1 of the quickstart from "Install Docker and set up
permissions" to "Install a container runtime", add a callout that any
Docker-compatible runtime works, and switch the per-platform default to
the lightest free path on each OS. Keep Docker Desktop, OrbStack, and
Rancher Desktop as documented alternatives, but not as the primary
recommendation. The deeper "alternatives" reference page is a follow-up.
## Why
- Docker Desktop is slow on macOS/Windows and requires a paid license
for most commercial use.
- The Coder Quickstart template only needs the Docker daemon, not Docker
Desktop's GUI.
- No single tool satisfies "curl install + cross-platform + free +
minimal setup", so a per-platform recommendation is the honest answer.
## Per-platform defaults
| Platform | Default in quickstart | Why |
|----------|----------------------|-----|
| Linux | Docker Engine via `curl -sSL https://get.docker.com \| sh` |
Already in the doc, already a curl one-liner, already free. No change. |
| macOS | Colima | Two commands (`brew install colima docker`, `colima
start`), free for commercial use, exposes `/var/run/docker.sock` so the
Coder template needs zero env vars. |
| Windows | Podman Desktop | Free, handles WSL2 prereq and `podman
machine` setup through the GUI, sets up Docker socket compatibility.
Lighter than Docker Desktop, simpler than CLI Podman + `DOCKER_HOST`. |
## Pre-mortem
1. **Coder Quickstart template assumes `/var/run/docker.sock`.** Colima
symlinks it on macOS. Podman Desktop on Windows enables Docker socket
compatibility by default, so the template's Docker provider should reach
the daemon without `DOCKER_HOST` gymnastics.
2. **External links into
`quickstart#step-1-install-docker-and-set-up-permissions`.** A grep of
`docs/` and `site/` found no internal references to the old anchor. Blog
posts or external links may land at the top of the page after the
rename; acceptable for this scope.
3. **Brew assumption on macOS.** Recommending `brew install colima
docker` assumes Homebrew. The callout links to brew.sh so users without
it can install Homebrew first.
4. **WSL2 on Windows.** Podman Desktop's onboarding installs WSL2 if
missing. Corporate-managed machines that block WSL2 can fall back to
other runtimes named in the callout.
5. **Onboarding tone shift.** "Container runtime" is more abstract than
"Docker." The callout names Docker Desktop as a runtime first, so the
unfamiliar phrase is anchored immediately.
6. **OrbStack license trap.** OrbStack is intentionally not in the
quickstart's default path because it is paid for commercial use. It will
be mentioned on the future alternatives page with the license caveat
called out explicitly.
## Out of scope (follow-up issues)
- New "Docker runtime alternatives" reference page covering OrbStack,
Rancher Desktop, CLI Podman, with license and compatibility notes.
- `docs/install/docker.md` updates. That page is about installing Coder
server in a Docker container, which is a separate concern.
- Updating the `coder/skills` `setup` skill if its install steps drift
from the new quickstart.
- Updating the Coder Quickstart template's description in
`coder/registry` if it links to the renamed section.
</details>
This pull request was generated by a Coder agent on behalf of
@nickvigilante.