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.
Migrate `wg.Add(1); go func() { defer wg.Done(); ... }()` to
`wg.Go(func() { ... })` in tests.
Where the prior pattern passed the loop variable explicitly via a
closure parameter (`go func(id int) { ... }(i)`), drop the parameter and
reference the loop variable directly. Per-iteration loop variables since
Go 1.22 make this safe.
Replace the `docs/.style/style-guide.md` scaffold with the populated
prose style guide,
structured as a `README.md` landing page plus one subpage per topic so
GitHub auto-renders the landing when readers open the style-guide
folder.
## Layout
```text
docs/.style/
style-guide/
README.md (landing: intro, section list, editing conventions, Vale enforcement)
audience-and-scope.md (one audience, one outcome, declared up front; canonical personas)
voice-and-tone.md
word-choice.md
accessibility-and-inclusion.md (new)
capitalization-and-punctuation.md
formatting.md (text formatting + block elements + screenshots sparingly)
numbers-units-and-dates.md
editor-setup.md (placeholder)
```
Every repo reference to the old path is rewired to the new path:
`AGENTS.md` (and its `CLAUDE.md` / `.cursorrules` symlinks),
`.claude/docs/DOCS_STYLE_GUIDE.md`,
`docs/about/contributing/documentation.md`, `docs/.style/README.md`,
`docs/.style/styles/Coder/README.md`, and a comment in
`.github/workflows/ci.yaml`. The touched paragraph in each of those
files is reformatted to one sentence per line per the touch-paragraph
rule (refer to [Conventions the guide
dogfoods](#conventions-the-guide-dogfoods)).
## What each page covers
- **Audience and scope** (new): every page targets **one audience
working toward one outcome**; the **install-vs-deploy Coder example**
(workspace user vs platform engineer); pick one audience per page (write
two pages and cross-link rather than tagging sections); pick one outcome
per page (`Configure SSO with Okta` is one outcome, `Configure SSO` is
not); declare audience and scope up front (the H1 names the outcome; the
first paragraph names the audience); **canonical Coder personas**
inlined as four primary (Dave the Developer, Ada the Infrastructure
Admin, Perry the Platform Engineer, Steven the Sponsor) and six
secondary (Melissa the Machine Learner, Tommy the Tester, Caitlin the
Citizen Developer, Felipe the FinOps, Sergio the Security Officer, Tara
the Team Leader), each with a `Coder surface:` line covering the
relevant CLI/workspace/template/RBAC surfaces.
- **Voice and tone**: address the reader directly, avoid first-person
singular, reserve first-person plural for **Coder Technologies the
company** (with an explicit ban on `we` for the product itself and on
combined `you and the docs`), active voice, present tense with a
**conditional/predictive `will` exception** (`If you do X, Y will
happen`), **no sentence-ending prepositions** with a clunky-exception
note.
- **Word choice**: Coder product and feature names with the **Coder CLI
always in backticks (`coder`)** rule, brand names with a parallel
**Terraform CLI in backticks (`terraform`)** rule, **Dev Container**
terminology (proper-noun specification vs lowercase instance, parallel
to Coder / workspace), **phrasal verbs and their noun forms generalized
as a table** (set up/setup, log in/login, sign in/sign-in, log
out/logout, back up/backup, roll out/rollout, start up/startup, shut
down/shutdown, with the `Quickstart` exception), `refer to` / `check
out` / `visit` over `see`, `Learn more` versus `Next steps` with an
**ableism rationale** (`steps` as a physical-mobility metaphor),
`tutorial` versus `walkthrough` with an **ableism rationale**,
**`select` over `click`**, **`Don't assume simplicity or
difficulty`** (covers both `simple`/`easy` and `complex`/`non-trivial`),
**`Avoid weasel words`** (vague attributions in the Wikipedia sense like
`many believe`, `experts agree`, `studies show`), plain language for
product actions with an **industry-term exception scope** for the Linux
`kill` command, the `SIGKILL` signal, and the `disabled` config flag
state.
- **Accessibility and inclusion** (new): WCAG 2.1 Level AA as the
minimum target with AAA as a stretch goal; heading structure (one H1 per
page, no skipped levels, **substantive content between headings**);
inclusive pronouns; inclusive-language substitutions including a
**dedicated `sanity check` row** with `smoke testing` / `confidence
testing` / `acceptance testing` alternatives; descriptive link text; alt
text and decorative-image conventions; **plain English for international
readers** (no idioms; common Latin abbreviations `e.g.`, `i.e.`, `etc.`,
`vs.`, and `et al.` allowed, less common ones not); page descriptions in
`docs/manifest.json` (the docs site does not yet support YAML front
matter); reading level; color contrast deferred to the docs site theme.
- **Capitalization and punctuation**: sentence-case headings, no
gerund-leading headings with **documented exceptions** (`Pricing`,
`Billing`, `Logging`, `String formatting`, etc.), **trailing heading
punctuation in three tiers** (periods and exclamation marks forbidden at
error severity, question marks allowed sparingly at suggestion severity,
characters inside backticks exempt for both), no em or en-dashes with a
**corrected example** showing parenthetical em-dash use rather than
series-joining, Oxford comma, US-style quotation, semicolons sparingly,
rare exclamation marks, numeric ranges.
- **Formatting**: text formatting (bold for UI with **explicit
greater-than separator rule for navigation paths**, italics for
emphasis, code font for identifiers presented as a **bulleted list**)
and block elements (code blocks with language fences plus **link to the
Prism supported-languages reference**, callouts with tightened
scenarios, tabs with the actual `` syntax and a **macOS/Linux/Windows
example**, lists with a **five-item prose-list cap rule** and an
**explicit terminal-punctuation rule** (complete sentences end in
periods, phrases completing a lead-in paragraph end in periods,
single-word labels carry no terminal punctuation, no mixing styles in
one list), tables with a **narrow-table guideline** that reconsiders the
structure when many columns are needed, links including the rule that
**non-docs codebase links also use relative paths**, images,
**screenshots sparingly** with a maintenance-burden rationale and an
adapted quote from Lorna Jane Mitchell's `Short tech writing style
guide for developers`), with cross-references to the accessibility page
for link text and alt text.
- **Numbers, units, and dates**: digits everywhere preference,
non-breaking space between number and unit with **separate pre-render
(Markdown source) and post-render (visible output) demonstrations** plus
a **window-shrink tip** for confirming the rule visually, `Month Day,
Year` date format, 12-hour time with AM/PM, ordinals exception.
- **Editor setup**: placeholder.
## Conventions the guide dogfoods
- **One sentence per line**. Source lines follow a one-sentence-per-line
policy: each sentence sits on its own Markdown source line, sentences
are not split across lines, and lines do not wrap to a fixed column
width. The same convention applies corpus-wide through an **incremental
touch-paragraph rule**: when a contributor edits any line inside a
paragraph, the whole paragraph is reformatted to one sentence per line
as part of the same edit. Bullet items, numbered list entries, and
blockquote lines are each their own paragraph for the rule. Headings,
fenced code blocks, and tables are out of scope. `markdownlint`'s
`MD013` is already disabled, so the convention is editorial.
- **No navigational `see`**. Replaced with **refer to** (formal
default), **check out** (informal/tutorials), or **visit** (external
URLs). `See` is reserved for the observational meaning.
- **HTML entities for em-dashes inside demos**. The em-dash demo encodes
`—` / `–` so the source stays ASCII while the rendered output still
shows the character.
- **No semicolons in body prose**. Body prose prefers two sentences over
a semicolon. Semicolons survive only in heading and rule labels where
they act as separators.
- **Common Latin abbreviations allowed in own prose**. `e.g.`, `i.e.`,
`etc.`, `vs.`, and `et al.` (citation contexts) are fine. Less common
Latin abbreviations (`a priori`, `q.v.`, `viz.`, `n.b.`, `cf.`, `ibid.`)
are not. The rule covers punctuation too: prefer parentheses around
`e.g.` and `i.e.` clauses, one period when `etc.` ends a sentence, both
periods when `etc.` ends a parenthetical that ends a sentence.
- **No idioms or industry-jargon idioms**. `deep dive`, `paved path`,
etc. are rewritten in plain language.
## Rule conventions
Each rule pairs a rationale with **Do** / **Don't** blockquoted
examples and a parenthetical noting the Vale rule that enforces (or will
enforce) the policy. Documentation-only rules are explicitly labeled as
such. Substitution rules use tables.
## Out of scope
- Wiring any new Vale rule. Per-rule PRs land separately per the
rule-authoring doctrine in `docs/.style/README.md`.
- Editor setup page population.
- Redirecting `docs/about/contributing/documentation.md` to the
populated guide (needs a coordinated `coder.com` PR after merge).
- Trimming the `Writing Style` block in
`.claude/docs/DOCS_STYLE_GUIDE.md` and removing the `currently a
scaffold` framing in the agent docs.
- A separate demo PR for the callout types rendered against an existing
docs page.
- Sweeping navigational `see` out of other docs files. The new rule only
dogfoods on the style guide itself; a corpus-wide sweep is a separate
ticket.
## Validation
- `make fmt/markdown`: clean.
- `make lint/markdown`: 0 errors across 494 files.
- `./scripts/check_emdash.sh`: clean.
- Pre-commit-light: passes (fmt + lint + emdash + shellcheck + typos +
actionlint + migrations + helm).
- Dogfood scan: no first-person singular in own prose, no idioms, only
the five allowed Latin abbreviations in own prose, no `walkthrough` or
`Next steps` outside rule definitions and examples, no navigational
`see`, no `click` outside rule definitions and examples, no semicolons
in body prose.
<details>
<summary>CI flake note: <code>check-docs</code> (linkspector)</summary>
The `check-docs` job can fail intermittently on pre-existing external
links in `docs/about/contributing/documentation.md` (lines 29 and 30):
Merriam-Webster occasionally returns HTTP 403 to GitHub Actions runners
and Chicago Manual of Style can time out at 30s. Neither link is touched
by this PR. `docs/.style/` itself is in `.github/.linkspector.yml`
`excludedDirs`, and linkspector annotations confirm zero broken links
from the new pages.
</details>
Resolves DOCS-434.
---
*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
ref DEVEX-491
Adds a `useFuzzySearch` hook based on the logic in IconsPage.tsx, and
Storybook stories for `ModuleSelectStep` to verify filter tab count
behavior.
c16e0d9516 and
b6ee21875c co-written with Claude Code
The workspaces table shortcuts row selected `resources[0].agents[0]`, so
a sub-agent that ended up first (for example the Claude/Task sub-agent
created on a workspace) could replace the parent agent's launcher icons,
and which apps showed depended on agent ordering.
Select the parent agent (`parent_id === null`) of the first non-hidden
resource instead, matching the convention already used on the workspace
detail page (`Workspace.tsx`). This keeps the shortcuts row
deterministic and excludes sub-agent apps.
Refs
[DEVEX-459](https://linear.app/codercom/issue/DEVEX-459/aggregate-workspace-table-shortcuts-across-all-agents)
<details>
<summary>Decision context and scope</summary>
Per the discussion on DEVEX-459, this is the agreed short-term fix:
> In the short term, we should display only apps from the parent agent
and make the behavior deterministic, rather than the current reported
behavior of showing apps from the first discovered agent.
Out of scope (tracked as a longer-term backlog item on DEVEX-459):
- Aggregating app shortcuts across multiple agents.
- Changing the 4-slot cap (`WORKSPACE_APPS_SLOTS`).
For workspaces with multiple top-level agents, the first parent agent's
apps are shown. This is deterministic but not aggregated.
A `ParentAgentApps` Storybook story was added (sub-agent listed first)
with a `play` function asserting the parent agent's app renders and the
sub-agent's app does not.
</details>
---
This PR was created by Coder Agents on behalf of @uzair-coder07.
## Problem
Workspace context surfaced in chat (Coder Agents) is incomplete and racy
on a fresh boot:
- The context panel is missing personal skills (only repo-level skills
under `.claude/skills` show up).
- The MCP section lists `.mcp.json` files but no MCP servers are
registered.
- The Issues panel reports instruction files as unreadable, e.g.
`CLAUDE.md (file: unreadable)` and `.cursorrules (file: unreadable)`
with `symlink resolve: lstat .../AGENTS.md: no such file or directory`.
## Root cause
`agentcontext.Manager` collected and pushed context too eagerly:
- `NewManager` ran an eager resolve at agent `init()`.
- `RunPush` starts as a normal connection routine (`startAgentAPI210`)
with no lifecycle gating, so the first snapshot was pushed
(`Initial=true`) as soon as the agent API connected.
Both happened **before startup scripts finish** and before the lifecycle
reaches `ready`. At that point:
- `CLAUDE.md` / `.cursorrules` symlinks to `AGENTS.md` don't resolve
yet, so `EvalSymlinks` fails and the resolver emits `StatusUnreadable`
"symlink resolve" issues.
- Personal skills haven't synced yet, so they're missing.
- MCP servers connect via `mcpManager.Reload(...)` only **after**
`ready`, so only `.mcp.json` configs appear, with no servers.
That partial, error-laden snapshot is persisted by coderd and can
hydrate a chat.
## Fix
Gate `agentcontext.Manager` until the agent is ready, unconditionally:
- The Manager always starts gated. `NewManager` leaves the zero-value
(version 0) snapshot in place and never walks the filesystem; `RunPush`
withholds version-0 snapshots, so nothing reaches coderd.
- The agent calls `Manager.SetReady()` from the lifecycle transition in
`handleManifest`, right after startup scripts finish (`ready`, or
terminal `start_error` / `start_timeout` so a failed startup still
surfaces whatever context exists).
- On `SetReady`, the Manager performs the first real resolve (version 1)
and broadcasts it; `RunPush` ships it with `Initial=true`. Later changes
(MCP connect, skill edits) re-resolve and push as before.
Eager resolution before `ready` was the bug, not a mode worth
preserving, so the gate is always on rather than an opt-in option. This
aligns the agent-side push with chatd, which already waits for agent
readiness before loading context. No proto/coderd/DB changes: coderd
simply never receives a pre-ready snapshot.
<details>
<summary>Design notes & decisions</summary>
- **Unconditional, not opt-in.** An earlier iteration added the gate as
an opt-in `ManagerOptions.GateUntilReady`. Since the eager
resolve-on-construct was the defect, the option, the eager first
resolve, and the now-dead `resolveLocked` helper were all removed; the
Manager is always gated until `SetReady`.
- **Version 0 is the pre-ready sentinel.** The gated placeholder is just
the zero-value snapshot (version 0); the first real resolve is version
1, so the push loop withholds anything at version 0. An earlier revision
carried a dedicated `Snapshot.Initializing` bool plus an HTTP `/resync`
field, but the push loop was the only consumer and nothing read the HTTP
field, so both were dropped.
- **Defer, don't retry symlinks.** Transient "unreadable" symlinks are
an artifact of collecting before checkout. Deferring until `ready` fixes
all three symptom classes at once and avoids masking genuine post-ready
errors (a broken symlink at `ready` is still reported).
- **Release on terminal startup states too** (`start_error`,
`start_timeout`), so a failed startup still surfaces whatever context
exists instead of gating forever. On reconnect the Manager instance is
reused and stays ready.
</details>
## Tests
- `agentcontext.TestManager_WithholdsCollectionUntilReady` simulates
collection running before startup finishes (broken `CLAUDE.md` /
`.cursorrules` -> `AGENTS.md` symlinks): asserts the gated snapshot is
the empty version-0 placeholder with no resources and no `unreadable`
issues, and that after `SetReady` (target now present) the inventory
resolves cleanly to a single instruction file with no spurious issues.
- `agentcontext.TestRunPush_WaitsForReady` asserts the push loop ships
nothing while gated even when content exists, then ships the full
inventory with `Initial=true` after `SetReady`.
- `agentcontext.TestManager_SetReadyIsIdempotent` covers the version-0
placeholder before ready, the single resolve to version 1 on `SetReady`,
and idempotency across repeated calls.
- Updated `agent.TestAgent_ContextStatePushed`: the first push now
already contains `AGENTS.md` with `Initial=true` and no `UNREADABLE`
resources (no pre-startup empty/partial push).
Validated on the changed packages: `go test -race
./agent/agentcontext/...`, `go test ./agent/ -run
TestAgent_ContextStatePushed`, `golangci-lint run`, `go vet`, `gofmt`
(all clean).
---
🤖 Generated by Coder Agents on behalf of @kylecarbs.
Workspace skills live on the workspace filesystem, and the agent's read_file
and execute tools already operate there. read_skill now returns "dir", the
absolute skill directory, for workspace skills, so the agent can read or run
bundled supporting files (for example a scripts/ helper) with the workspace
tools. The field is omitted for personal skills, which are database-backed and
have no files. read_skill_file is unchanged.
Generated with Coder Agents on behalf of @kylecarbs.
## Description
Updates documentation to use the new `/api/v2/ai-gateway/` URLs and
`/ai-gateway/` UI paths, following the backend rename in #26475 and
frontend route rename in #26569.
## Changes
- Update URL references across documentation files from
`/api/v2/aibridge/` to `/api/v2/ai-gateway/`
- Update UI path reference from `/aibridge/sessions` to
`/ai-gateway/sessions`
- Update route path references in client setup guides
- Covers client setup guides, authentication, monitoring, proxy setup,
and provider configuration
Addresses
https://github.com/coder/coder/pull/26475#issuecomment-4768351217
Refs https://linear.app/codercom/issue/AIGOV-226
> Generated with the assistance of Coder Agents (@ssncferreira)
closes DEVEX-532
## other changes
just cleaning up typographic styles a bit to match Figma better
- create `TemplateBuilderTitle`/`TemplateBuilderSubtitle` components for
h2+p elements at the top of steps
- left-align switch's description with its label
<!--
If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting.
-->
Fixes ENG-2720
The test was flaky because it tries to send updates to a local MCP server, and then read Workspace updates from a Coderd watch and expected them to be exactly 1:1. The problem is that Coderd is complicated and the watch can send updates for various reasons unrelated to the task status updates, so it isn't always 1:1.
This fix refactors the test to cut Coderd out entirely, and instead push task status updates in via MCP, and then accept them over the `agentsocket` where we assert they are as expected.
Document the optional Role ARN field on Bedrock providers, which has the
gateway assume an IAM role via STS before calling Bedrock. Covers the
permissions the assumed role requires and the trust policy.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promotes `ExperimentMinimumImplicitMember` (Gateway Accounts) from the
unsafe set into `ExperimentsSafe` so that deployments opting in with
`--experimental='*'` enable it, and the experiment is advertised through
the `AvailableExperiments` API used by the dashboard.
<sub>Coder Agents on behalf of @Emyrk.</sub>
Update `@Summary` and `@ID` annotations in
`enterprise/coderd/aibridge.go` from "AI Bridge" to "AI Gateway".
Regenerate swagger docs and API reference via `make gen`.
This was missed in the original API route aliases PR (#26475) which
renamed `@Tags` but not `@Summary` or `@ID` values. The `@ID` must also
change because a test (`assertConsistencyBetweenRouteIDAndSummary`)
enforces that the ID is the kebab-case form of the summary.
Refs https://linear.app/codercom/issue/AIGOV-230
> Generated with the assistance of Coder Agents (@ssncferreira)
## Description
Updates comments, error strings, and documentation within the
`aibridge/` package to use the new AI Gateway naming, following the
backend rename in #26475.
## Changes
- Update `aibridge/provider/provider.go` comment examples from
`/aibridge` to `/ai-gateway` and "AI Bridge" to "AI Gateway"
- Update `aibridge/AGENTS.md` architecture description
- Update `aibridge/README.md` mount path examples from
`/api/v2/aibridge/` to `/api/v2/ai-gateway/`
- Rename "AI Bridge" to "AI Gateway" in comments across `bridge.go`,
`intercept/client_headers.go`, `intercept/responses/base.go`,
`intercept/messages/base.go`, and `intercept/messages/reqpayload.go`
- Update error string in `intercept/responses/base.go` and matching test
assertion
Addresses
https://github.com/coder/coder/pull/26475#pullrequestreview-4544441981
Refs https://linear.app/codercom/issue/AIGOV-226
> Generated with the assistance of Coder Agents (@ssncferreira)
Bedrock rejects legacy `thinking.type=enabled` requests for Claude Opus
4.8 because the model requires adaptive thinking. The AI Bridge Bedrock
shim only recognized Opus 4.7 as adaptive-only, so Opus 4.8 requests
could fall through and produce Bedrock 400 responses.
Add Opus 4.8 to the adaptive-only model detection and cover the regional
Bedrock model ID form with a regression test.
<details>
<summary>Coder Agents disclosure</summary>
This PR was generated by Coder Agents on behalf of @ericpaulsen.
</details>
## Description
Updates dogfood templates to use the new AI Gateway naming and
`/api/v2/ai-gateway` URLs, following the backend rename in #26475.
## Changes
- Update `ANTHROPIC_BASE_URL` and `OPENAI_BASE_URL` from
`/api/v2/aibridge/` to `/api/v2/ai-gateway/`
- Rename user-facing parameter names and descriptions from "AI Bridge"
to "AI Gateway"
Refs https://linear.app/codercom/issue/AIGOV-226/ai-gateway-rebrand-work
> Generated with the assistance of Coder Agents (@ssncferreira)
Adds two nullable booleans to `telemetry.Deployment`:
- `SCIMEnabled`: `true` when `CODER_SCIM_AUTH_HEADER` is set.
- `SCIMUseLegacy`: `true` when `CODER_SCIM_USE_LEGACY` is set.
Both mirror `Deployment.IDPOrgSync`: nullable for backward
compatibility, and report configuration state rather than license
entitlement (#16323).
Lives on `Deployment` rather than `Snapshot` so the existing
`bqDeployment` table on `coder/coder-telemetry-server` gets two columns
instead of a new table.
`SCIMAPIKey` is annotated as a secret and is scrubbed by
`WithoutSecrets` before the config reaches telemetry, so
`DeploymentConfig.SCIMAPIKey` is always empty in production. The
booleans are pre-computed from the pre-scrub `DeploymentValues` in
`cli/server.go` and passed in via `telemetry.Options.SCIMEnabled` /
`SCIMUseLegacy`.
Pairs with
[coder/coder-telemetry-server#43](https://github.com/coder/coder-telemetry-server/pull/43),
which adds the matching `bqDeployment` columns and the manual BigQuery
`ALTER TABLE` step.
---
Generated by Coder Agents on behalf of @Emyrk.
Adds AI budget and Budget type columns to the group members table, shown when
aibridge is enabled and the ai-gateway-cost-control experiment is on. A member's
spend, limit, and source come from an ai_cost_control object embedded in the
group members and groups responses, so no extra request is made.
- Add AI budget and Budget type columns, gated by the aibridge feature and the
ai-gateway-cost-control experiment
- Read ai_cost_control inline from the group and member lists instead of calling
a separate spend endpoint
- Share an AIBudgetUsage component (spend vs budget with severity colors) and an
InfoIconTooltip for the column headers
- When another group governs a member's budget, grey the spend and name that
group in a tooltip; otherwise render the spend (severity-colored) against a
white limit
- Resolve a member's effective group in the AI budget override dialog, marking
only the governing group "(default)" and none when no group governs them
- Defer the override's custom-budget error until the field is touched
Closes AIGOV-291
Move the providers routes into a dedicated providers sub-tree: `/ai/settings/providers`, `/ai/settings/providers/add`, and `/ai/settings/providers/:providerId`.
The old `/ai/settings/:providerId` and `/ai/settings/add` URLs are
removed without backward-compatibility redirects. Bookmarked or shared
links to these paths now return a 404. Creating a provider with id `models` (although unlikely) made it impossible to edit it due to a conflict with the static models route.
Add a new variant `size="lg"` for the `Table` component, and make use of
it in the new AI settings page. This allows us to ensure each table is
using the same implementation and are consistent.