Add a top-level "Get started" docs section to the nav and move the Quickstart to /docs/get-started, with inbound link updates and the install page TIP pointing to the Quickstart.
Filed via Coder Agents on Nick's behalf.
The golden files from #24993 were generated against a stale fork that
predated the `CODER_CLUSTER_HOST` env var addition. This regenerates
them against current main.
All 4 files just need the missing env var block:
```yaml
- name: CODER_CLUSTER_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
```
Fixes the `gen` / `TestRenderChart` failures tracked in
https://github.com/coder/internal/issues/1611#issuecomment-4835998513.
---
*PR created by [Coder Agents](https://coder.com/docs/ai-coder) on behalf
of @bpmct*
closes DEVEX-517
alternative to #26759
This PR is mainly about the type style overrides I added to
`MemoizedMarkdown` in BaseTemplateParametersStep.tsx.
I did also shoehorn some other misc styling updates into this PR, but
they're only scoped to template builder so I'll keep it as 1 PR instead
of making a Graphite stack:
- 1b98ac374f: adjusted `StepDivider`'s
left margin which I missed during #26757
- f1eb921048: refactored
templates/modules' avatars to use an `AvatarData` component, so that the
text appears to the right of the avatar instead of below it
Fix four broken links that caused the weekly-docs link-check CI job to
fail.
**Changes:**
- `docs/install/rancher.md`: Remove `#readme` anchor from
`../../helm#readme` — linkspector splits on `#`, finds a directory, and
errors with EISDIR.
- `docs/install/kubernetes.md`: Same fix for `../../helm/coder#readme`.
- `docs/install/cloud/compute-engine.md`: Point both `gcp-linux` links
to `README.md` explicitly
(`../../../examples/templates/gcp-linux/README.md` and
`../../../examples/templates/gcp-linux/README.md#authentication`) so
linkspector can resolve the file and anchor.
- `.github/.linkspector.yml`: Add `merriam-webster.com` to
`ignorePatterns` (returns 403 from GitHub runner IPs).
<details>
<summary>Linear issue and CI context</summary>
**Linear issue:**
https://linear.app/codercom/issue/DOCS-494/fix-broken-links-in-weekly-docs-link-check
**Failing CI run:**
https://github.com/coder/coder/actions/runs/28366176335/job/84032582533
The workflow is `.github/workflows/weekly-docs.yaml`, job `check-docs`,
step `Check Markdown links` (umbrelladocs/action-linkspector).
Root causes confirmed per investigation:
- `#readme` anchors on directory paths trigger EISDIR in linkspector's
local resolver.
- The `gcp-linux` directory links needed explicit `README.md` targets;
linkspector cannot resolve bare directory references.
- `merriam-webster.com` blocks GitHub runner IPs with 403.
`ignorePatterns` is reserved for external links only, not internal or
GitHub file links.
</details>
---
*Generated by Coder Agents on behalf of @nickvigilante*
The Quickstart template offered a "VS Code Desktop" editor option backed
by the `vscode-desktop` module, which produced two "VS Code Desktop"
buttons in the workspace view.
Coder already enables the VS Code Desktop display app for every
workspace by default via [`DefaultDisplayApps()` in
`provisionersdk/agent.go`](https://github.com/coder/coder/blob/c782cbce77b08c0d031a7131a05f5babd762519f/provisionersdk/agent.go#L58-L65)
(`Vscode: true`), so the module was redundant and is the source of the
duplicate.
This removes the `vscode-desktop` module and its editor option, and
updates the README to note that VS Code Desktop is available by default.
The browser-based `code-server`, Cursor, JetBrains, Zed, and Windsurf
options are unchanged. `examples.gen.json` is regenerated.
Closes#26788
---
*Generated by Coder Agents on behalf of @bpmct.*
`TestWorkspaceTagsTerraform` runs a real terraform provisioner but
waited on builds with `coderdtest` helpers whose deadlines are sized for
the echo provisioner used by most tests, which replays canned responses
and completes in well under a second. On Windows runners, where
terraform providers are not cached and every `terraform init` downloads
from the registry, template imports exceeded the 25s budget in
`AwaitTemplateVersionJobCompleted` and workspace builds exceeded the 10s
context in `AwaitWorkspaceBuildJobCompleted`, even though the test
intends a 120s budget.
Add `AwaitTemplateVersionJobCompletedWithTimeout` and
`AwaitWorkspaceBuildJobCompletedWithTimeout`, which take a
caller-provided wait bound, and use them in the test with
`2*testutil.WaitSuperLong` (120s). Also fix
`AwaitWorkspaceBuildJobCompleted` creating a `WaitShort` (10s) context
while polling for `WaitMedium` (15s), which guaranteed `context deadline
exceeded` errors for the final five seconds of polling.
`TestWorkspaceTemplateParamsChange` has the same shape (real terraform
provisioner, 120s test context, plain await helpers) and the same latent
bug, so it gets the same fix.
Closes https://github.com/coder/internal/issues/1470 (Linear: PLAT-176)
<details>
<summary>Root cause analysis</summary>
Two CI failures, same mechanism:
- 2026-04-16 (run 24493089585, windows-2022):
`overrides_with_dynamic_option_from_var/dynamic` failed at
`coderdtest.AwaitTemplateVersionJobCompleted` with `Condition never
satisfied ... make sure you set IncludeProvisionerDaemon!`. The template
import job (real terraform init/plan, with network provider download)
did not complete within `WaitLong` (25s).
- 2026-05-27 (run 26492817796, windows-2022): `tag_param/dynamic` failed
at `coderdtest.AwaitWorkspaceBuildJobCompleted` with `failed to get
workspace build ...: context deadline exceeded`. The helper's internal
context was `WaitShort` (10s) while its polling window was `WaitMedium`
(15s), so after 10s every poll could only fail. The logged `terraform
apply: exit status 1` and the `TempDir RemoveAll ... Access is denied`
cleanup error are consequences of test teardown canceling the in-flight
job while the provider exe was still file-locked.
The test declares a 120s budget (`2*testutil.WaitSuperLong`, commented
"This can take a while"), but the await helpers ignored it and applied
their own 10-25s budgets. `testutil.CacheTFProviders` is a no-op on
Windows, so real builds are much slower there.
This change raises the ceiling for the tests rather than making
terraform faster; both observed failure signatures are eliminated. The
default helper budgets are unchanged for the ~880 existing call sites.
One small behavior change: `AwaitTemplateVersionJobCompleted` previously
marked the test failed on any transient poll error via `assert.NoError`;
it now logs and keeps polling, matching the workspace build helper, and
still fails on timeout.
`TestWorkspaceTemplateParamsChange` is the sibling real-terraform test
in the same file (also covered by the original provider-caching work in
#20603). It runs three sequential real builds with the plain await
helpers under a 120s context, so it is exposed to the same Windows
slowness even though it has not produced its own issue yet. Its context
is raised to `6*testutil.WaitSuperLong` to outlast three sequential
await budgets.
API note: a context-taking variant was considered first, but a
`time.Duration` parameter avoids an implicit "context must have a
deadline" contract and matches how the existing helpers manage their own
wait budgets.
</details>
---
🤖 This PR was generated by Coder Agents on behalf of @jscottmiller.
Groups the agent-related AI settings pages under a new **Coder Agents**
parent in the sidebar, with a continuous left rule connecting the
children and an active-segment indicator that lights up the rule where
the current sub-item sits.
The new nav order:
- AI Governance
- AI Gateway keys
- Providers
- Coder Agents
- Models
- MCP servers
- Templates
- Spend
- Instructions
- Lifecycle
All target pages already exist on main (Danielle's recent migrations of
Models, MCP servers, Templates, Instructions, Lifecycle, Spend, and
Coder Agents into AI Settings). This PR only changes the sidebar visual
structure: the children move into an indented group with a `border-l
border-l-border` rule, and the active child paints a
`border-l-content-primary` segment over that rule via `-ml-px` so the
rule and indicator share a column instead of stacking.
<details>
<summary>Design notes</summary>
Concept 1 from the earlier exploration: always-expanded with indents,
the parent is its own page. Chosen because it adds no expand/collapse
state, no "which child is the default" question, and no animation work;
the parent reuses the existing nav-item, and the children sit in a
wrapper `div` with a left rule. The site bundle ships without Tailwind's
preflight, so the wrapper and sub-item borders are paired with
`border-solid` to actually paint, matching the pattern already in
`Sidebar.tsx`.
</details>
---
_This PR was prepared by Coder Agents on behalf of @tracyjohnsonux._
Rename user-facing "AI Bridge" strings to "AI Gateway" in deployment
config, RBAC display names, log messages, error strings, docs style
guide, and Grafana dashboard README.
Deprecated option names and descriptions (the `--aibridge-*` block) are
intentionally kept as "AI Bridge". The `Name` field cannot be renamed
because `serpent` uses it as a unique key during JSON serialization;
duplicating names causes `UnmarshalJSON` failures (e.g. in the support
bundle). Descriptions also stay as "AI Bridge" to avoid confusion
between the deprecated and primary options.
Refs https://linear.app/codercom/issue/AIGOV-226
> Generated with the assistance of Coder Agents (@ssncferreira)
Closes [AIGOV-455](https://linear.app/codercom/issue/AIGOV-455/extend-drpc-with-buildproviders).
## Why
The AI Gateway (`aibridged`) is being split into a standalone process that must not touch the database. `coderd` stays the source of truth and seeds the `ai_providers` / `ai_provider_keys` tables from the environment. This PR adds a DRPC call so the gateway fetches provider config from `coderd` instead of reading the DB, for both the embedded and standalone daemons.
## What
- **Proto:** new `ProviderConfigurator` service with a unary `GetAIProviders` RPC, plus `AIProvider` / `AIProviderBedrock` messages. `CurrentMinor` bumped to 1 (additive).
- **Server (`coderd/aibridgedserver`):** `GetAIProviders` runs a read-only `InTx` under `LockIDAIProvidersEnvSeed` so it never returns a mid-seed snapshot, reads providers (incl. disabled) plus keys for enabled ones, and maps to proto under `dbauthz.AsAIBridged`. Unmappable rows are skipped and logged; plaintext keys and Bedrock secrets are never logged.
- **Client:** `DRPCProviderConfiguratorClient` wired into the client union, `dialer.go`, and `CreateInMemoryAIBridgeServer`.
- **cli:** `BuildProvidersFromProto` maps the response through the existing DB-neutral `buildProvider`. A shared `poolRPCReloader` does the fetch/build/replace for both daemons: the embedded daemon reloads on every `ai_providers` change and fails startup if it cannot subscribe; the standalone gateway drives the same reloader once at startup, retrying until success and staying interruptible.
- **Dead code removed:** `BuildProvidersFromConfig`, `ProvidersFromConfig`, `AIProviderFromConfig`, and the DB-read `BuildProviders` path.
Drops provider-executed tool history (calls and results) from
assistant rows whose producing provider ID differs from the target turn's
provider ID, before the prompt is built. Same-provider history is left
untouched, so normal `web_search` replay is unaffected.
- When a model config has an `AIProviderID`, use this as identity so two
providers of the same type (e.g. two `openai-compat` providers at
different base URLs) are correctly distinguished. Falls back to
the normalized provider type name.
- Sanitization runs at the `database.ChatMessage` row level in
`prepareGeneration`. The `chatloop`
pre-request and reload paths are untouched.
- Foreign provider-executed results are dropped and not converted
to text.
- Unknown origin (unresolvable `ModelConfigID`) fails closed (strip).
- Adds tests for the pure `stripForeignProviderExecutedToolRows`.
- Adds unit tests for `modelConfigProviderIdentity`.
_This pull request was created by Coder Agents on behalf of @johnstcn._
When adding chatd tests to route through a real in-process `aibridged`
daemon (#26658), found two races:
- **Pool:** `CachedBridgePool.Shutdown` calls `cache.Close()` while an
in-flight `Acquire` runs `cache.Wait()`. ristretto closes the channel
`Wait` sends on.
- **Bridge:** `RequestBridge.ServeHTTP` does `inflightWG.Add(1)` after
the `b.closed` check, racing `Shutdown`'s `inflightWG.Wait()`.
## Fix
- `RequestBridge`: adds `admitMu` RWMutex to order `inflightWG.Add`
(ServeHTTP, read) before `close(b.closed)` (Shutdown, write).
- `CachedBridgePool`: adds `opsMu` + `opsWG` so `Shutdown` drains
in-flight `Acquire`/`ReplaceProviders` before `cache.Close()`
- Adds tests `TestRequestBridgeShutdownAdmissionRace` and
`TestPoolShutdownReplaceProviders` for above. (Note:
`TestRequestBridgeShutdownAdmissionRace` leverages a `serve_admission`
quartz trap added to `RequestBridge`).
---
> 🤖 Created by Coder Agents on behalf of @johnstcn.
- `site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts`):
`providerFormValuesToCreate` returns `type: "bedrock"` when the form
type is `bedrock`, instead of `type: "anthropic"` with Bedrock settings.
- Updated `providerFormApiMap.test.ts` to expect `type:
"bedrock"` and `MockAIProviderBedrock` fixture in `entities.ts` to
`type: "bedrock"`. Added Bedrock fixtures to `ModelsPageView` stories
asserting the AWS Bedrock icon renders correctly.
Refs: [CODAGT-549](https://linear.app/codercom/issue/CODAGT-549/bedrock-models-use-anthropic-styling-and-icons)
> 🤖
Migrates the CreateUserPage tests from vitest to Storybook play-function
stories.
The old `CreateUserPage.test.tsx` rendered the full page through
`renderWithAuth` and MSW, which is slow and contributes to `test-js`
timeout flakes. The new stories seed the react-query cache directly and
assert the same behavior in `play` functions, so they run in the
Storybook test lane instead of the vitest `unit` project.
Coverage is preserved: a success story asserts the success toast after
creating a user, and an error story asserts that an API failure surfaces
in the form's error alert.
Relates to CODAGT-686
Relates to https://github.com/coder/internal/issues/1598
Update the group AI budget settings UI to match the unlimited,
no-budget, and finite-budget states. The field now uses a USD input
suffix, the label reads "Monthly limit per member", and unlimited or $0
budgets show explanatory helper text with an info alert.
Adds an avatar URL field to the admin **Edit user** page, available only
for users whose login type is `password` or `none`.
For identity-provider login types (`github`, `oidc`) the avatar is
synced from the IdP on every login, so the field is hidden and the API
ignores any submitted avatar to avoid confusing overwrites.
The field reuses the same emoji picker + URL input (`IconField`) already
used for template, group, and organization icons.
A follow-up PR will add the same control to the self-service Account
settings page.
<details>
<summary>Implementation plan & decisions</summary>
**Goal:** Let an admin set/clear a user's avatar from the Edit user
page, gated to `password`/`none` login types.
**Backend**
- Add `avatar_url` to `codersdk.UpdateUserProfileRequest`.
- `putUserProfile` applies the submitted avatar only for
`password`/`none`; otherwise it preserves the existing (IdP-synced)
value.
- Regenerated TS types and API docs via `make gen`.
**Frontend**
- `EditUserForm` renders an `IconField` ("Avatar URL") when the login
type allows it.
- `EditUserPage` passes the avatar value and a `canEditAvatar` flag.
- `AccountPage` round-trips `avatar_url` so the shared request type
doesn't wipe avatars on the self-service path.
**Gating** is enforced in both the UI (field hidden) and the backend
(submitted value ignored for IdP login types).
**Tests/stories:** backend `TestUpdateUserProfile` covers apply
(password) and ignore (SSO); `EditUserForm` stories cover the
shown/hidden states with interaction tests.
</details>
---
> Generated by Coder Agents on behalf of @aslilac.
ref DEVEX-517
Some small preliminary UI spruce-ups to get each step closer to the
design before implementing the main feature for DEVEX-517. See commit
messages for individual changes.
The most prominent change is making it so that every step gets wrapped
in this rounded/bordered/padded container. Previously it was present
only in `ModuleSettingsStep` and `TemplateCustomizationsStep`; but
missing from `BaseInfraSelectStep`, `BaseTemplateParametersStep`, and
`ModuleSelectStep`
<img width="1840" height="1191" alt="image"
src="https://github.com/user-attachments/assets/f143a6b1-abdf-4103-8041-cd6e1431c856"
/>
## Problem
#26637 caps each locally-executed tool result (built-in,
global/deployment MCP, workspace MCP) at a per-result byte budget
derived from the model's context window. The budget was `ContextLimit/2
* 4 bytes` — i.e. **half the window at an optimistic 4 bytes/token**.
On large-context models that is far too generous. With a
`1,000,000`-token `ContextLimit` the per-result cap is **~2 MB**. A user
hit exactly this with a chatd (deployment-pinned) MCP tool: the result
was truncated to **1,998,709 characters** and still overflowed the
prompt. 2 MB of dense text (JSON/logs/code) is ~650k–1M tokens — most or
all of the window for a *single* result — so the cap fired but didn't
actually prevent the overflow.
## Fix
Tighten the two budget constants in `tooltruncate.go`:
| constant | before | after |
| --- | --- | --- |
| `toolResultContextDivisor` | `2` (½ window) | `3` (⅓ window) |
| `bytesPerTokenEstimate` | `4` | `3` (conservative) |
The budget becomes `ContextLimit/3 * 3 ≈ ContextLimit` bytes:
| ContextLimit | before | after |
| --- | --- | --- |
| 1,000,000 | ~2 MB | ~1 MB |
| 200,000 | ~400 KB | ~200 KB |
| unknown (≤0) | 64 KB | 64 KB (unchanged) |
The 16 KB floor and 64 KB unknown-window default are unchanged. A
conservative bytes-per-token estimate is intentional: dense payloads run
well under 4 B/tok, so a lower estimate yields a smaller byte budget
that is less likely to underestimate the true token cost.
No behavioral code paths change — only the two constants and their doc
comments. The existing `tooltruncate_internal_test.go` cases derive
their expectations from the constants (`LargeWindow`) or exercise the
floor/default (`BelowFloor`, `Unknown`), so they remain green.
<details>
<summary>Investigation notes</summary>
Global/deployment MCP tools (`mcpclient.ConnectAll`) are appended to
`prepared.Tools` and execute locally via `ExecuteLocalTools →
executeTools → executeSingleTool`, so the #26637 cap *does* apply to
them for text results (`convertCallResult` joins text content into
`resp.Content`). The cap was simply too large:
`toolResultByteBudget(ContextLimit)` = `ContextLimit/2*4` ≈ 2 MB for a
1M-token window. Reverse-engineering the reported `1,998,709` truncated
characters confirms a `ContextLimit` of ~1,000,000 tokens.
Known gaps left for follow-ups (out of scope here):
- **Per-step aggregate is unbounded.** MCP tools advertise `Parallel:
true` and `executeSingleTool` caps each result independently, so N
parallel calls in one step can sum to N × the per-result cap.
- **Binary/media `Data` bypasses the cap.** Only the text payload is
bounded; `image`/`media`/blob embedded-resource results are
base64-encoded untouched in `executeSingleTool`.
- **Compaction is reactive.** It is gated on the prior step's reported
usage (`latestPromptUsage`), so it can't pre-empt a single large result
appended on the current step.
</details>
---
Generated by Coder Agents on behalf of @kylecarbs.
Add `Organization` as a first-class field to `WorkspaceFilter` so Go SDK
callers can filter workspaces by organization name or UUID without
constructing a raw `FilterQuery` string.
The backend already supports `organization:` as a search parameter via
`searchquery.Workspaces()`. This change surfaces it consistently
alongside the existing `Owner`, `Template`, and `Status` fields.
Closes https://github.com/coder/coder/issues/21545
Renames the `last_used_at` column to `last_heartbeat_at` in `ai_gateway_keys` table.
`ai_gateway_keys` table has not been released yet.
All references updated.
The MCP Registry rejects our `server.json` remote because the URL uses
`{coder_url}` as the entire base. Registry validation requires remote
URLs to literally begin with `https://`, and template variables are only
allowed after the scheme/host. The previous value
(`{coder_url}/api/experimental/mcp/http`) fails both the JSON schema
`^https?://[^\s]+$` pattern and the semantic remote-URL check.
## Changes
- Use `https://{coder_hostname}/api/experimental/mcp/http` with a
`coder_hostname` variable (users now enter a hostname like
`coder.example.com` instead of a full URL).
- Update the VS Code registry instructions in
`docs/ai-coder/mcp-server.md` to ask for the deployment hostname.
Verified with `mcp-publisher validate` against
`registry.modelcontextprotocol.io`:
```
Validating against https://registry.modelcontextprotocol.io...
✅ server.json is valid
```
This was caught by running the `Publish to MCP Registry` workflow in
validate-only mode (`publish: false`) before any real publish, so
nothing broken reached the public registry.
<details>
<summary>Root cause detail</summary>
The registry validator (`internal/validators`) substitutes known
template variables, then parses the URL. Because `{coder_url}` replaces
the whole scheme+host, the parsed URL has no scheme and is rejected as
an invalid remote URL. Hard-coding `https://` and scoping the variable
to the host satisfies both the schema pattern and `IsValidRemoteURL`
(which also requires `https`). The registry mandates `https` for remotes
regardless, so there is no loss of functionality.
</details>
---
_Generated with Coder Agents._
Adds a new enterprise-only `GET /api/v2/ai-gateway/serve` endpoint that standalone AI Gateway replicas use to connect to `coderd` over a DRPC-over-WebSocket transport, mirroring the existing in-memory path used by the embedded AI Bridge daemon.
- The endpoint upgrades the HTTP connection to a WebSocket, multiplexes it with yamux, and finally serves the three DRPC services (Recorder, MCPConfigurator, Authorizer).
- The `X-AI-Governance-Gateway-Key` header is used for authentication.
- The key is looked up by its hashed secret
- Missing or revoked keys return `401`.
- API version negotiation is enforced via a new `aibridged/proto` version (`v1.0`).
- Incompatible versions return `400`.
- `FeatureAIBridge` entitlement is required.
- Key liveness (`last_used_at`) is recorded immediately on connection and refreshed every 60 seconds while the session remains open.
- When key liveness detects the key was deleted (no rows where updated) session is closed.
#### Small refactors
* The three DRPC service registrations are extracted into `aibridgedserver.Register`, shared by both the in-memory and WebSocket paths.
* The literal `256 * 1024` used as the yamux-aligned WebSocket read limit is replaced with the named constant `drpcsdk.YamuxDefaultStreamWindowSize` in all call sites.
* as noted in review comment https://github.com/coder/coder/pull/26506#discussion_r3461905223 order of `SetReadLimit` and `WebsocketNetConn` calls was fixed.
<!-- Authored by Coder Agents on behalf of @Emyrk. -->
Adds an opt-in `CODER_DANGEROUS_OIDC_EMAIL_FALLBACK` flag (alias
`--dangerous-oidc-email-fallback`) for IdP brokers that do not issue a
stable `sub` for the same user across connections.
## Summary
This adds the necessary configuration to publish Coder's remote MCP
server to the official MCP Registry at registry.modelcontextprotocol.io.
## Changes
- **`server.json`**: MCP server metadata for registry discovery
- **`.github/workflows/publish-mcp-registry.yaml`**: GitHub Actions
workflow to automatically publish on release
## How it works
1. When a new Coder release is published, the workflow automatically
publishes to the MCP Registry
2. MCP clients (Claude, ChatGPT, VS Code, etc.) can discover Coder via
the registry
3. Users just need to provide their Coder deployment URL - OAuth handles
authentication automatically via RFC 7591 Dynamic Client Registration
## MCP Registry Entry
The server will be listed as `io.github.coder/coder` with:
- **Transport**: `streamable-http`
- **Endpoint**: `{coder_url}/api/experimental/mcp/http`
- **Auth**: OAuth2 (automatic via
`/.well-known/oauth-authorization-server`)
## Testing
After merge and next release, verify at:
```bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?q=io.github.coder"
```
Closes#21275
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking:
`medium`_
---------
Co-authored-by: Ben Potter <me@bpmct.net>
Adds DB methods`GetAIGatewayKeyIDByHashedSecret` and `UpdateAIGatewayKeyLastUsedAt`.
`GetAIGatewayKeyIDByHashedSecret` - returns AI Gateway key ID by hashed secret value.
`UpdateAIGatewayKeyLastUsedAt` - updates last used timestamp for given AI Gateway key.
Used by standalone AI Gateway for authentication and keeping track of currently used keys.
Restores the `repo_base_dir` parameter that was removed in #26668,
preserving its original name (`Coder Repository Base Directory`),
default (`~`), description, and `~` → `/home/coder` resolution logic.
Requested by @cian in Slack.
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Cian Johnston <cian@coder.com>
`TestProvisionerd/CloseCancelsJob` closes the daemon inside the
provisioner parse/init callback, which cancels the in-flight
`AcquireJobWithCancel` RPC immediately after the job is delivered. dRPC
can report `context.Canceled` from the server-side `stream.Send` even
after the message was successfully sent, causing a spurious
`assert.NoError` failure.
The tolerance for this race already existed in the `acquireOne` test
helper (added in #17448 for coder/internal#584), but the inline acquire
handlers each carried their own bare `assert.NoError`, so
`CloseCancelsJob` never got it and flaked.
This extracts a single `assertAcquireNoError` helper that tolerates
`context.Canceled` on an acquire-stream `Send`/`Recv` (and fails on any
other error), and routes every `AcquireJobWithCancel` test handler and
`acquireOne` through it. The tolerance now has one definition and cannot
drift out of sync. If a job is never delivered, the affected tests still
fail waiting on their completion channels.
Fixes PLAT-172. Refs https://github.com/coder/internal/issues/1478
<details>
<summary>Investigation and validation</summary>
- The reported failure was `assert.NoError` at
`provisionerd_test.go:112` (commit 615be176) receiving `context
canceled`. The same dRPC behavior is documented in `retryable()` in
`provisionerd.go` and was already handled in
`acquireOne.acquireWithCancel`.
- Prior art: PR #17448 (merged 2025-04-17) added the identical tolerance
to `acquireOne` to fixcoder/internal#584 (`flake:
TestProvisionerd/MaliciousTar`), which failed with the same signature.
That fix did not reach the inline handlers, which is the gap addressed
here.
- The natural timing race did not reproduce locally (~96,000 iterations,
including single-threaded and race-detector runs); it requires the
server `Send` goroutine to be descheduled past cancel propagation, which
only manifests under CI load.
- To validate deterministically, the handler was temporarily
instrumented to surface `context.Canceled` once the stream context
cancelled after a successful `Send`. With a bare `assert.NoError` this
reproduced the exact CI failure (`context canceled` at line 112); with
the tolerance it passed. The instrumentation was reverted.
- Scope note: `CloseCancelsJob` is the only handler with an observed CI
failure (it closes synchronously at acquisition). The other handlers
shut down only after job progress, so their first `Send` is not
realistically racing a cancel; applying the helper there is consistency
and drift-prevention, not a fix for an observed flake. This resolves
review finding CRF-1 in code.
</details>
> Generated by Coder Agents on behalf of @jscottmiller.
Extracts test infrastructure for AI Gateway routing into shared helpers
under a new package `coderd/aibridgedtest` so both AGPL and enterprise
tests can use them.
- aibridgedtest.StartTestAIBridgeDaemon` spins up a real in-process
aibridged daemon wired to fake upstream providers.
- `chattest.MockAIBridgeTransport` is a mock `aibridge.TransportFactory`
for the 3 bare-chatd tests that use `newActiveTestServer`.
> 🤖 Generated by Coder Agents under the eyes of a human.
Closes GRU-69
Adds CODER_CLUSTER_HOST enviroment variable and CLI arg.
I ended up not making it hidden since we'll just have to unhide it later and even when hidden it still shows up in some autogenerated stuff. Might as well just go for it.
I also added it to the helm chart.
We currently have two possible sources of truth for the Bedrock region:
* `cfg.Region`, when explicitly provided (this also covers the case
where the UI parses the base URL and populates `cfg.Region`)
* the region resolved from the AWS environment
An explicitly configured region should always take precedence over the
environment-derived region.
I suggest implementing this resolution policy in the `NewAnthropic`
constructor so that, after initialization, there is a single source of
truth for the resolved region.
relates to GRU-69
Modifies replicasync to handle discovering NATS enabled primary replicas explicitly, and passing that info to the NATS Pubsub.
This PR adds a new deployment value to explicitly represent the host or IP that the replica can be reached on. It isn't wired up to the CLI, but piggybacks on the DERP config for now.
We learn the NATS port directly from NATS at runtime, and propagate it thru replicasync to learn all peers for clustering.
## Problem
The `lint/emdash` check fails on Graphite-stacked PRs. See [this failed
run](https://github.com/coder/coder/actions/runs/28225390084/job/83616080375?pr=26650):
```
Base ref origin/graphite-base/26650 not found locally, fetching graphite-base/26650...
ERROR: could not fetch base ref origin/graphite-base/26650.
ERROR: could not determine base ref.
make: *** [Makefile:768: lint/emdash] Error 1
```
`scripts/check_emdash.sh` resolved its diff base by fetching
`origin/$GITHUB_BASE_REF` and computing a merge-base. Graphite sets
`GITHUB_BASE_REF` to a `graphite-base/<n>` ref that is ephemeral (it is
not reliably present on origin), so the fetch fails and the check errors
out instead of running.
## Fix
`actions/checkout` checks out the PR **merge commit**
(`refs/pull/<n>/merge`), whose **first parent (`HEAD^1`) is the exact
base commit GitHub merged against**. Diffing `HEAD^1` against the
checkout yields every change the PR makes against its base branch, for
normal and Graphite-stacked PRs alike. No base-branch fetch, no
merge-base computation, no `gh`-based deepen dance.
- `scripts/check_emdash.sh`: use `HEAD^1` (the PR base commit) as the
diff base in CI. Drops `resolve_merge_base` and `fetch_base_ref`. Emits
a clear error if `HEAD^1` is missing (checkout too shallow).
- `.github/workflows/ci.yaml`: bump the `lint` job checkout to
`fetch-depth: 2` so `HEAD^1` is present with no runtime fetch.
Local dev behavior (merge-base against `origin/main`) is unchanged.
## Verification
- `make lint/emdash`, `make lint/shellcheck`, `make
lint/actions/actionlint` pass.
- Simulated the CI path with `GITHUB_BASE_REF` set: the check resolves
to `HEAD^1` without fetching and still flags an added line containing an
emdash.
<details>
<summary>Why the merge commit's first parent</summary>
For a `pull_request` checkout of `refs/pull/<n>/merge`:
- `HEAD` = GitHub's synthetic PR merge commit
- `HEAD^1` = the exact base commit used for the merge
- `HEAD^2` = the PR head commit
`git diff HEAD^1 HEAD` is the full-tree diff from the base snapshot to
the merged result, i.e. all of the PR's changes against its base. This
is immutable and always local (given depth >= 2), unlike base branch
refs which are mutable and, for Graphite stacks, ephemeral.
</details>
---
This PR was generated by Coder Agents on behalf of @dannykopping.
Update scaletest bridge code to use the new AI Gateway naming and API
paths.
## Changes
- `scaletest/bridge/strategy.go`: Update API URLs from
`/api/v2/aibridge/` to `/api/v2/ai-gateway/` and rename comment from "AI
Bridge" to "AI Gateway".
- `scaletest/bridge/config.go`: Rename comment from "AI Bridge" to "AI
Gateway".
- `cli/exp_scaletest_bridge.go`: Rename user-facing CLI strings (Short,
Long, Description, stderr output) from "AI Bridge" to "AI Gateway".
Refs https://linear.app/codercom/issue/AIGOV-230
> Generated with the assistance of Coder Agents (@ssncferreira)
fixes https://github.com/coder/internal/issues/1602
The `TestValidate/regular` case was failing because it was chaining to a root CA that expired in 2025. We didn't see it until last week because we fake the validation time for the test, but still get the CA certificate itself from the OS. Presumably our CI runners OS got upgraded last week to a version that doesn't ship that CA cert, so we fail to validate, even with the faked time.
I spun up a new Azure instance and grabbed its identity document to update the test, and validated that it chains to a CA that expires in 2038, so we should be good to go for a long time.
I also checked the other test cases, and they had already migrated to the new CA, so don't need to be updated yet.
However, if we want to remove any expired intermediate certificates that are used in the test, we'll have to get new tokens for govcloud at the very least.
I also removed the "TestExpiresSoon" test case because we have been skipping it and _not_ removing expiring intermediates (presumably because of the `TestValidate` test cases. Also it makes no sense to remove intermediates when they are expiring "soon" but have not expired. It poses negligible danger to keep the old intermediates around, since we trust the OS to give us the correct time in production.
Tool errors caused orchestrators to abandon spawned agents. Bare error
responses and the close_agent name framed delegation as one-shot: one
transient failure or timeout ended the work, and the orchestrator had no
way to recover or reuse agents.
Renames close_agent to interrupt_agent with a hidden backward-compatible
alias. wait_agent and message_agent return structured payloads instead
of bare errors, so the orchestrator can retry after a timeout, recover
from an error status, or redirect an idle agent. Adds list_agents so
orchestrators can rediscover spawned agents. Adds root-only
orchestration guidance for error recovery.
## Problem
In `/agents`, the sticky user-message truncation sometimes does not
update as new content arrives. While pinned to the bottom with the
transcript overflowing, several messages (or a streaming response) can
land and the sticky bubble keeps a stale clip height, overflowing and
overlapping the content below it. It only snaps back once you scroll
manually.
## Root cause
`StickyUserMessage` recomputes its clip height (`--clip-h`) and push-up
`top` in an `update()` driven by three triggers: a scroll listener, a
window-resize listener, and a `ResizeObserver` meant to catch the
transcript growing.
The observer watched `scroller.firstElementChild`, but in
`ChatScrollContainer` the scroller's first child is the `flex-1 basis-0`
spacer that pins content to the bottom, not the content wrapper. That
spacer collapses to `0px` the moment the transcript overflows (exactly
when truncation engages) and then never resizes again, so the observer
goes silent.
The other triggers do not cover this case either: in `flex-col-reverse`
the `scrollTop` stays at `0` while pinned to the bottom, so no scroll
event fires as content grows. The result is a stale `--clip-h` until the
next manual scroll.
## Fix
- Observe the real content wrapper instead of the collapsing spacer. The
wrapper is tagged with `data-chat-scroll-content` (it contains both the
committed timeline and the streaming live tail), and the sticky code
resolves it via `sentinel.closest(...)`, falling back to the previous
node only if the marker is absent.
- Recompute the scroller geometry (`scrollerTop`/`scrollerHeight`)
inside `update()` on every tick instead of caching it at effect setup,
so the clip and push-up math cannot drift when the scroller moves or
resizes without a window resize (for example the composer growing). This
also removes the now-redundant `onResize` handler.
No change to the sticky visuals or the rAF throttling.
## Testing
- New story `StickyUserMessageClipUpdatesWhilePinned` grows the
transcript while pinned (no scroll dispatched) and asserts the clip
tracks the new geometry, plus structural guards that the observed node
is the content marker and not the `aria-hidden` spacer.
- Verified as a true regression guard: with the fix reverted the new
story fails; with the fix it passes. The existing
`StickyUserMessagePinsOnScroll` is unaffected.
- `biome check`, `tsc -p .`, React Compiler check, emdash check, and
`vitest --project=storybook` for this stories file all pass (48/48).
<details>
<summary>Decision log</summary>
- Considered centralizing the per-message scroll/resize/observer wiring
into a single coordinator in `ConversationTimeline` (it already
centralizes sentinels) to cut N observers/listeners down to one.
Deferred as a follow-up to keep this PR a surgical, low-risk fix; this
change alone resolves the staleness.
- Chose a semantic `data-chat-scroll-content` marker over reusing the
`chat-timeline-wrapper` test id so runtime behavior does not depend on a
test-only attribute. The marker sits on the wrapper that contains both
the timeline and the live tail, so streaming growth is observed too.
- Kept the `scroller.firstElementChild` fallback so other
`ConversationTimeline` consumers and stories without the marker keep
working.
</details>
---
Filed via Coder Agents on behalf of @kylecarbs.
Required external auth (`optional = false`) was only enforced by
client-side preflight checks, so creating a workspace via the REST API
succeeded even when the owner had never authenticated, producing a
broken workspace.
`createWorkspace` now validates the workspace owner's external auth
server-side and returns 403 before any row is inserted or prebuild is
claimed. The owner (not the initiator) is checked because build-time
token injection uses their links, so this also covers admin-on-behalf-of
creates and prebuild claims. Use `optional = true` to allow
pre-provisioning for unauthenticated users.
Fixes PLAT-241.
> This PR was generated by Coder Agents on behalf of
@dylanhuff-at-coder.