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.
The Update model button on `/ai/settings/models/:modelId` was enabled on
mount even when the form had not been edited, so it was possible to
submit an unchanged update. This matches the Provider form behavior
already established in #25551 by gating submit on `form.dirty` when
editing.
### Changes
- `ModelForm.tsx` adds `(!isEditing || form.dirty)` to the `canSubmit`
predicate so Update is disabled until the user changes a field.
Add/duplicate flows are unaffected because their existing
`model.trim().length > 0` requirement already enforces user input.
- `ModelForm.stories.tsx` tightens `EditSaveSubmits` to assert the
disabled-on-mount and enabled-after-edit transitions, and adds
`EditUpdateDisabledUntilDirty` covering the case where the user reverts
an edit back to the original value.
### Verification
- `pnpm exec biome check
src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx
src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx`:
clean
- `pnpm exec tsc -p . --noEmit`: clean
- `pnpm test:storybook --project=chromium
src/pages/AISettingsPage/ModelsPage`: 21/21 stories pass (including the
two new dirty-state stories)
- `make pre-commit` via the project git hooks: passed (lint/ts, lint/go,
lint/emdash, lint/agents, lint/check-scopes, build, all green)
> [!NOTE]
> 🤖 This PR was written by Coder Agents on behalf of @tracyjohnsonux
Mirrors the providers page on `/ai/settings/providers`: when the models
table is empty on `/ai/settings/models`, the empty state now renders an
**Add model** dropdown alongside the description so users have an
obvious next step.
## Changes
- `AddModelDropdown` accepts an optional `align` prop (defaults to
`"end"`), so the existing header instance is unchanged.
- The empty state passes a second instance via `TableEmpty`'s `cta` prop
with `align="start"`, matching how `ProvidersPageView` duplicates
`AddProviderDropdown`.
- Updated the `Empty` Storybook story to assert two **Add model**
buttons render (header + empty state).
## Verification
- `pnpm --dir site exec biome check
src/pages/AISettingsPage/ModelsPage/`
- `pnpm --dir site exec tsc -p . --noEmit`
- `pnpm --dir site exec vitest run --project=storybook
src/pages/AISettingsPage/ModelsPage/` (20/20 stories pass, including the
updated `Empty` play)
<details>
<summary>Reference: providers page pattern</summary>
`ProvidersPageView.tsx` already does this with `AddProviderDropdown`:
```tsx
<TableEmpty
message="No providers configured"
cta={<AddProviderDropdown align="start" />}
/>
```
This PR brings the models page in line with that pattern.
</details>
---
> [!NOTE]
> Opened by Coder Agents on behalf of @tracyjohnsonux.
ref: DEVEX-532
During #26627, I think we should've given `ModuleConfiguration` an
`optionalFields` prop in the first place--our one and only usage of
`ModuleConfiguration` doesn't render optional fields in a way that makes
sense as children. Also, `ModuleConfiguration` should be the component
responsible for rendering the collapsible section, not
`ModuleSettingsStep`
This makes Storybook more accurately represent what module config looks
like, since ModuleConfiguration.stories.tsx now shows the optional field
in a collapsible section:
<img width="1840" height="1191" alt="image"
src="https://github.com/user-attachments/assets/2b385ea7-a060-409a-8e84-aa37f0b09c2c"
/>
relates to GRU-69
Adds cluster_host and nats_port to replicas table, to explicitly track NATS routes in the cluster.
I decided to make the NATS support explicit and transport the port number over the replicasync so that different Coder Servers can run on different ports. This is not something customers will typically care about, but is very useful for testing, so that they can all run on localhost within one machine.
I've also gone with a design where the NATS pubsub directly tells replicasync the port number _after_ it opens the socket. This is also very useful for testing because it allows us to have the OS assign the port number at runtime, avoiding races where we fail to bind to a free port.
Removes the following parameters from the dogfood `coder` template that
no one was using:
- `repo_base_dir` (hardcoded to `/home/coder`)
- `res_mon_memory_threshold` (hardcoded to `80`)
- `res_mon_volume_threshold` (hardcoded to `90`)
- `res_mon_volume_path` (hardcoded to `/home/coder`)
- `enable_ai_gateway` (hardcoded to `true`)
- `vscode_channel` (hardcoded to `stable`)
The underlying values match each parameter's previous default, so
existing workspaces continue to function identically — these just don't
appear as configurable inputs anymore.
`ide_choices` is kept per @Emyrk's review.
`var.anthropic_api_key` and `var.openai_api_key` are left declared
because they're still wired up from `.github/workflows/dogfood.yaml`.
They're effectively unused now (AI Gateway is always on) but removing
them would require coordinated workflow changes.
Requested by @kylecarbs in Slack.
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
# Support IAM role assumption for AWS Bedrock in AI Bridge
## Summary
Implements
https://linear.app/codercom/issue/AIGOV-371/support-dynamic-bedrock-assumerole-across-aws-accounts-for-ai-gateway
A Bedrock provider can now be configured with an IAM role to assume.
Before calling Bedrock, the gateway assumes that role via STS and signs
requests with the resulting temporary credentials. Whether the role
lives in the same account or another one is entirely a matter of the
role's trust policy.
## Problem
Many organizations prohibit long-lived AWS access keys and expect
workloads to authenticate through assumed IAM roles instead. A common
case is an organization that runs Bedrock across several AWS accounts,
one per business unit, and needs each unit's usage billed to its own
account by assuming a role there. AI Bridge previously authenticated a
Bedrock provider only with static keys or the gateway's own ambient AWS
identity, which is shared by every provider, with no way to assume a
role. These deployments had no clean path.
## How it works
When a provider is configured with a role ARN, the gateway uses its base
identity to assume that role via STS and signs Bedrock requests with the
temporary credentials it returns. The base identity is whatever the AWS
default credential chain resolves, IRSA, EKS Pod Identity, EC2 Instance
Profile, or static keys.
Credentials are resolved once when the provider is set up and are then
cached and rotated, so individual requests are served from the cache
rather than triggering a new STS call. A deployment that needs several
roles configures several providers, each pointing at its own role.
## Configuration
The role ARN is part of the Bedrock provider settings and is set through
the AI provider API. It is optional: a provider with no role ARN behaves
exactly as before.
## Scope and trade-offs
- This PR is backend only. The settings UI for the role ARN ships in a
follow-up.
- Configuration is not exposed through environment variables.
Environment-based provider configuration is being phased out in favor of
database-managed providers, so the role ARN is intentionally database
and API only.
Follow-up PR: https://github.com/coder/coder/pull/26578
## Problem
Local tool results were persisted and replayed to the model verbatim,
with no size cap. A single oversized result, most often a multi-megabyte
response from an MCP tool, overflows the prompt on the next request.
Every retry rebuilds the same history and fails the same way, leaving
the chat wedged in `error`. Auto-compaction is reactive (token usage is
only known after a response), so it can't catch a single result that
blows the very next request.
## Fix
Cap every locally-executed tool result at its single choke point,
`executeSingleTool` in `chatloop`, so the cap covers built-in tools,
**global (deployment-pinned) MCP**, **workspace MCP**, and provider
runners uniformly. Because this runs before the result is published to
the live stream and before it is committed, the SSE preview, the
persisted message, and the model replay all see the same bounded output.
The budget is token-aware: a single tool result may use at most half the
model's context window (`~4 bytes/token`), with a `16KB` floor and a
`64KB` default when the window is unknown. Truncation keeps the head and
tail of the output and replaces the middle with a marker telling the
model how much was removed and to narrow its query; it is UTF-8 safe and
never exceeds the budget. Binary media `Data` is passed through
untouched (only the text payload is bounded).
A `coderd_chatd_tool_result_truncated_total{provider,model,tool_name}`
counter and a warning log record each truncation.
## Out of scope
- Provider-executed results (e.g. web search) arrive via the stream, not
`executeSingleTool`.
- Dynamic/external tool results submitted through the `/tool-results`
API are validated as JSON elsewhere.
- Cumulative growth across many results is still handled by context
compaction; this change only bounds any single result.
<details>
<summary>Implementation notes</summary>
- New `coderd/x/chatd/chatloop/tooltruncate.go`:
`toolResultByteBudget(contextLimitTokens)` and
`truncateToolResultText(text, maxBytes)` (pure, unit-tested).
- `chatloop.go`: added `ContextLimit` to `ExecuteLocalToolsOptions`;
threaded a computed byte budget through `executeTools` into
`executeSingleTool`, where `resp.Content` is capped for the text,
media-text, and error branches.
- `generation.go`: passes `ContextLimit: prepared.ContextLimitFallback`
(the model's configured context limit).
- `metrics.go`: new `ToolResultTruncatedTotal` counter +
`RecordToolResultTruncated`.
- Tunable knobs live as constants in `tooltruncate.go`
(`toolResultContextDivisor = 2`, `bytesPerTokenEstimate`,
`minToolResultBytes`, `defaultToolResultBytes`).
Verified: `go build ./coderd/x/chatd/...`, `go test
./coderd/x/chatd/chatloop/...`, and the `chatd` test binary compiles.
</details>
---
Resolves CODAGT-678
Generated by Coder Agents on behalf of @kylecarbs.
Agents can now attach any file type as a downloadable chat artifact,
where previously the stored-file allowlist rejected types like `.zip`.
The reason arbitrary types were blocked is that a single media-type list
(`codersdk.AllChatAttachmentMediaTypes`) was doing three different jobs
at once: gating what users may upload as prompt input, deciding what is
safe to render inline in the browser, and admitting what the agent's
`attach_file` could store. Because the agent storage path reused that
same list as an admission gate, any artifact outside it was rejected
even though agent artifacts are only ever downloaded by the user and are
never forwarded to the model, so the prompt-input and inline-render
constraints did not actually apply to them.
This splits those concerns. `PrepareStoredFile` now only normalizes the
name and classifies the bytes, and the prompt-input allowlist is
enforced inline at `postChatFile` instead, which is the correct layer
for user-provided input.
User uploads are unchanged and still limited to the allowed prompt-input
media types, and unsafe or unknown types remain download-only because
`IsInlineRenderableStoredMediaType` still refuses to render them inline.
Model replay is also unchanged: assistant and tool attachments are never
forwarded to the LLM.
Closes CODAGT-654
## Problem
PR #23109 introduced port normalization for the private IP blocking
feature, which mutated `CoderAccessURL.Host` to always include the
default port (e.g. `coder.example.com:443`). This leaked into the `Host`
header of every request forwarded to the Coder server.
When `CODER_REDIRECT_TO_ACCESS_URL=true`, the `redirectToAccessURL`
middleware compared the `Host` header literally against the access URL
(`coder.example.com`), saw a mismatch, and returned a 307 redirect to
the Coder dashboard HTML page.
Copilot then received HTML instead of JSON:
```
Failed to start MCP client: Streamable HTTP error: Unexpected content type: text/html; charset=utf-8
Failed to load custom agents: SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
```
## Changes
- Stop mutating `coderAccessURL.Host`; store the resolved port in a
separate field for `isBlockedIP`
- Update existing tests that asserted the old (mutated) `.Port()`
behavior
- Add test cases verifying the Host is preserved with and without an
explicit port
> Generated with the assistance of Coder Agents on behalf of
@ssncferreira
Fixes CODAGT-447.
Alternative implementation of https://github.com/coder/coder/pull/26212
and https://github.com/coder/coder/pull/25978
- Adds up to the first 1000 characters of `README.md` (with leading
frontmatter stripped) to `chattool.list_templates` output
- Adds up to 800 characters of `README.md` to `chattool.read_template`.
**Note:** skipping `toolsdk` versions to keep scope small.
> 🤖 Generated by Coder Agents
Bumps `github.com/coder/boundary` from pseudo-version
`v0.8.4-0.20260304164748-566aeea939ab` to `v0.9.0`.
This picks up the fixes included in the [v0.9.0
release](https://github.com/coder/boundary/releases/tag/v0.9.0),
notably:
- feat: report drop counts to agent via BoundaryStatus
- fix: preserve percent-encoded path when forwarding
Relates to https://linear.app/codercom/issue/AIGOV-424
> Generated by Coder Agents on behalf of @SasSwart
Follow-up to #26584.
After the nixpkgs 25.05 update, the default go-migrate package panics at
startup due to its Snowflake driver before Postgres commands can run.
Coder only uses the migrate CLI for migration creation and local Postgres
migrations, so override the build tags to include only the Postgres driver.
## Description
Moves frontend routes from `/aibridge` to `/ai-gateway` and adds client-side redirects so existing bookmarks and deep links continue to work.
## Changes
- Move React Router paths from `/aibridge` to `/ai-gateway`
- Add `<Navigate>` redirects from `/aibridge`, `/aibridge/sessions`, and `/aibridge/sessions/:sessionId`
- Update `navigate()` calls and `Link` components to use new paths
Closes https://linear.app/coder/issue/AIGOV-233
> Generated with the assistance of Coder Agents (@ssncferreira)
## Description
Updates frontend and Go SDK client URLs from `/api/v2/aibridge/*` to `/api/v2/ai-gateway/*` to match the new route aliases introduced in #26475.
## Changes
- Update `site/src/api/api.ts` to call `/api/v2/ai-gateway/*` for all AI Gateway endpoints
- Update `codersdk/aibridge.go` type comment to reference the new path
- Regenerate `site/src/api/typesGenerated.ts`
Closes https://linear.app/coder/issue/AIGOV-230
> Generated with the assistance of Coder Agents (@ssncferreira)