Commit Graph
15280 Commits
Author SHA1 Message Date
Yevhenii Shcherbina f66df86aed feat: add Bedrock Role ARN field in UI (#26578)
Adds a Role ARN field for the Bedrock provider in the UI. When set, the
gateway assumes that IAM role (using the base identity) before calling
Bedrock. The field is optional and non-secret, so it round-trips back
into the form on edit and clears when left blank.

<hr/>

Related PR: https://github.com/coder/coder/pull/26527
Related issue:
https://linear.app/codercom/issue/AIGOV-371/support-dynamic-bedrock-assumerole-across-aws-accounts-for-ai-gateway
2026-06-24 21:48:33 -04:00
TJ 0f4731446b fix(site/src/pages/AISettingsPage/ModelsPage): disable Update model until form is dirty (#26684)
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
2026-06-25 01:01:56 +00:00
TJ 99c0362b26 fix(site/src/pages/AISettingsPage/ModelsPage): duplicate Add model dropdown in empty state (#26685)
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.
2026-06-24 17:58:14 -07:00
Andrew Aquino 612b6d4e95 refactor(site): make optionalFields a prop of ModuleConfiguration instead of children (#26681)
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"
/>
2026-06-24 14:00:28 -07:00
Jon Ayers 29f124a650 fix: resolve client IP from the rightmost untrusted X-Forwarded-For entry (#26646) 2026-06-24 16:00:16 -05:00
Steven Masley c08b04adbc feat: escape composite-literal fields in NameOrganizationPair (#26675) 2026-06-24 14:27:23 -05:00
Andrew Aquino 6a57f4751f feat(site): give SelectionSummary's template/module names secondary text color (#26580)
Figma design:
https://www.figma.com/design/z1qkNiNIya2myaVW4kR6jP/Template-creation-builder?node-id=450-8030&m=dev
2026-06-24 12:13:19 -07:00
Andrew Aquino 63b9388c6c fix(site): open TemplateBuilder template docs in new tab (#26589)
Mainly makes the template builder's external docs/registry links behave
consistently.

Includes some small changes to make copy consistent (sentence casing for
links + period after page header subtitle, sourced from
[Figma](https://www.figma.com/design/z1qkNiNIya2myaVW4kR6jP/Template-creation-builder?node-id=257-906&m=dev))

Also, `noreferrer` has automatically been applied to `target="_blank"`
links in major browsers since 2021 ([source
1](https://frontendmasters.com/blog/bone-up-html-2025/#you-probably-dont-need-noopener-noreferrer-on-links-anymore),
[source 2](https://stackoverflow.com/a/50709724/6432160))
2026-06-24 12:11:53 -07:00
Spike Curtis e8bd5004a2 chore: add replica_host and nats_port to replicas table (#26665)
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.
2026-06-24 15:04:04 -04:00
Bobby HoandClaude Sonnet 4.6 e0305cfa59 docs: fix contributing guide link in README (#26670)
The Contributing section of README.md linked to
https://coder.com/docs/CONTRIBUTING, which is missing the
`about/contributing/` path prefix. Updated to the correct URL:
https://coder.com/docs/about/contributing/CONTRIBUTING.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 11:32:35 -07:00
Jon Ayers 1961908ca7 fix(coderd): scope provisioner module file downloads to the daemon's org (#26635) 2026-06-24 12:09:22 -05:00
blinkagent[bot]andblink-so[bot] 9defdb4af2 chore(dogfood): remove unused parameters from coder template (#26668)
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>
2026-06-24 10:31:14 -06:00
Yevhenii Shcherbina 8bf6f43016 feat: support cross-account Bedrock AssumeRole in AI Bridge (#26527)
# 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
2026-06-24 12:03:27 -04:00
Kyle Carberry 32217259b7 feat: cap tool output to fit the model context window (#26637)
## 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.
2026-06-24 09:16:38 -06:00
Ethan 68808c015e fix: allow agents to attach any file type (#26560)
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
2026-06-25 00:51:13 +10:00
Susana Ferreira c41d219478 fix(enterprise/aibridgeproxyd): stop injecting default port into forwarded Host header (#26656)
## 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
2026-06-24 13:00:33 +01:00
Cian JohnstonandCopilot Autofix powered by AI e8c53f7968 chore: add test to document current behaviour on template ACL revocation (#26104)
Documents a question raised in
https://github.com/coder/coder/pull/26061#discussion_r3361458492 - I
couldn't find the exact answer, so adding a test and accompanying
documentation seemed like the prudent move here.

Obligatory disclosure: an agent wrote this code under my supervision.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 12:57:27 +01:00
Cian Johnston 2d28c1b396 feat: surface template README to agent template tools (#26334)
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
2026-06-24 12:32:46 +01:00
Danielle Maywood 85652554f9 feat: move MCP servers to AI settings (#26642) 2026-06-24 12:30:15 +01:00
Danielle Maywood 3133a8b9c6 feat: move instructions to AI settings (#26624) 2026-06-24 12:29:58 +01:00
Paweł Banaszewski b11cd07d97 chore: move ai gateway keys link up in ai settings (#26654)
Moves 'AI Gateway Keys' link under 'AI Governance' so it doesn't sit between 'Providers' and 'Models'.
Providers remain page loaded by default.
2026-06-24 11:34:54 +02:00
Danielle Maywood 791b4b6413 feat(site): move lifecycle settings to AI settings (#26625) 2026-06-24 10:15:28 +01:00
Danielle Maywood e569e16682 feat(site): move template allowlist to ai settings (#26615) 2026-06-24 10:00:37 +01:00
Sas Swart cec1b4e886 fix: upgrade coder/boundary to v0.9.0 (#26651)
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
2026-06-24 10:42:12 +02:00
George K ff71c1e824 fix(flake.nix): build go-migrate with only postgres driver (#26643)
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.
2026-06-24 09:11:54 +01:00
Susana Ferreira 0807b3272c feat(site): move frontend routes from /aibridge to /ai-gateway with redirects (#26569)
## 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)
2026-06-24 09:02:36 +01:00
Susana Ferreira a9c58ab8ef feat: update API URLs from /aibridge to /ai-gateway (#26567)
## 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)
2026-06-24 08:52:24 +01:00
Jon Ayers 4cfed1b3ed feat: plumb time_til_autostop_notify template field (#26439) 2026-06-23 17:32:47 -05:00
a11f349c16 docs: document log collection for Coder Desktop on macOS and Windows (#26631)
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
2026-06-23 19:22:34 +00:00
Jeremy Ruppel 61dfefb991 fix(site/src/pages/TemplateBuilder): block continuing without base template or module selection (#26626)
Fixes DEVEX-520

The Template Builder wizard allowed users to continue past the base
template selection step without selecting a template, and past the
module selection step without selecting any modules.

Adds `canContinue` validation for the `base-infra` and
`module-select` steps in `computeCanContinue()`. The Continue button
is now disabled until a base template is selected on the first step
and at least one module is selected on the module selection step.

> Generated with [Coder Agents](https://coder.com/agents)
2026-06-23 15:20:48 -04:00
Cian Johnston 7cf6a4d304 fix(coderd/x/chatd): convert file attachment that would otherwise be dropped (#26556)
fix(coderd/x/chatd): inline text attachments that providers would drop

Text-family file attachments (e.g. application/json) sent to providers
that reject them as file parts were silently dropped with a CallWarning
the user never saw. Convert them to TextPart at prompt build when the
target provider would drop that media type, so the model sees the
content while the stored file part (chip, download, history) is unchanged.

Provider acceptance is keyed on model.Provider() (the fantasy transport
identity) to correctly handle aibridge routing remapping. OpenAI distinguishes
Responses vs Chat Completions via IsResponsesModel. Only text/plain,
text/markdown, text/csv, and application/json are ever decoded; binary
content is never touched. Inlined content is sent in full with no truncation,
matching how a provider that accepts the media type natively would receive
the file.
2026-06-23 20:13:22 +01:00
Yevhenii Shcherbina a06e5a3698 test: index AI budget audit logs by action (#26630)
Fixes
https://linear.app/codercom/issue/AIGOV-436/flake-testgroupaibudgetaudit
2026-06-23 13:57:21 -04:00
Jeremy Ruppel 24f97e86ad feat(site/src/pages/TemplateBuilder): put optional module variables in collapsible section (#26627) 2026-06-23 13:39:01 -04:00
Callum StyanandMux 51591e3d59 fix(coderd/x/nats): default ClusterPort so cluster routes form (#26591)
Co-authored-by: Mux <mux@coder.com>
2026-06-23 10:03:48 -07:00
Callum Styan baf10d4d8d feat: have fake agents subscribe to derpmap updates (#26148) 2026-06-23 10:01:00 -07:00
Jon Ayers 6da322d59f feat: add Prometheus metrics to NATS pubsub for parity with PGPubsub (#26441) 2026-06-23 11:59:48 -05:00
Callum StyanandMux d104d0dcb3 fix(enterprise/coderd): propagate license events over Postgres pubsub when NATS is in use (#26536)
Co-authored-by: Mux <mux@coder.com>
2026-06-23 09:58:45 -07:00
Jon Ayers c7ddcce62c fix: only return group member count for workspace acl (#26206) 2026-06-23 11:58:00 -05:00
Nick Vigilante 6acf32701e fix: preserve Vale severity in CI annotations and add three-severity demo (#26587)
Closes DOCS-426. Follow-up to
[#26586](https://github.com/coder/coder/pull/26586) (DOCS-425, strip),
which merged first.

## Problem

The Vale problem matcher at `.github/vale-problem-matcher.json`
hard-codes `"severity": "warning"`. Every Vale finding renders as a
GitHub `warning` annotation, regardless of Vale's actual severity. Nick
observed this on PR [#25501](https://github.com/coder/coder/pull/25501):
error-level findings from `Coder.BrandNames` appear as warnings.

This collapsed the doctrine's three-severity ladder (`error` / `warning`
/ `suggestion`) into a single advisory channel for the reader of a PR
diff. This PR restores the ladder visually so contributors and reviewers
see each rule's intended severity.

## Root cause

GitHub Actions problem matchers expect either a regex capture group for
severity or a hard-coded severity. Vale's `--output=line` format
produces `path:line:col:rule:message` with severity stripped, so the
matcher had no severity to capture and fell back on the hard-coded
value.

## Fix

### Commit 1: severity rendering

Switch the Vale prose lint step to `vale --output=JSON` and pipe through
`jq` to emit GitHub workflow commands directly. Drop the problem matcher
file.

| Vale severity | GitHub workflow command |
|---|---|
| `suggestion` | `::notice::` |
| `warning` | `::warning::` |
| `error` | `::error::` |

Message bodies are URL-encoded for `%`, `\r`, and `\n` per the GitHub
Actions workflow command spec. The Vale step stays advisory
(`continue-on-error: true`, `vale --no-exit`); rendering becomes correct
but the step never fails the job.

### Commit 2: three-severity demo

Three throwaway `Coder.Demo*` rules at `level: suggestion`, `level:
warning`, and `level: error`, plus a
`docs/.style/_vale-annotation-demo.md` file that triggers each rule
exactly once. Together with the rendering fix above, this PR's CI
surfaces three GitHub annotations in three distinct severities (notice,
warning, error). Use the Files Changed view to inspect rendering.

The demo files live permanently in `docs/.style/`, which is excluded
from coder.com. They re-trigger annotations only on PRs that touch the
demo file itself, so they don't pollute CI on day-to-day PRs.

## Sample output

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/fb337315-7b55-40b3-9983-828b2d5399fc"
/>

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/b02d575d-5905-4c6d-b145-ad5df6e04f11"
/>

## Out of scope

Blocking merge on `error`-level findings is the natural next step but is
sequenced as the **final** step of the prose-style rollout. It was
prototyped in this PR (commit 3, since backed out) and verified
end-to-end against the demo doc. The work moved to
[DOCS-433](https://linear.app/codercom/issue/DOCS-433/block-merge-on-vale-error-level-findings-final-step-of-prose-style)
so the corpus of enabled rules is broad enough by the time the gate
lands that it catches real violations rather than novelty failures from
a single rule.

## Expected CI state on this PR

`lint-docs` passes. The three demo annotations render at lines 17 / 19 /
21 of `docs/.style/_vale-annotation-demo.md` as `::notice::`,
`::warning::`, and `::error::` respectively. The `::error::` annotation
does not fail the job because the Vale step is still advisory under this
PR.

Local verification of the rendering pipeline:

```
$ printf '%s\n' 'docs/.style/_vale-annotation-demo.md' \
    | xargs -d '\n' vale --no-exit --output=JSON \
    | jq -r '...'
::notice  file=docs/.style/_vale-annotation-demo.md,line=17,col=3,title=Coder.DemoSuggestion::[Demo] Suggestion-level Vale annotation.
::warning file=docs/.style/_vale-annotation-demo.md,line=19,col=3,title=Coder.DemoWarning::[Demo] Warning-level Vale annotation.
::error   file=docs/.style/_vale-annotation-demo.md,line=21,col=3,title=Coder.DemoError::[Demo] Error-level Vale annotation.
```

<details>
<summary>Decision log</summary>

- **Workflow commands vs custom Vale template + updated matcher**: chose
workflow commands because the transform is a 10-line jq pipeline with no
extra files to maintain, and it bypasses GitHub Actions problem-matcher
limitations entirely. The custom-template option would have kept the
matcher infrastructure but required an additional Go template file under
`.github/`.
- **Throwaway demo rules vs reusing existing rules**: chose throwaway
because we wanted each severity to fire deterministically from a single
unambiguous marker. Reusing existing rules would couple the demo to
corpus content and obscure the signal.
- **Demo persists vs drops before merge**: persists. The merge-gate
constraint that originally forced the demo to drop is gone (deferred to
DOCS-433). The four demo files live in `docs/.style/`, excluded from
coder.com, and only annotate PRs that touch them. They double as a
permanent canary so a future regression in severity rendering surfaces
immediately on whichever PR introduces it, and as the verification
artifact DOCS-433 uses when re-installing the merge gate.
- **`docs/.style/_vale-annotation-demo.md` filename**: underscore prefix
follows Coder convention for files that exist outside the normal docs
taxonomy. Not surfaced on coder.com/docs because `docs/.style/` is
excluded from the manifest, deploy workflow, and docs preview.
- **Merge-block deferred to DOCS-433**: the rendering fix and the merge
gate are independent changes. Shipping the rendering first lets
contributors see the three-severity ladder while the rule catalogue is
still small and the false-positive policy hasn't been stress-tested yet.
The gate lands as the final step of the rollout, after the catalogue is
broad enough that the gate covers real prose-style policy rather than
one rule's enforcement.

</details>

---
*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
2026-06-23 12:55:15 -04:00
Steven Masley 854d280834 chore: add --force-reset-all flag to oidc link repair cli (#26534)
Useful when the issuer is unchanged, but oidc subject claims have
changed.
2026-06-23 11:37:33 -05:00
Nick Vigilante bdf0e417b1 feat: strip third-party rules; enable per-rule only (#26586)
Closes DOCS-425.

## Summary

Collapse `.vale.ini` to load only the Coder rule package. Drop `Packages
= Google, alex, write-good`. Replace `BasedOnStyles = Google,
write-good, Coder` with `BasedOnStyles = Coder`. Drop every `Google.X`,
`write-good.X`, and `alex.X` per-rule line. Add a rule-rollout doctrine
under `docs/.style/README.md`.

## Why

The previous config carried roughly 12,000 baseline findings across
`docs/`: 412 errors / 5380 warnings / 6247 suggestions, almost entirely
from third-party rules whose false-positive patterns Vale cannot
distinguish from author intent.

- `Google.Headings` false-positives on every acronym and product name:
VM, AWS, GCP, Coder, Vale, JetBrains, VS Code.
- `Google.Will` fires on legitimate event-sequencing prose.
- `Google.Acronyms` fires on widely-known terms the audience reads
fluently (AWS, RDP, VPC).
- `alex.*` rules shipped in DOCS-40 without a corpus cleanup commit.

When CI surfaces false positives, engineers stop reading annotations. PR
#25501 review surfaced this concretely on `Google.Headings`. The fix is
a tight, trustworthy ruleset rather than tuning around individual false
positives.

## Doctrine

Full text in `docs/.style/README.md`. Summary:

| Element | Value |
| --- | --- |
| PR title | `feat(docs/.style): enable <RuleName>` |
| Commits | (1) corpus-wide cleanup, (2) rule enable + `style-guide.md`
section + custom YAML if applicable |
| Acceptance | zero baseline findings at merge, at the rule's chosen
severity |
| Severity | deliberate per-rule choice: `error` blocks merge; `warning`
and `suggestion` annotate without failing CI |
| False-positive policy | one confirmed FP after enable, refine or
revert; applies regardless of severity |

Applies equally to Coder-authored rules and third-party rules.
Third-party rules return through the same per-rule pattern after their
corpus is clean.

### Severity ladder

The three-severity ladder is deliberate. Some rules catch hard policy
where any violation is wrong (brand names, banned first-person pronouns,
em-dashes); those ship at `error` and block merge. Other rules catch
strong guidance with legitimate human-judgment exceptions (`disabled` as
a technical state vs. ableist usage); those ship at `warning` and
annotate without failing CI. Soft guidance (noun-as-adjective patterns
like `desired state`, wordiness) ships at `suggestion` as a `notice`
annotation.

The cleanup discipline applies at every severity. A rule landing at
`warning` or `suggestion` still ships with zero baseline findings; the
rule's purpose is to catch new violations, not to surface a backlog of
existing ones. Standing backlogs train contributors to ignore the
annotation channel.

The `error`-blocks-merge half of this contract lands operationally via
PR [#26587](https://github.com/coder/coder/pull/26587) (DOCS-426), which
removes `continue-on-error: true` and `vale --no-exit` from the CI step.

## Effect on the corpus baseline

| Metric | Before | After |
| --- | --- | --- |
| Errors | 412 | 0 |
| Warnings | 5380 | 0 |
| Suggestions | 6247 | 0 |
| Files | 465 | 465 |

Verified locally with `mise exec aqua:errata-ai/vale -- vale --no-exit
docs/`.

## Functional state after merge

The CI `Vale prose lint` step stays advisory (`continue-on-error: true`,
`--no-exit`) until PR #26587 lands. With no rules loaded except Coder's
package (currently empty on `main`), the step is effectively a no-op
until `Coder.BrandNames` lands via PR #25501 (DOCS-34). At that point
the lint step becomes a `Coder.BrandNames`-only check. Subsequent
per-rule PRs extend coverage one rule at a time per the doctrine, each
rule choosing the severity that matches its policy strictness.

The Makefile target `docs/.style/.vale-synced: .vale.ini` still runs
`vale sync`, which is now a no-op because `Packages` is empty. The
previously-synced `docs/.style/styles/{Google,alex,write-good}/`
directories remain on developers' disks (they're gitignored) but are no
longer loaded by Vale.

## Sequencing

1. **This PR merges first**
2. PR #26587 (DOCS-426) installs the CI merge gate and the
severity-rendering fix
3. PR #25501 (DOCS-34) rebases onto main, drops its now-redundant
`Google.Parens = NO` change, lands `Coder.BrandNames` as the first
concrete rule
4. DOCS-424 (Vale rule audit) is complete; per-rule re-enablement work
begins per the doctrine

<details>
<summary>Decision log</summary>

- **Strip everything vs. partial disable**: chose full strip because
each third-party rule loaded by default is a tacit endorsement. The
doctrine requires every enabled rule to be deliberate. A partial disable
still loads styles whose other rules haven't been audited.
- **`alex.*` rules**: yanked in this PR. They were enabled in DOCS-40
without a corpus cleanup commit. The "audit then keep" call returns them
via dedicated per-rule PRs once the audit confirms baseline violation
counts and the doctrine accepts them.
- **`Packages` directive dropped**: with no third-party rules loaded,
`vale sync` had no work to do. Removing the directive avoids implying we
intend to re-add packages without a per-rule PR. The directive returns
when a future PR opts in a Google or write-good rule.
- **Doctrine location**: under `docs/.style/README.md` rather than a
dedicated `docs/.style/RULE_ROLLOUT.md`. Keeps the contributor-facing
entry point single, and the section sits alongside the existing "Editing
the style guide" and "Editing the content guidelines" sections.
- **Three-severity ladder vs. error-only**: chose deliberate per-rule
severity because the rule catalogue contains rules at different policy
strictness. Forcing every rule to `error` would either reject useful
warning- and suggestion-level rules (noun-as-adjective patterns,
wordiness guidance) or push them onto an inappropriate gate. The CI
severity rendering and merge-gate work in PR #26587 was built
specifically to support this ladder.

</details>

---
*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
2026-06-23 12:15:49 -04:00
George K fccb238ec7 fix: upgrade nixpkgs to 25.05 and pin protoc 23.4 (#26584)
`nix develop` and `nix-shell` were broken because the `nixos-24.11`
`google-chrome` derivation still pointed at Chrome 138, and Google no
longer serves that versioned `.deb`.

Update the flake's main `nixpkgs` input to `nixos-25.05` so the
shell resolves a current Chrome package again. Pin `protoc
23.4` explicitly from the upstream protobuf release archives. This keeps
the local Nix shell aligned with `mise.toml` and the CI/release codegen
toolchain.
2026-06-23 09:08:35 -07:00
Jeremy Ruppel 47adfb3f28 feat(site): display prerequisites in base template step (#26524)
Display the base template prerequisites in the Template Builder wizard.
Stacked on #26523.

The `base-parameters` wizard step now renders the prerequisites markdown
(served by the backend `prerequisites` field) below the variable
configuration fields using `MemoizedMarkdown`. The step is shown when
the base has parameters **or** prerequisites, so Docker (no parameters,
has prerequisites) now shows this step.

## Changes

- `SelectedBaseMeta` gains `hasPrerequisites` boolean
- `toSelectedBaseMeta()` populates it from `base.prerequisites`
- `base-parameters` step skip logic: show when base has parameters or
prerequisites
- `BaseTemplateParametersStep`: render full prerequisites markdown as-is
(headings intact)
- Updated all test fixtures with the new field

*Generated with the assistance of an AI coding agent. Reviewed by
@jeremyruppel.*

Relates to https://linear.app/codercom/issue/DEVEX-446
2026-06-23 11:57:28 -04:00
Ehab Younes 0aa14c7d45 feat(site): add group AI budget column (#26566)
Adds an AI budget column to the organization Groups list showing each
group's current AI spend against its configured limit, or "unlimited"
when no limit is set.

- Column and its spend request are gated behind `aibridge` visibility
  and the `ai-gateway-cost-control` experiment
- Shows loading placeholders while spend is fetched from
  `/api/v2/organizations/{org}/groups/ai/spend`
- Spend severity thresholds extracted into shared `utils/budget.ts`:
  warning at 85%, destructive at or above the limit
- Response type defined locally with a TODO to replace with the
  generated type once the backend endpoint exists

Closes AIGOV-290
2026-06-23 18:56:54 +03:00
Kyle Carberry bafc86310c fix(agent/agentcontext): identify context sources by lexical path (#26616)
## What

Identify agent workspace-context **sources** by their lexical
(configured) path so `coder exp chat context list` no longer shows the
same directory twice, and so a source is shown as the path the operator
actually configured.

## Why (the bug)

Source identity was the canonical path from `CanonicalizePath`, which
resolves symlinks via `EvalSymlinks` **only when the target exists**.
That makes canonicalization time-dependent:

- At boot the agent seeds sources from `CODER_AGENT_EXP_*_DIRS`. If
`~/.coder/skills -> ~/my-agent/agent-rules/skills` and the target does
not exist yet (a startup script creates it later), `~/.coder/skills`
canonicalizes to the lexical `/home/coder/.coder/skills`.
- After the manifest lands (or the target is added directly), the same
configured source canonicalizes to the resolved
`/home/coder/my-agent/agent-rules/skills`.

The same configured source produced two different strings, so dedupe
keyed on the string registered both and the list showed one directory
twice.

Resolving symlinks for identity is also misleading on its own (per
@mafredri's review): a source added by a symlink path appears in the
list as its resolved target, as if that target had been added
explicitly.

## How

Source identity is now the **lexical** path: cleaned, `~`-expanded,
absolute, with symlinks **not** resolved (new `lexicalPath`;
`CanonicalizePath` is refactored to build on it). `AddSource`,
`SeedSources`, `HasSource`, `RemoveSource`, and boot seeding all key on
this stable identity.

`AddSource` still **validates** the resolved (`CanonicalizePath`) path
against the allowed roots, so a symlink cannot escape them. Only the
identity/display path changed.

This replaces the earlier `os.SameFile`/inode dedupe, which was unstable
and failed on Windows runners.

## Testing

- `go test ./agent/agentcontext/` (full package) and `go vet` pass;
`gofmt` clean.
- `TestManager_SourceIdentityIsLexicalAndStable`: adds the same
symlinked source before and after its target exists and asserts one
source whose path is the lexical link (skipped on Windows, matching the
package's other symlink tests).
- Existing `TestCanonicalizePath_FollowsSymlinks` and
`TestValidateSourcePath_*` confirm symlink resolution and the security
boundary are unchanged.

<details>
<summary>Related review findings</summary>

Fixes the "duplicate symlinked paths in `context list`" issue from the
chat-context system review and the dedupe-ordering question (lexical
identity preserves first-come-first-served order). Showing the
configured path for **resources** (not just sources) and restoring
scope-based skill precedence are separate, larger changes tracked
elsewhere.

</details>

---

*This PR was created by Coder Agents on behalf of @kylecarbs.*
2026-06-23 15:46:26 +00:00
Jeremy Ruppel 10717572ac feat: show template prerequisites in builder UI (#26523)
Surface base template prerequisites to admins before they create a
template in the Template Builder wizard.

Today, template prerequisites (Docker socket setup, Kubernetes auth, AWS
IAM policies) are only visible in the registry README after import.
Admins hit opaque provisioner errors and have to hunt for docs. This
change extracts the prerequisites from the README and serves them via
the API so the frontend can display them inline.

## How it works

Each base template README uses HTML comment markers (`<!--
prerequisites:start -->` / `<!-- prerequisites:end -->`) to delimit the
prerequisites section. At boot time, the base catalog loader reads the
README, extracts the content between markers via `strings.Index`, and
caches both the full README and the prerequisites string.

The prerequisites are served via a new `prerequisites` field on `GET
/api/v2/templatebuilder/bases`. The full README is included in the
composed template tar bundle and stored as the template version readme.

## Changes

- Add `README.md` with prerequisite markers to
`coderd/templatebuilder/bases/{docker,kubernetes,aws-linux}/`
- New `ExtractPrerequisites()` in `prerequisites.go` using literal
string matching
- `bases.go`: load README at boot, fail loudly if missing, extract
prerequisites
- `compose.go`: include README in `ComposeResult` and tar bundle
- `codersdk`: add `Prerequisites` field to `TemplateBuilderBase`
- Handler: populate prerequisites in bases response, set readme on
template version

<details>
<summary>Implementation notes</summary>

- Prerequisites extraction uses `strings.Index` for exact literal marker
matching; no regex or AST parser needed since we control the markers.
- YAML frontmatter is deliberately retained in the stored README. The
frontend `TemplateDocsPage` already strips it at render time via
`front-matter`.
- The prerequisite markers are HTML comments, invisible in rendered
markdown.
- The `RejectsMissingReadme` test enforces that every base template must
include a README.
- AWS Linux prerequisites span two H2 sections (`## Prerequisites` and
`## Required permissions / policy`), which is why heading-based parsing
was rejected in favor of explicit markers.

*Generated with the assistance of an AI coding agent. Reviewed by
@jeremyruppel.*
</details>

Relates to https://linear.app/codercom/issue/DEVEX-446
2026-06-23 11:36:07 -04:00
Jeremy Ruppel 7ea5d48296 fix: render base variables into templates instead of tfvars (DEVEX-287) (#26436)
Part of the Template Builder wizard PR stack.

## Problem

The kubernetes base template used Terraform `variable` blocks and
`var.*` references for `use_kubeconfig` and `namespace`, but the
composed tar bundle never included a `.tfvars` file. This caused
`terraform plan` to fail with "required template variables need values:
namespace".

## Fix

Base templates now use Go template variables (`{{ .Variables.* }}`) just
like module templates do. Values are validated, HCL-quoted, and rendered
directly into the output HCL.

Also adds explicit "variable is required" validation to both
`mergeBaseVariables` and `mergeModuleVariables`, replacing the previous
reliance on `missingkey=error` at render time for clearer error
messages.

---
> [!NOTE]
> Generated by Coder Agents on behalf of @jeremyruppel
2026-06-23 11:26:38 -04:00
Jon Ayers 2f6f8b9520 feat: add workspace autostop reminder template (#26429) 2026-06-23 10:16:17 -05:00
Jeremy Ruppel c9e94c20cc feat: TemplateCustomizationsStep and compose POST (DEVEX-287) (#26433)
Part of the Template Builder wizard PR stack.

## Frontend changes

1. **TemplateCustomizationsStep**: Final wizard step with org picker,
icon picker, name/display name/description/icon fields, and redirect on
success.

2. **Refactor**: Moved queries/mutation from PageView to Page container,
extracted `renderStepContent` switch and `computeCanContinue` switch
into standalone functions.

---
> [!NOTE]
> Generated by Coder Agents on behalf of @jeremyruppel
2026-06-23 10:23:00 -04:00
Kyle Carberry 44f2a77b34 feat(site/src/pages/AgentsPage/components): show context sizes and full .mcp.json paths (#26614) 2026-06-23 07:36:05 -06:00