Commit Graph
4192 Commits
Author SHA1 Message Date
Ethan ea4554025e fix(coderd): stop manual title generation from writing to chat_messages (#27087)
Coder Agents chats could get stuck showing "Thinking" forever when a
title regenerate/propose request ran while a generation was in flight.

Manual title generation recorded token cost by inserting a hidden
assistant message into `chat_messages` and immediately soft-deleting it.
Triggers on that table sync `chats.history_version` to
`snapshot_version`, so this out-of-band write broke the
`history_version` fence of an in-flight generation task, killing it
without a replacement and leaving the chat stuck in `running`.

Remove the accounting path entirely; AI Gateway already records
title-call usage in `aibridge_interceptions`/`aibridge_token_usages`.
The manual title endpoints no longer write to `chat_messages` at all,
and new regression tests assert `history_version` stays untouched. Note
this intentionally drops title-generation cost from chatd's chat-level
cost surfaces; it still counts against the user's AI budget via AI
Gateway.

Closes CODAGT-595
2026-07-13 16:50:19 +10:00
Thomas Kosiewski 3d8ffd34b3 fix(coderd/x/chatd): retry quickgen without temperature when model rejects it (#27120) 2026-07-10 09:13:42 +02:00
Danielle Maywood d66e4d794f feat: add configurable reasoning effort to Coder agents (#26974) 2026-07-09 23:35:12 +01:00
dylanhuff-at-coder 5fed583a46 fix(coderd): enforce required external auth on task create (#26718)
Tasks created through the API now enforce required external auth:
`tasksCreate` rejects an owner who is missing a required (non-optional)
provider with a 403 before generating a task name or inserting any rows,
matching the gate `createWorkspace` already applies to workspaces. Adds
`TestCreateTaskExternalAuth` covering the required and optional-provider
cases.

Fixes PLAT-298.

_Coder Agents generated._
2026-07-09 13:59:49 -07:00
Dallin Stevens 37558fcdc9 fix(coderd/externalauth): preserve scopes on entra v1 token refresh (#24851)
Without this, Entra silently narrows scopes to the default set.
2026-07-09 11:49:19 -08:00
Paweł Banaszewski bab8ce9d41 feat: setup logging, tracing and metrics in standalone AI Gateway (#27068)
Adds logging, tracing and metrics setup to standalone AI Gateway.
Existing options are re-used when possible.
2026-07-09 14:02:18 +00:00
Danny Kopping ef0b5585d5 feat: record and expose terminal upstream interception errors (#26961)
Categorises the terminal error of a failed interception and persists it
on the interception record, then surfaces it on the AI Gateway API.

- Categorise into an enum (`bad_request`, `unauthorized`,
  `rate_limited`, `overloaded`, `server_error`, `unknown`), unwrapping
  the ResponseError envelope, the upstream Anthropic/OpenAI SDK errors,
  and key-pool exhaustion so blocking and streaming paths agree.
- Thread the type and raw message through the recorder dRPC into the
  `aibridge_interceptions` row (optional proto fields; NULL on success).
- Expose the error on the AI Gateway thread API from the root
  interception.

*This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-09 15:36:56 +02:00
Danny Kopping 63497ee9d8 feat(coderd/database): add error columns to aibridge interception records (#26960)
Adds a nullable `aibridge_interception_error_type` enum and an
`error_message` column to `aibridge_interceptions`, so a failed
interception's terminal upstream error can be persisted.

Schema only: the write path and API exposure land in the stacked
backend PR.

*This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-09 15:06:44 +02:00
Hugo Dutka cd1a676232 chore(coderd): deflake chat http tests (#27121)
Closes https://github.com/coder/internal/issues/1615. The affected test
was starting coderd with a live chatd worker, but assumed that the chat
would not be processed by a worker. The fix was to start coderd without
a chatd worker. I noticed that some other tests in the file could suffer
from the same flake root cause, so I fixed them too.
2026-07-09 11:20:42 +00:00
Jon Ayers a6afcd046f fix: prevent deadlock between async error handling and failed subscribes (#27109) 2026-07-08 17:28:08 -05:00
George K 52775ef172 test(coderd/externalauth): fix RevokeTokenRFC_Timeout flake (#27082)
Under CI load the request's 10ms revoke timeout could expire before
the request reached the FakeIDP revoke handler. The handler never
ran, so the test's wait for it to finish blocked until the 25s test
context expired instead of passing quickly.

Raise `RevokeTimeout` to 100ms so the request has ~10x more headroom to
reach the handler under load. After RevokeToken returns, check a
`handlerStarted` signal before asserting: this anchors the
`DeadlineExceeded` assertion to a request that was actually in flight,
and turns any residual scheduling race into a fast, labeled failure
instead of a hang.

Unblock the handler on the early-exit path with a `t.Cleanup`. It must
be registered after the FakeIDP setup so LIFO runs it before the
server's `Close()`; otherwise a handler that dispatched late would
block `Close()` and hang teardown until the test timeout. Drop the
previous `time.Sleep` watchdog and the handler-done channel, since the
FakeIDP server's `Close()` already joins the in-flight handler.

Refs: https://linear.app/codercom/issue/PLAT-317
2026-07-08 13:01:29 -07:00
Michael Suchacz 2ad5af5b54 fix(coderd): use pasted-text attachments as chat title input (#27067)
Closes https://linear.app/codercom/issue/CODAGT-268

## Problem

The chat UI collapses large pastes (>=10 lines or >=1000 chars) into a
synthetic `pasted-text-*.txt` attachment. A chat created with only such
an attachment had no title input anywhere: the create path derived
`titleSource` only from text and file-reference parts (so the chat was
named "New Chat"), async auto-titling extracted text the same way and
silently skipped generation, and the manual propose/regenerate paths
returned an empty title for the same reason. The regular prompt path
already inlines these files for the model; only the title paths were
blind.

## Fix

Add a single title-input derivation in `chatprompt` and use it
everywhere:

- `chatprompt.TitleText` joins text and file-reference parts (unchanged
formatting), and falls back to synthetic pasted-text attachment content
(truncated to a 16 KiB title budget) when they yield nothing.
- `chatprompt.SyntheticPasteFileIDs` identifies paste attachments;
`chatprompt.FallbackTitle` consolidates the previously duplicated
`chatTitleFromMessage` / `fallbackChatTitle`.
- Chat creation captures paste blob references while validating file
parts (the file row was already loaded there) and derives `titleSource`
via `TitleText`. Only the create path derives titles; message send and
edit reuse the same validation without copying any blob data.
- `GenerateChatTitleAsync` and the manual propose/regenerate paths
resolve paste content via `titlePasteText`, which only queries when a
visible user message has no other title text, so chats with typed text
never incur a file fetch.
- Title-path paste fetches are bounded: a new
`GetChatFileDataPrefixesByIDs` query returns only a `substr` prefix
(`chatprompt.TitlePasteBytePrefix`, 64 KiB = 4 bytes x the 16 Ki-rune
title budget) so full blobs (up to 10 MiB each) never leave the database
for titling, and `chatprompt.TitlePasteText` applies the same bound to
the create path which already holds the loaded row.

Deliberate side effect: because generation-time extraction now matches
create-time `titleSource` exactly, file-reference-only chats also become
eligible for AI titles. They were previously skipped by the same
derivation mismatch.

Non-goals: no frontend changes (attachment chip UX stays as is), and
non-synthetic user-uploaded `.txt` files still yield "New Chat".

## Testing

- Unit tests for `TitleText`, `TitlePasteText`, `SyntheticPasteFileIDs`,
`FallbackTitle`, `titleInput`, `titlePasteText`, and paste-aware
`extractManualTitleTurns`.
- Real-database test for `GetChatFileDataPrefixesByIDs` (prefix shorter
and longer than stored data) plus dbauthz coverage for the new query.
- Integration tests: paste-only create gets a fallback title from the
paste content, async title generation fires with the paste content as
input, and `RegenerateChatTitle` works on a paste-only chat.

> This PR was written by [Mux](https://mux.coder.com) on Mike's behalf.
2026-07-08 21:37:30 +02:00
Cian Johnston 990f0a5529 chore(coderd/database): remove unused UpdateChatMessageByID query (#27099)
Removes the `UpdateChatMessageByID` query. Its only non-generated
reference was its own dbauthz coverage test, so it is dead code.

> Generated by Coder Agents on behalf of @johnstcn.
2026-07-08 19:02:19 +01:00
Danny Kopping affb359d13 feat: synchronise provider changes with WatchAIProviders (#27091)
## Why

PR #26797 was accidentally merged into the stale `graphite-base/26797`
branch instead of `main` (Graphite picked the wrong base), so its
changes never landed on `main`. This PR re-lands that work as a clean
cherry-pick onto the current `main`.

## What

Adds a `WatchAIProviders` streaming RPC to the `ProviderConfigurator`
service so a running standalone AI Gateway refetches its provider set
when the provider configuration changes. The server subscribes to
`AIProvidersChangedChannel` (published by the provider CRUD endpoints)
and forwards each event as a payload-free signal, plus one signal on
subscribe; the gateway calls `GetAIProviders` on each signal to rebuild
its pool. The aibridged API is bumped to v1.2.

Env-seeded providers don't need a signal: seeding finishes before coderd
serves the gateway connection, so the gateway's initial fetch already
reflects the seeded set.

## For reviewers

The change is split into two commits to make review easy:

1. **`feat: synchronise provider changes with WatchAIProviders`** is a
faithful cherry-pick of #26797, identical to the originally reviewed PR.
It is committed without pre-commit hooks because it does not build
against current `main` on its own.
2. **`fix: resolve cherry-pick conflicts against main`** contains only
the deltas needed to re-land on current `main`, and passes the full
pre-commit suite:
- `coderd/aibridged/proto/aibridged.pb.go` regenerated via the proto
make target (the cherry-picked copy was generated against the older
proto).
- `enterprise/cli/aigatewaystart.go` import block unioned; `main` added
`os` and `strings` while the PR added `sync`.
- Three `aibridgedserver.NewServer` test call sites that landed on
`main` after the original branch diverged now pass the new `pubsub`
argument.

Refs https://linear.app/codercom/issue/AIGOV-465

*This PR was produced by opencode (agent) using the
`anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-08 15:32:17 +02:00
Susana Ferreira 48f07e6e13 feat: add user AI spend endpoint (#26978)
## Description

Adds the `GET /api/v2/users/{user}/ai/spend` endpoint returning the
user's current AI spend, effective budget, and period bounds.

## Changes

- Add `userAISpendStatus` handler under the same feature/experiment gate
as `/api/v2/users/{user}/ai/budget`.
- Add `codersdk.UserAIBudgetSummary` (embedded into `UserAISpendStatus`)
and a `UserAISpendStatus` client method.
- Move `LimitSource` from `coderd/aibridge/budget` to `codersdk` so the
type is shared across endpoints.

Closes https://linear.app/codercom/issue/AIGOV-472

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
2026-07-08 12:46:47 +01:00
Paweł Banaszewski 60d0859bde feat: add /healtz and /readyz endpoints to standalone Gateway (#26988)
Adds `/healthz` and `/readyz` endpoints to standalone AI Gateway.
* `/healthz`: returns 200 once the AI Gateways HTTP server is listening.
* `/readyz`: returns 200 when the DRPC connection to `coderd` is
established.

Cleanup:
Removed `initConnection*` fields from `aibridged.Server` as they where
not used anywhere.
2026-07-08 09:28:02 +00:00
Paweł BanaszewskiandDanny Kopping ccba3969ab feat: add ai-gateway start command (#26605)
> AI Tools were used to produce this PR

This PR adds `coder ai-gateway start` command that runs the AI Gateway
as an independent process.

- Standalone process doesn't have access to DB. Uses DRPC services under
`/api/v2/ai-gateway/serve`for auth, recording and provider
initialization.
- It only handles LLM traffic, other endpoints (eg. `/sessions`) are
only available though `coderd`.
- The standalone gateway reuses applicable flags from AI Gateway
deployment options. Provider-seeding and coderd-only options are
excluded.
- Only added to fat build, the slim build stub rejects the command.

Some wiring used by this new command is added.

**`NewWebsocketDialer`** - implements the standalone gateway's
connection to coderd's `/api/v2/ai-gateway/serve` endpoint. It upgrades
to a WebSocket, multiplexes with yamux, and wires all DRPC services.

**`AIGatewayDataPlaneMiddleware`** - extracts the per-request middleware
chain (concurrency limiting, rate limiting, BYOK gating) into a shared
function used by both the embedded route and the standalone gateway.

**`RootCmd.ResolveClientConnection`** - resolve the deployment URL and
builds an HTTP transport without requiring a session token. Used in
`ai-gateway start`command as it authenticates using different credential
type.

---------

Co-authored-by: Danny Kopping <danny@coder.com>
2026-07-08 11:12:53 +02:00
Cian Johnston 14767dfa8e feat: explain missing POSIX sh on Windows workspaces (#27048)
When a workspace has no POSIX sh on PATH (typical for fresh Windows workspaces), the execute tool fails with a raw `exec: "sh": executable file not found in %PATH%` error the model cannot act on. 

This change:
- Enriches the above error in chattool with remediation steps and a docs link.
- Documents the requirement in the Coder Agents architecture page.

> This PR was generated by Coder Agents on behalf of @johnstcn
2026-07-07 21:32:27 +00:00
Bobby Ho b169f4d8cb feat: expose external auth token expiry in agent API and CLI (#26883)
Previously, \`ExternalAuthResponse\` contained no expiry information, so
workspace agents and git credential helpers had no way to know when a
cached token would stop being valid. Every git operation had to call
back to coderd via \`GIT_ASKPASS\` to get a fresh token, adding 1-2
seconds of latency.

This PR surfaces \`OAuthExpiry\` from the database as \`ExpiresAt\` in
\`ExternalAuthResponse\`, allowing agents to cache tokens with correct
eviction timing (compatible with \`git-credential-cache --timeout\` and
\`password_expiry_utc\` introduced in git 2.34).

\`ExpiresAt\` is normalized to UTC before JSON encoding to avoid
sub-minute precision loss that occurs when the PostgreSQL driver applies
historical Local Mean Time (LMT) timezone offsets to year-1 AD
timestamps.

The \`coder external-auth access-token\` CLI command gains \`--output
json\` to print the full response including \`ExpiresAt\`, enabling
scripts to consume the expiry without parsing heuristics.

Closes https://github.com/coder/coder/issues/26036

## Manual Test

<details>
<summary>Setup</summary>

1. Create a GitHub OAuth app at https://github.com/settings/developers
with:
   - Homepage URL: `http://127.0.0.1:3000`
- Authorization callback URL:
`http://127.0.0.1:3000/external-auth/github/callback`

2. Start the dev server with the GitHub provider configured:
   ```sh
CODER_EXTERNAL_AUTH_0_ID=github CODER_EXTERNAL_AUTH_0_TYPE=github
CODER_EXTERNAL_AUTH_0_CLIENT_ID=<client-id>
CODER_EXTERNAL_AUTH_0_CLIENT_SECRET=<client-secret> ./scripts/develop.sh
   ```

3. Log in at `http://127.0.0.1:3000` (use `127.0.0.1`, not `localhost`,
so the OAuth state cookie domain matches the callback URL).

4. Go to Account > External Authentication and click **Connect** next to
GitHub. Complete the OAuth flow.

5. Create a workspace and SSH into it:
   ```sh
   coder create test-workspace
   coder ssh test-workspace
   ```

</details>

<details>
<summary>Flow 1: Token is valid — JSON output includes
<code>expires_at</code></summary>

Inside the workspace, run:

```sh
coder external-auth access-token github --output json
echo "Exit code: $?"
```

Expected output (GitHub tokens have no expiry, so \`expires_at\` is the
zero value):

```json
{
  "access_token": "<redacted>",
  "token_extra": null,
  "url": "",
  "type": "github",
  "expires_at": "0001-01-01T00:00:00Z",
  "username": "<redacted>",
  "password": ""
}
```

```
Exit code: 0
```

</details>

<details>
<summary>Flow 2: Token missing — JSON output includes auth URL, exit
code 1</summary>

Disconnect GitHub in the Coder UI (Account > External Authentication >
Disconnect), then inside the workspace run:

```sh
coder external-auth access-token github --output json
echo "Exit code: $?"
```

Expected output:

```json
{
  "access_token": "",
  "token_extra": null,
  "url": "http://127.0.0.1:3000/external-auth/github",
  "type": "",
  "expires_at": "0001-01-01T00:00:00Z",
  "username": "",
  "password": ""
}
```

```
Exit code: 1
```

</details>
2026-07-07 12:38:37 -07:00
George K 6af0f4d698 feat: add workspace restart functionality to API (#25757)
This models restart as durable orchestration of existing stop and
start workspace builds instead of adding a new restart transition.
Keeping restart as two existing transitions preserves the current
build/provisioner model.

The child start build is created only after the parent stop build
succeeds, rather than being inserted immediately in a pending
state. That keeps `workspace_builds` aligned with actual
provisioner-ready work and avoids introducing a second
pending-build lifecycle that the provisioner and build acquisition
paths would need to understand.

Refs: https://linear.app/codercom/issue/PLAT-143
2026-07-07 09:18:30 -07:00
George K ba094c5706 fix(coderd): allow user-admin password resets to succeed (#26537)
User Admin password resets could update the target user's hashed
password but fail while revoking that user's API keys. The transaction
then rolled back and returned HTTP 500, so the password was never
changed.

Add a user-scoped API key revoker actor and use it in both password
reset flows so key revocation succeeds without broader system auth.

Refs: https://linear.app/codercom/issue/PLAT-316
2026-07-07 09:05:14 -07:00
Sas Swart fc188fdaee fix: create agent firewall sessions without requiring agent read access (#26990)
## Overview

Part of the **boundary correlation** feature. Fixes lazy creation of
`boundary_sessions` rows so it works within the agent's RBAC
constraints, and consumes the new `ConfinedProcessName` field reported
by boundary.

Pairs with coder/boundary#206, which adds `ConfinedProcessName` to
`ReportBoundaryLogsRequest`. This branch bumps the
`github.com/coder/boundary` module to pick up that work.

## Problem

`ensureSession` did a pre-insert existence check via
`GetBoundarySessionByID`. Agents are **not permitted to read boundary
sessions**, so that read path is not viable when the session is created
from an agent-reported log batch.

## Changes

- **Remove the pre-insert read.** `ensureSession` now inserts directly
and treats a primary-key unique violation as success, covering sessions
already created by a prior batch, a reconnection, or another coderd
replica — without requiring read access.
- **Per-connection guard.** Add a mutex-protected `ensuredSessions` set
so repeated log batches on the same connection skip the existence check
and insert entirely, touching the database only for the logs. On a
transient insert failure the session is left unmarked so the next batch
retries.
- **Consume `ConfinedProcessName`.** Pass `req.GetConfinedProcessName()`
through to the session insert.
- **Bump boundary module** from `v0.9.0` to
`v0.9.1-0.20260706095856-35ba90f9e8b2`.
- **Tests.**
- Add `TestReportBoundaryLogsAgentRBAC`
(`coderd/boundary_logs_test.go`), an integration test that connects as a
real workspace agent, verifies the session and log are persisted under
agent RBAC, and asserts the agent subject cannot read boundary sessions
— guarding against reintroducing a pre-insert read.
- Add `TestReportBoundaryLogsSessionGuard` (session inserted once across
two batches, logs inserted per batch) and
`TestReportBoundaryLogsSessionRetriedOnError` (insert retried after a
transient error).
- Regenerate `agent-firewall` CLI docs/golden files and adjust the
clidocgen template to render the YAML path when a flag has no long name.

> 🤖 This PR was opened by Coder Agents on behalf of @SasSwart.
2026-07-07 10:42:01 +00:00
Michael Suchacz 0ae4554de9 fix(coderd/x/chatd/chatloop): remove compaction timeout (#27007) 2026-07-07 12:22:59 +02:00
Jon Ayers 8996506d43 fix: use a unique channel name per latency measurement (#27040) 2026-07-07 00:25:42 -05:00
Michael SuchaczandMathias Fredriksson 1eb5d579b0 fix: unblock manual chat title generation for unowned chats (#26963)
## Problem

The Generate button in the chat Rename dialog (POST
`/api/experimental/chats/{chat}/title/propose`) could fail in ways
unrelated to actual concurrent title generation:

- The manual title lock returned 409 for any `pending` chat and any
`running` chat without a worker. Legacy `pending` rows are never
acquired by workers, so those chats 409'd forever. Running chats are
unowned in the normal window between message submission and worker
acquisition (indefinitely when runners are down), producing spurious
409s.
- A missing default chat model config surfaced as a generic 500, and the
dialog hid the actionable cause carried in the error detail.

## Fix

Backend (`coderd/x/chatd`, `coderd`, `coderd/database`):

- Remove the manual title lock entirely. Races between title writers are
already resolved by `recordManualTitleUsage`, which re-reads the chat
under `GetChatByIDForUpdate` and only persists the generated title when
it is unchanged since the request snapshot, so concurrent regenerates
and renames settle by last write wins. The lock only suppressed
duplicate model calls (the dialog already disables the button in flight,
and usage limits bound spend), and its synthetic `worker_id` marker was
the source of the spurious 409s. The 409 responses, the marker and
staleness handling, and the now-unused
`UpdateChatStatusPreserveUpdatedAt` query are gone.
- New `ErrNoDefaultChatModelConfig` sentinel mapped to 400 "No default
chat model config is configured." in both title endpoints, matching the
POST `/chats` precedent.

Frontend (`site`):

- The Rename dialog error alert now renders the API error detail under
the message, reading `error.response.data.detail` directly so
detail-less API errors do not show the generic developer-console hint.
- Removed the dead regenerate-title UI plumbing (`onRegenerateTitle`
outlet wiring and the `regeneratingTitleChatIds` spinner pipeline). The
Rename dialog propose flow is the only live title-generation UX; the
endpoint, codersdk methods, and the `api.ts`/`queries/chats.ts` layer
are kept for API consumers.

## Tests

- chatd internal: a strict-mock test pinning the compare-and-swap guard
(a concurrently changed title must not be clobbered by a generated one),
plus the existing persist-and-broadcast coverage without lock
transactions.
- HTTP: `PendingWithoutWorker` expects 200 for both endpoints,
`NoDefaultModelConfig` (400) subtests, a stopped-workspace propose
regression, and an `Unauthenticated` propose subtest.
- Storybook: stories asserting the API error detail renders in the
dialog alert, and that detail-less API errors and plain errors do not
leak the developer-console hint.

> Authored by Mux on Mike's behalf.

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2026-07-06 23:09:08 +00:00
Danielle Maywood d51762440b feat: add custom AI provider icons and instance-based model picker grouping (#27026) 2026-07-06 23:00:09 +01:00
Jeremy Ruppel 581f906947 fix(coderd): declare project_id variable in GCP template builder bases (#27015)
## Summary

GCP base templates (`gcp-linux`, `gcp-windows`) in the Template Builder
had a Terraform `variable "project_id"` with no default, but their
`base.json` manifests didn't declare it. The UI never prompted for it,
so the provisioner import always failed with:

```
required template variables need values: project_id
```

## Changes

- Add `project_id` as a required variable in both GCP `base.json`
manifests
- Convert templates from raw Terraform variable blocks to Go template
injection (`{{ .Variables.project_id }}`), matching the existing
kubernetes pattern
- Fix `DefaultBaseRenderContext` to supply a `"REQUIRED"` placeholder
for required variables without defaults (previously rendered as `<no
value>`)
- Replace duplicate test subtests with proper GCP coverage including a
missing-variable error case

<details>
<summary>Implementation plan</summary>

### Root cause

The GCP base templates contained `variable "project_id" {}` (no default
= required) in their `.tf.tmpl` files, but the `base.json` manifests had
an empty `variables` array. The Template Builder UI
(`BaseTemplateParametersStep`) is data-driven from `base.json`, so it
never showed a field for `project_id`. The composed Terraform output
still contained the required variable, causing the provisioner import to
fail.

### Fix approach

Follow the pattern established by the kubernetes base template:
1. Declare variables in `base.json` so the UI prompts for them
2. Use Go template syntax (`{{ .Variables.project_id }}`) to inject
values at compose time
3. Remove raw Terraform `variable` blocks from the template since the
value is now baked in

### Files changed

| File | Change |
|---|---|
| `bases/gcp-linux/base.json` | Added `project_id` as a required
variable |
| `bases/gcp-windows/base.json` | Added `project_id` as a required
variable |
| `bases/gcp-linux/main.tf.tmpl` | Removed Terraform variable block, use
Go template injection |
| `bases/gcp-windows/main.tf.tmpl` | Same |
| `bases.go` | `DefaultBaseRenderContext` supplies placeholder for
required vars without defaults |
| `compose_test.go` | Replaced duplicate subtests with proper GCP tests
|
| `templatebuilder_handler_test.go` | Updated `gcp-windows` spec to
expect `project_id` variable |
| Golden files | Regenerated |

</details>

> 🤖 Generated by Coder Agents on behalf of @jeremyruppel
2026-07-06 16:48:00 -04:00
Ben PotterandJeremy Ruppel 7b19ec3933 feat: improve the image management experience with template builder (#27018)
Makes it easier to pick the right workspace image, both in the template
builder and in the docs.

- Template builder: the Docker and Kubernetes bases now expose a
`container_image` variable in the wizard (freeform text, defaults to
`codercom/example-base:ubuntu`), and their prerequisites explain why
image choice matters, with tradeoffs between
`codercom/example-base:ubuntu` (minimal) and
`codercom/example-universal:ubuntu` (catch-all), plus pointers to
[coder/images](https://github.com/coder/images) and the image management
docs.
- Docs: reworked [image
management](https://coder.com/docs/@ben%2Fdevrel-201-image-guidance-prereqs/admin/templates/managing-templates/image-management)
into a clearer maturity ladder (minimal → golden → project-specific →
developer customization), with pullable image references in every
example, `codercom/oss-dogfood` as a project-specific example, and Dev
Containers + [mise](https://mise.jdx.dev/) as ways to customize without
new images.

Companion PR for the starter templates: coder/registry#943

Part of DEVREL-201.

🤖 Generated with Coder Agents using Claude, on behalf of @bpmct (wizard
variable by @jeremyruppel in #27024)

---------

Co-authored-by: Jeremy Ruppel <jeremyruppel@users.noreply.github.com>
2026-07-06 20:37:34 +00:00
Callum Styan bf58e8a402 fix(coderd): require deployment-wide workspace read permissions for WatchAllWorkspaceBuilds endpoint (#26985) 2026-07-06 13:04:48 -07:00
Hugo Dutka 96130e2bc5 chore(coderd/x/chatd): address generation review items (#26517)
Addresses the deferred `coderd/x/chatd/generation.go` review comments
from PR #26109: [required generation
dependencies](https://github.com/coder/coder/pull/26109#discussion_r3380311853),
[scoped chat
variables](https://github.com/coder/coder/pull/26109#discussion_r3387161874),
[generation state error
handling](https://github.com/coder/coder/pull/26109#discussion_r3387191468),
[generation attempt return
values](https://github.com/coder/coder/pull/26109#discussion_r3387251382),
[generation fence
verification](https://github.com/coder/coder/pull/26109#discussion_r3387288234),
and [chatdebug outcome
recording](https://github.com/coder/coder/pull/26109#discussion_r3387544273).

This makes generation task dependencies explicit, packages generation
attempt episode state into a struct, and centralizes generation task
fence checks for generation transitions.

Generated by Coder Agents, closely reviewed by Hugo.
2026-07-06 16:37:45 +00:00
Ethan 2cbc464c72 fix(coderd): invalidate chatd provider cache on AI provider changes (#26987)
chatd subscribes to `ChatConfigEventChannel` and invalidates its
provider cache on a `providers` event kind, but nothing ever published
that kind. AI provider CRUD only publishes on
`AIProvidersChangedChannel` (consumed by aibridged and aibridgeproxyd),
so chatd's provider cache only converged via its 10 second TTL.

Subscribe chatd to the same `AIProvidersChangedChannel` publish instead
of adding a second publish, per the review feedback on #26207: one
publish, multiple subscribers. The now-unused `ChatConfigEventProviders`
kind is removed so `ChatConfigEvent` stays scoped to model configs, user
prompts, and advisor config, and can't regrow a dead subscriber.

Follow-up to CRF-5 from the review of #25673. Supersedes #26207.

Closes CODAGT-499
2026-07-07 00:20:54 +10:00
Danielle Maywood 7c78698a6d feat: notify users when chats are shared (#26914) 2026-07-06 13:15:58 +01:00
Cian Johnston b21e0717d5 feat: remove chat chain mode (#26980)
Removes OpenAI Responses "chain mode" from chatd. Closes CODAGT-445.

- Deletes `chatopenai/responses.go` (chain detection, activation, prompt filtering, response ID extraction) and its tests.
- Deletes the `ChainBroken` classification in `chaterror` and the chatloop retry bookkeeping that disabled chain mode mid-generation.
- Drops the `chain_broken` label from the `coderd_chatd_stream_retries_total` metric.
- Stops reading and writing `chat_messages.provider_response_id`
- Deletes the dead `ClearChatMessageProviderResponseIDsByChatID` query. Dropping the column is a follow-up migration.
- Deletes three chatloop hooks no caller sets (`ReloadMessages`, `DisableChainMode`, `PrepareMessages`), the dead `const AgentChatContextSentinelPath`, and stale chain-mode comments.

🤖 Generated by Coder Agents on behalf of @johnstcn.
2026-07-06 11:57:12 +01:00
Hugo Dutka c0e2811e01 fix(coderd/x/chatd): runner bootstrap race (#26989)
Addresses https://github.com/coder/internal/issues/1589. Supersedes
https://github.com/coder/coder/pull/26455. The previous fix did not
handle the case where there were 2 runners for the same chat: one
shutting down and one being bootstrapped. The one shutting down could
queue a stale state update which would cause the new runner to
immediately exit.
2026-07-06 12:45:16 +02:00
Michael Suchacz 4669e1c538 feat(coderd/x/chatd): improve title generation repeatability and quality (#26982) 2026-07-06 20:10:47 +10:00
Susana Ferreira 64ca5ac9f8 refactor(coderd): add budget.CurrentPeriod for [start, end) windows (#26972)
## Description

Extracts the AI budget period computation into a shared
`budget.CurrentPeriod` helper. Pure refactor with no wire-value changes.

## Changes

- Add `budget.CurrentPeriod(now, period)` returning a
`PeriodWindow{Start, End}` in UTC. Unknown periods return an error,
matching the pattern used by `ResolveUserAIBudget` for unknown policies.
- Update the callers and respective tests to use `CurrentPeriod`.

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
2026-07-06 10:37:27 +01:00
Danny Kopping dd216b96fe feat: record provider_item_id for tool usage (#26856)
## Summary

Plumbs the Responses output item id (added as `ToolUsageRecord.ItemID` in #26855) through to the database, captured independently of the `provider_tool_call_id` correlation key. Hosted tools (`web_search_call`, etc.) only have an item id; agentic tools have both.

`provider_item_id` is specific to the OpenAI Responses API; it stays empty for chat completions and Anthropic messages, which have no separate item id.

## Changes

- Migration `000534`: nullable `provider_item_id` column on `aibridge_tool_usages`.
- Proto: `item_id` field 11 on `RecordToolUsageRequest`.
- Server handler: persists `provider_item_id` and adds it to structured logging.
- Translator: maps `ToolUsageRecord.ItemID` to the proto field.

## Tests

- `TestRecordToolUsageProviderItemID`: real-database round-trip asserting `provider_item_id` persists for both hosted and agentic tools, independently of `provider_tool_call_id`.

Stacked on #26855. Linear: AIGOV-96

---

_This PR was produced by opencode (agent) using the_ _`anthropic/claude-opus-4-8`_ _model, under human direction and review._
2026-07-06 09:29:07 +02:00
Itay Dafna d7ad85f7f6 feat: support multiple OIDC redirect URIs (#25408)
This PR adds a new opt-in setting, `CODER_OIDC_REDIRECT_ALLOWED_HOSTS`,
that lets a single Coder deployment complete OIDC login on more than one
hostname. When the allowlist is non-empty, Coder picks the OIDC
`redirect_uri` based on the incoming request's Host header (validated
against the list) instead of always using the static URL derived from
`CODER_ACCESS_URL`. When unset, the (default) behavior is identical to
today.

The motivation is that a single Coder deployment is frequently reachable
via multiple hostnames - for example, an internal hostname for users on
a corporate VPN and a different hostname routed through a zero-trust
gateway for users off-VPN - but OIDC login today only works on whichever
single hostname `CODER_ACCESS_URL` points to, because the `redirect_uri`
sent to the IdP is fixed at server startup. Users who reach the
deployment on any other valid hostname can see the login page but fail
the OIDC callback, since the IdP redirects them back to a hostname they
can't reach (or whose cookies they don't have).
2026-07-05 06:36:33 +02:00
Susana Ferreira 1989db0e2b feat(coderd): enforce ai budget on pre-request path (#26915)
## Description

Adds pre-request AI budget enforcement to `aibridged`. Requests are rejected with HTTP 403 when the user's aggregated spend for the current period has reached their effective limit.

## Changes

- Add `IsBudgetExceeded` RPC to `aibridgedserver`. Resolves the user's effective budget, aggregates spend over the caller-supplied `[period_start, now]` window, and returns whether the limit has been reached along with the effective limit.
- Wire the check into `aibridged`'s HTTP handler. The caller computes the period start (monthly for now) and passes it in the request.
- Reject exceeded requests with HTTP 403 Forbidden and a message directing the user to contact an administrator.
- Add `dbtime.StartOfMonth` alongside `StartOfDay` for period computation.
- Add real-DB tests covering the enforcement path: month-boundary excludes prior-period spend, and a new user override unblocks a previously-exceeded user.

Closes https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:53:36 +01:00
Susana Ferreira be9c95c8f5 feat(coderd): accumulate user daily AI spend on token usage (#26741)
## Description

Adds post-response spend accumulation to `RecordTokenUsage`.

## Changes

- Wrap the token usage insert and daily spend increment in a single transaction.
- Skip the spend update when the user is unbudgeted, the model is unpriced, or the computed cost is non-positive.

Depends on #26562

Closes https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:42:37 +01:00
Susana Ferreira fcdd029d74 feat: add ai_user_daily_spend table and queries (#26562)
## Description

Adds the spend tracking table and queries needed by [AIGOV-427](https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation) (post-response accumulation) and [AIGOV-428](https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement) (pre-request enforcement).

## Changes

- Add `ai_user_daily_spend` table to aggregate per-user, per-effective-group AI spend by UTC day.
- Add `UpsertUserAIDailySpend` and `GetUserAISpendSince` queries.

Closes https://linear.app/codercom/issue/AIGOV-426/add-daily-spend-table-and-queries

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:29:25 +01:00
Paweł Banaszewski 79a74fc817 Revert "chore: hide AI Gateway key management UI/CLI/API (#26879)" (#26913)
Reverting
https://github.com/coder/coder/commit/377c1309b7a42ead9cfbd864f0af8e9b6e472851
since release 2.35 was already cut:
https://github.com/coder/coder/tree/release/2.35
2026-07-02 13:45:05 +00:00
Cian Johnston 843754a547 refactor(coderd/x/chatd): remove dead model-routing dispatch shim (#26942)
Follow-up to #26862 ("remove direct chat routing"), which collapsed the
routing discriminated union into a single `aiGatewayModelRoute` but left
a one-path dispatch shim behind in `model_routing.go`.

Removes
`resolveModelRouteForConfig`/`resolveModelRouteForProviderType`/`newModel`
wrapper functions that did nothing but call their `*AIGateway*`
counterparts, and renames the `*AIGateway*` targets to take over those
names directly. Also collapses a redundant if/else in
`title_override.go` where both branches called the same function with
the same effective argument, and has `chatutil.NormalizedStringPointer`
delegate to the existing `coderd/util/strings.EmptyToNil` instead of
reimplementing empty-string-to-nil logic.

No behavior change.

<details>
<summary>Investigation notes / decision log</summary>

Two independent read-only investigations were run over `coderd/x/chatd`
looking for cleanup opportunities following #26862: one focused on
residue from that PR specifically, one a general over-engineering pass
on the whole package. Both independently converged on the
`model_routing.go` shim as the top finding (verified zero divergent call
sites).

Other candidates considered and explicitly deferred/rejected for this
PR:

- Renaming away the vestigial `AIGateway` prefix package-wide:
cosmetic-only, touches many call sites, skipped.
- Inlining the `chatcost` subpackage into `chatd`: unrelated to #26862,
skipped.
- Deleting the deprecated `AIGatewayRoutingEnabled` deployment flag:
confirmed dead/no-op, but intentionally kept as a back-compat shim per
#26862; removal should follow the same deprecation cadence as other
deprecated deployment options, as a separate, differently-timed change.
- Folding `chatutil` entirely into `chatprovider`/`chatopenai`:
`NormalizedStringPointer` overlapped with
`coderd/util/strings.EmptyToNil` (now reused), but `NormalizedEnumValue`
has no equivalent elsewhere in the repo and still has 2 real call sites,
so the package stays.

</details>

---
Generated by Coder Agents on behalf of @johnstcn.
2026-07-02 11:22:56 +01:00
Jon Ayers cda7a9d4f4 fix: reword autostop reminder to use relative countdown (#26948) 2026-07-01 21:24:42 -05:00
Jon Ayers 40bceeaf8d fix: nats timing flakes (#26944) 2026-07-01 17:57:42 -05:00
Yevhenii Shcherbina db7f4438b4 feat: generate STS external ID for Bedrock role assumption (#26869)
Implements:
https://linear.app/codercom/issue/AIGOV-495/add-externalid-to-prevent-confused-deputy-problem

When a Bedrock provider assumes an IAM role via STS, the gateway now
generates a unique external ID for it and sends that value on every
`AssumeRole` call. The external ID guards against the [confused deputy
problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
on cross-account role assumption. Per [AWS's
recommendation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html),
the gateway generates and owns the value rather than accepting one from
the operator; that ownership is what makes it effective, since a party
who knows another's external ID can't induce the gateway to send it.

The external ID is server-owned and read-only over the API. It is
generated once, when a provider first has a `role_arn`, and is stable
thereafter. Clients cannot set it: create rejects any supplied
`external_id`, and update rejects a value that differs from the stored
one. An update may echo the stored value back unchanged, so the normal
read-modify-write flow (GET the provider, change a field, PATCH the full
settings object) keeps working. The value is not a secret and is
returned on GET so operators can copy it into the target role's trust
policy as an `sts:ExternalId` condition.

It is persisted in the existing JSON settings blob, so there is no
migration or audit-table change.
2026-07-01 20:44:15 +00:00
Cian Johnston 4936ff9808 refactor: deprecate AIGatewayRoutingEnabled, remove direct chat routing (#26862)
This PR removes the now-dead direct-routing code:

- Deletes the direct routing implementation.
- Collapses the resolvedModelRoute discriminated union into aiGatewayModelRoute.
- Removes the dead providerKeys cascade.
- Deletes the preferredShortTextCandidates quickgen function.
- Simplifies the advisor override error handling.
- Deprecates the AIGatewayRoutingEnabled deployment option. It is now a no-op so as to not break existing deployments on upgrade.

Once direct routing was gone, the AI Gateway became mandatory for chat, which surfaced gaps in how the product behaves with the gateway disabled:

- Exposes ai-gateway-enabled to the frontend via embedded page metadata.
- Disables the chat composer via the existing AgentSetupNotice when the gateway is disabled, for both new and existing chats.
- Fixes nil/typed-nil chatDaemon panics on startup and shutdown when gateway is disabled.
- Fixes chat WebSocket from retrying the still-gated stream endpoint forever when the gateway is disabled.
2026-07-01 20:15:03 +01:00
Kyle Carberry 58f70b4488 fix(coderd/x/chatd): sanitize workspace MCP tool names (#26928)
## Summary

Workspace MCP tools (servers a workspace declares in `.mcp.json`) take
their model-facing name from the server key joined with the tool name as
`serverName__toolName`. That name reached the model **unsanitized**, so
a server or tool name containing a character outside
`^[a-zA-Z0-9_-]{1,128}$` (for example `@`) produced an invalid tool
name. Anthropic and Bedrock reject the whole request with `HTTP 400`:

```
tools.N.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$'
```

which fails the entire turn, not just the one tool. The remote MCP path
(`mcpclient`) and the AI Gateway path (`aibridge/mcp`) already sanitize;
the workspace path did not.

Alternative to #26853 (thanks @ibdafna for the report and repro).

## Fix

Sanitize and length-cap the **model-facing** name, and keep the original
`serverName__toolName` as a `routingName` the workspace agent uses to
reach the original server and tool. `NewWorkspaceMCPTools` builds a
whole set and disambiguates names that collide after sanitization (for
example server keys `foo.bar` and `foo_bar` both exposing `echo`) so
every tool stays addressable in the model's name-keyed dispatch map.
Names already within the allowed set are unchanged, so there is no
behavior change for valid names.

The sanitizer is local to `coderd/x/chatd/chattool`; the fix does
**not** touch the `aibridge` package or the remote MCP client.

### Changes
- `coderd/x/chatd/chattool/mcpworkspace.go`: local provider-safe
sanitizer + length cap, `routingName` for the agent proxy, and
`NewWorkspaceMCPTools` for set-level collision disambiguation.
- `coderd/x/chatd/chatd.go`: build the pinned workspace tool set via
`NewWorkspaceMCPTools`.

## Why sanitize here (not at `.mcp.json` / agent parse)?

The agent uses `serverName__toolName` to route to the real downstream
server (it splits on `__` and calls the original tool name), so
sanitizing at parse time would break routing or merely relocate the
original->sanitized mapping. Sanitization is also a provider constraint
the agent has no knowledge of, and coderd/agent version skew means
coderd must sanitize at its own boundary regardless. The model-facing
boundary in chatd is the right place.

## Test plan
- `@` in a name is sanitized for the model while the original routes to
the agent; a valid name is unchanged; an over-length name is truncated;
colliding names in a set are disambiguated while each still routes to
its own original name.
- `go build`, `go vet`, `golangci-lint`, and `go test
./coderd/x/chatd/chattool/...` pass locally.

<details>
<summary>Design notes / decision log</summary>

**Constraint that drives the design.** The tool name is both the
identifier shown to the model (and the key the model layer dispatches
tool calls by) and, for the workspace path, the string the agent splits
on `__` to route back to the original server and tool. Those roles
conflict once sanitization changes the name, so the name is sanitized
for the model while the unsanitized form is kept as `routingName`.

**Options considered.**
1. **Chosen:** sanitize in the workspace path only, with helpers local
to `chattool`. Smallest blast radius; no new cross-package dependency.
This matches the shape of the other MCP paths (`mcpclient` keeps
`originalName` + `configID`) without sharing code.
2. Sanitize at `.mcp.json` parse time or in the agent. Rejected: breaks
routing (the agent needs the original name), pushes a provider concern
into the agent, and coderd must still defend its own boundary because
the agent and coderd version independently. Tool names also come from
the downstream server at list time, not from `.mcp.json`, so parsing
cannot fully validate them.
3. Extract a shared sanitize/truncate/dedupe helper into `aibridge/mcp`
and adopt it in `mcpclient` too (so the remote path also gains collision
disambiguation). This DRYs all paths, but it grows chatd's coupling to
the `aibridge` subsystem and expands scope/behavior/tests in the remote
path for what is a workspace-path bug. Left out deliberately to keep
this change minimal and self-contained; it can be a separate refactor.
4. Sanitize once at the provider serialization boundary (chat loop). The
only truly generic spot, but the model dispatches by name, so it needs a
reverse (sanitized -> original) mapping and set-wide collision handling
in the model layer. Larger, riskier change.

**Notes.**
- The workspace path defines its own sanitizer (`[^a-zA-Z0-9_-]` -> `_`)
and a `maxModelToolNameLen = 64` constant that mirrors the strictest
provider limit (OpenAI 64, Bedrock 128), rather than importing
`aibridge/mcp`, so it carries no new dependency.
- The set builder sorts before assigning suffixes so disambiguation is
stable across turns.

</details>

---

_Opened by Coder Agents on behalf of @kylecarbs. Alternative to #26853._
2026-07-01 20:34:25 +02:00
Mathias Fredriksson 047c47495b refactor: drop chat_model_configs provider column (#26877)
The provider type already lives authoritatively in ai_providers.type,
reachable on every active row through ai_provider_id, which the
chat_model_configs_ai_provider_required_when_active CHECK makes
mandatory. The stored provider string was a denormalized copy the system
kept in sync with a startup backfill and no longer needs.

Every surface now derives provider type from the linked ai_providers
row. Telemetry is the one exception: it keeps emitting provider, now
sourced from ai_providers.type via a JOIN, so the BigQuery column and the
Nexus dashboards that read it are unaffected. The experimental HTTP/SDK
response drops provider and makes ai_provider_id required, since those
endpoints return only active configs; consumers resolve provider type
from ai_provider_id and the AI providers listing.

This ships in a single release with no compatibility window: production
reads the table via SELECT *, so a pre-drop binary fails config reads the
moment the column is gone. Operators must scale to zero before upgrading,
and there is no rollback.

Closes CODAGT-599
2026-07-01 15:59:55 +03:00
Jon Ayers 6b3341aad3 fix!: require org membership for user ACLs (#26852) 2026-07-01 02:15:08 -05:00