## Summary
Adds `--ai-gateway-allow-byok` deployment option to control whether
users can use Bring Your Own Key (BYOK) mode with AI Gateway.
When disabled (`--ai-gateway-allow-byok=false`), BYOK requests are
rejected with a 403 and a message directing the admin to enable the
flag. Centralized key authentication works regardless of this setting.
Defaults to `true` (BYOK allowed).
---------
Co-authored-by: Danny Kopping <danny@coder.com>
## Summary
Fixes the "See all templates" link overlapping template items in the New
Workspace dropdown.
## Root cause
Two compounding issues:
1. **`OverflowY` className was being overwritten, not merged.** The
component spread `...attrs` (which included the caller's `className`)
onto the div, silently replacing its own base classes
(`overflow-y-auto`, `shrink`, `w-full`). This meant the template list
never scrolled independently.
2. **`PopoverContent` has `overflow-y-auto` in its base styles.** With
the inner `OverflowY` not scrolling, the *entire popover* became the
scroll container. The "See all templates" footer was part of that
scrollable flow and overlapped template rows as the user scrolled.
## Fix
- **`OverflowY`**: Destructure `className` explicitly and merge it with
the base classes using `cn()` so `overflow-y-auto` and `shrink` are
always preserved.
- **`PopoverContent`**: Add `overflow-hidden flex flex-col` to make it a
non-scrolling flex container. Only the `OverflowY` child scrolls.
- **`OverflowY` usage**: Add `min-h-0` so the flex child can shrink
below its content size when the popover's available height is
constrained.
## Screenshot
<img width="1460" height="1072" alt="image"
src="https://github.com/user-attachments/assets/9b519f2d-9806-44ca-a354-12248de36952"
/>
> 🤖 Generated with [Coder Agents](https://coder.com/agents)
Addresses review findings from #23827 that were added post-merge:
- Persisted attachments now store `organizationId`; mismatched orgs
pruned on restore
- Workspace selection reconciliation: stale IDs from previous orgs
dropped via derived `effectiveWorkspaceId`
- Org picker uses `permittedOrganizations()` for RBAC-aware filtering
- Org picker hidden when user belongs to only one org
- Ref-sync `useEffect` replaced with `useEffectEvent`
- `CreateWorkspace()` and `ListTemplates()` take `organizationID` and
`db` as required function parameters instead of optional struct fields —
compiler enforces them, removes scattered nil guards
- Cross-org template check in `CreateWorkspace` is now unconditional
- `ListTemplates` org-scoping filter now has test coverage
- `setupChatInfra` comment fixed; test helpers use params structs
instead of positional UUIDs
- Enterprise test documents that org admin only sees own chats (handler
hardcodes `OwnerID` — future work needs sidebar UI before lifting that
restriction)
> 🤖
_Disclaimer: produced mostly by Claude Opus 4.6 following detailed
planning._
## Summary
- Support multiple instances of the same AI Bridge provider type via
indexed env vars (`CODER_AIBRIDGE_PROVIDER_<N>_<KEY>`), following the
`CODER_EXTERNAL_AUTH_<N>_<KEY>` pattern
- Existing single-provider env vars (`CODER_AIBRIDGE_OPENAI_KEY`, etc.)
continue to work unchanged
- Setting both a legacy env var and an indexed provider with the same
name errors at startup to prevent silent misconfiguration
- Mark legacy provider fields (`OpenAI`, `Anthropic`, `Bedrock`) as
deprecated in `AIBridgeConfig` in favor of `Providers`
## Example
```sh
CODER_AIBRIDGE_PROVIDER_0_TYPE=anthropic
CODER_AIBRIDGE_PROVIDER_0_NAME=anthropic-corp
CODER_AIBRIDGE_PROVIDER_0_KEY=sk-ant-corp-xxx
CODER_AIBRIDGE_PROVIDER_0_BASE_URL=https://llm-proxy.internal.example.com/anthropic
CODER_AIBRIDGE_PROVIDER_1_TYPE=anthropic
CODER_AIBRIDGE_PROVIDER_1_NAME=anthropic-direct
CODER_AIBRIDGE_PROVIDER_1_KEY=sk-ant-direct-yyy
```
Each instance is routed by name:
- /api/v2/aibridge/**anthropic-corp**/v1/messages
- /api/v2/aibridge/**anthropic-direct**/v1/messages
Closes
[AIGOV-157](https://linear.app/codercom/issue/AIGOV-157/spike-to-understand-if-there-is-a-simple-way-to-handle-multi-api-key)
---------
Signed-off-by: Danny Kopping <danny@coder.com>
Previously we reserved some env vars that may collide with AI gateway.
These were incomplete and take away flexibility from the user, which
we're prioritizing in the first iteration of the feature.
The vitest process hung after all 2132 story tests passed because
leftover refetchInterval polls kept the Node.js event loop alive.
Components that set per-query refetchInterval override the
QueryClient default, causing HTTP requests through vite's proxy
to localhost:3000 (no backend) that never resolve cleanly.
Three fixes:
- preview.tsx: disable all automatic refetching defaults and cancel
in-flight queries on story unmount via useEffect cleanup
- storybook.tsx: save/restore the original window.WebSocket in the
withWebSocket decorator, clear pending timers in close()
- vite.config.mts: add explicit testTimeout, hookTimeout, bail, and
retry settings to the storybook vitest project
Also fix 5 story files that imported from @testing-library/react
instead of storybook/test.
> This PR was authored by Mux on behalf of Mike.
Chats sharing one workspace (e.g. sibling subagents) all wrote to
`/home/coder/PLAN.md`, causing plan file collisions. This change derives
a unique plan path per chat from the workspace home directory and chat
ID.
## Changes
* `write_file`, `edit_files`, and `propose_plan` reject any `plan.md`
variant (case-insensitive) at the workspace home root, with a clear
error pointing to the chat-specific path.
* Root chats receive a `<plan-file-path>` block inlined in the main
system prompt with the concrete path.
* Prompt and tool descriptions no longer hardcode `/home/coder/PLAN.md`.
* Plan path handling is POSIX-only (forward-slash), relying on the
contract that workspace agent paths are normalized before reaching
chatd.
* Updated `ProposePlanTool.stories.tsx` to use per-chat path examples.
* Full test coverage for plan path detection, legacy-path rejection in
all three tools, inline prompt rendering, and fallback behavior.
* Removes experiment `web-push`.
* Falls back to NoopWebpusher in case of error
* Checks browser capability in FE
* Adds note to agents getting-started docs regarding webpush without TLS
> 🤖
## What
Fixes the `TestSVGIconAttributes/texlive.svg` CI failure introduced by
#24312.
Two changes:
1. **Fix `texlive.svg` viewBox**: Changed from `0 0 1024 1024` to `0 0
256 256` (wrapping content in `<g transform="scale(0.25)">` to preserve
rendering). Also cleaned up non-standard attributes (`version`, `style`,
`preserveAspectRatio`) to match other icons.
2. **Add icon/theme paths to CI go filter**: Added `site/static/icon/**`
and `site/src/theme/**` to the `go` path filter in `ci.yaml` so Go tests
(`test-go-pg`, `test-go-pg-17`, `test-go-race-pg`) run when icons or
theme config change. This is why the failure wasn't caught on the PR —
only `site/` files were modified, so Go tests were skipped entirely.
Closes https://github.com/coder/internal/issues/1468
Adds a user preference to remove the `max-w-3xl` constraint on agent
chat messages, letting the content fill the available viewport width.
The toggle lives in **Agents > Settings > Behavior** under "Chat Layout"
and persists via `localStorage` (`agents.chat-full-width`). A
`useSyncExternalStore` hook (`useChatFullWidth`) provides same-tab
reactivity so flipping the toggle updates all mounted consumers
immediately — the chat timeline, chat input, the Suspense skeleton, and
the in-page loading view.
This was requested by a customer and is an individual user setting, so
it seems fine to add.
Adds a small LinearProgress (determinate + MUI-style dual-bar
indeterminate) and uses it on workspace build progress; drops MUI and
adds Tailwind keyframes + Storybook stories.
- New component under `site/src/components/LinearProgress/`
- `WorkspaceBuildProgress` switched off `@mui/material/LinearProgress`
- Added `bar-indeterminate` / `bar-indeterminate-2` animation keyframes
<img width="563" height="115" alt="image"
src="https://github.com/user-attachments/assets/bdcd8584-fa2d-487c-96f6-084891a9b084"
/>
`OrganizationAutocomplete` declared `options: Organization[]` (mutable),
but `useDashboard().organizations` returns `readonly Organization[]`.
Callers forced to spread to satisfy the type even though the component
never mutates the array.
While fixing this, found two more issues with how organization context
is resolved:
- `AgentCreateForm`, `ChatPageContent`, and `OrganizationRedirect` used
`organizations[0]` assuming the default org is first — nothing
guarantees that ordering.
- `ChatPageInput` was guessing the org from `useDashboard()` for file
uploads on existing chats, even though the chat already has
`organization_id`. In multi-org deployments, uploads could land in the
wrong org.
Changes:
- Widen `OrganizationAutocomplete` `options` to `readonly
Organization[]`
- Remove unnecessary `[...organizations]` spread in `AgentCreateForm`
- Replace all `organizations[0]` with `.find(o => o.is_default) ??
organizations[0]` for users not in the default org
- Eliminate `[...organizations].sort()` allocation in
`OrganizationRedirect` with direct `.find()` lookups (equivalent
priority: editable default → any editable → viewable default → any
viewable)
- Thread `chat.organization_id` from `AgentChatPage` →
`AgentChatPageView` → `ChatPageInput` so file uploads use the chat's
actual org instead of guessing
Closes#24285
> 🤖 PR initially created by Claude Opus 4.6
Add secret value validation to reject null bytes and values exceeding 32KB.
The 32KB limit applies uniformly to both env var and file secrets because the
value field is shared and the destination can change after creation.
Add file path validation to also reject null bytes and paths exceeding 4096
bytes.
Wire up secret value validation into both POST and PATCH handlers.
Replaces remaining MUI `<Skeleton />` usages with our shared `Skeleton`
component to keep loading states consistent and reduce MUI/Emotion
coupling. Also cleans up a few adjacent touched styles while preserving
existing behavior.
- Replace `@mui/material/Skeleton` with `#/components/Skeleton/Skeleton`
- Remove a few MUI-specific skeleton patterns at callsites
- De-mui adjacent Emotion styles
- Simplify filter layout handling with class-based responsive wrapping
Fixes https://github.com/coder/internal/issues/1436
* Adds organization_id to chats with backfill (workspace org → user org membership → default org)
* No support yet for ACLs (follow-up issue)
- Cross-org workspace binding rejected (both in `CreateChatRequest` and in `create_workspace` tool
- Adds `OrganizationAutocomplete` to `AgentCreateForm`
- Docs updated with `organization_id` in chats-api.md
> 🤖 Written by a Coder Agent. Reviewed by many humans and many agents.
---------
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This pull-request addresses a few design things within the `<AgentRow
/>` element. This is a follow-on from the previous work done with
implementing tabs.
- Workspace border can no longer be red, will always be orange (this was
done in a previous PR but not stated).
- Warnings have been moved to inside the Agent Logs collapsible.
- Warning badge has been added to the Agent Logs collapsible trigger.
- Collapsible is now open by default when there is an error inside of
the agent.
- Agent disconnected is no longer prominent by default.
Closes#16332
Previously `coder provisioner jobs list` showed no indication of what a workspace
build job was doing (i.e., start, stop, or delete). This adds
`workspace_build_transition` to the provisioner job metadata, exposed in
both the REST API and CLI. Template and workspace name columns were also
added, both available via `-c`.
```
$ coder provisioner jobs list -c id,type,status,"workspace build transition"
ID TYPE STATUS WORKSPACE BUILD TRANSITION
95f35545-a59f-4900-813d-80b8c8fd7a33 template_version_import succeeded
0a903bbe-cef5-4e72-9e62-f7e7b4dfbb7a workspace_build succeeded start
```
The "By model" and "Pull requests" tables on the PR Insights page
(`/agents/settings/insights`) were side-by-side at `lg` breakpoints, and
the Pull requests table was hard-capped at 20 rows by the backend.
- Replaced `lg:grid-cols-2` with a single-column stacked layout so both
tables span the full content width.
- Removed the `LIMIT 20` from the `GetPRInsightsRecentPRs` SQL query so
all PRs in the selected time range are returned.
- Can add this back if we need it. If we do, we should add a little
subheader above this table to indicate that we're not showing all PRs
within the selected timeframe.
- Added client-side pagination to the Pull requests table using
`PaginationWidgetBase` (page size 10), matching the existing pattern in
`ChatCostSummaryView`.
- Renamed the section heading from "Recent" to "Pull requests" since it
now shows the full set for the time range.
<img width="1481" height="1817" alt="image"
src="https://github.com/user-attachments/assets/0066c42f-4d7b-4cee-b64b-6680848edc68"
/>
> 🤖 PR generated with Coder Agents
Previously, editing a past user message in Agents chat waited for the
PATCH round-trip and cache reconciliation before the conversation
visibly settled. The edited bubble and truncated tail could briefly fall
back to older fetched state, and a failed edit did not restore the full
local editing context cleanly.
Keep history editing optimistic end-to-end: update the edited user
bubble and truncate the tail immediately, preserve that visible
conversation until the authoritative replacement message and cache catch
up, and restore the draft/editor/attachment state on failure. The route
already scopes each `agentId` to a keyed `AgentChatPage` instance with
its own store/cache-writing closures, so navigating between chats does
not need an extra post-await active-chat guard to keep one chat's edit
response out of another chat.
Fixes https://github.com/coder/internal/issues/1440
- Convert `OrganizationAutocomplete` to a purely presentational, fully
controlled component
- Accept `value`, `onChange`, `options` from parent; remove internal
state, data fetching, and permission filtering
- Update `CreateTemplateForm` and `CreateUserForm` to own org fetching,
permission checks, auto-select, and invalid-value clearing inline
- Memoize `orgOptions` in callers for stable `useEffect` deps
- Rewrite Storybook stories for the new controlled API
> 🤖 Written by a Coder Agent. Reviewed by a human.
Closes CODAGT-123
Assistant messages containing only source parts (no markdown or
reasoning)
were missing the bottom spacer that normally fills the gap left by the
hidden
action bar, causing them to sit flush against the next user bubble.
The existing fallback spacer guarded on `Boolean(parsed.reasoning)`, so
it
only fired for thinking-only replies. Replace that guard with the
broader
`hasRenderableContent` flag (which covers blocks, tools, and sources)
and
extract a named `needsAssistantBottomSpacer` boolean so future content
types
inherit consistent spacing without re-reading compound conditions.
Adds a `SourcesOnlyAssistantSpacing` Storybook story mirroring the
existing
`ThinkingOnlyAssistantSpacing` pattern for regression coverage.
Closes CODAGT-124
When a streaming assistant response finishes and moves from the live
stream
tail into the conversation timeline, the message jumps 4px upward. This
happens because the outer layout wrapper and live-stream section both
used
`gap-3` (12px), while the committed-message list used `gap-2` (8px).
Unify all three containers to `gap-2` so the gap between messages stays
at 8px regardless of whether they're streaming or committed, eliminating
the layout shift.
A Storybook story with play-function assertions locks the invariant: it
renders both committed messages and an active stream, then verifies both
the outer and inner containers report `rowGap === "8px"`.
Fixes a regression introduced in #24060 that could crash the frontend.
`thunk` is created by `useEffectEvent()`, and React 19.2 enforces that
effect-event functions are not invoked during render. The previous code
called `thunk()` inside a `setState` updater function, and React
executes updater
functions during render, so this became an illegal render-phase call.
The fix computes `next` in the interval callback (`const next =
thunk()`) and then stores it via `setComputedValue(() => next)`. This
keeps the `useEffectEvent` call outside render and also preserves
correct behavior when `func` returns a function value, because React
stores `next` instead of treating it as a functional updater.
Go's html/template has a built-in security filter (urlFilter) that only
allows http, https, and mailto URL schemes. Any other scheme gets
replaced with #ZgotmplZ.
The OAuth2 app's callback URL uses custom URI scheme which the filter
considers unsafe. For example the Coder JetBrains plugin exposes a
callback URI with the scheme jetbrains:// - which was effectively
changed by the template engine into #ZgotmplZ. Of course this is not an
actual callback. When users clicked the cancel button nothing happened.
The fix was simple - we now wrap the apps registered callback URI into
htmltemplate.URL. Usually this needs some validation otherwise the
linter will complain about it. The callback URI used by the Cancel logic
is actually validated by our backend when the client app
programmatically registered via the dynamic OAuth2 registration
endpoints, so we refactored the validation around that code and re-used
some of it in the Cancel handling to make sure we don't allow URIs like
`javascript` and `data`, even though in theory these URIs were already
validated.
In addition, while testing this PR with
https://github.com/coder/coder-jetbrains-toolbox/pull/209 I discovered
that we are also not compliant with
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 which requires
the server to attach the local state if it was provided by the client in
the original request. Also it is optional but generally a good practice
to include `error_description` in the error responses. In fact we follow
this pattern for the other types of error responses. So this is not a
one off.
- resolves#20323
<img width="1485" height="771" alt="Cancel_page_with_invalid_uri"
src="https://github.com/user-attachments/assets/5539d234-9ce3-4dda-b421-d023fc9aa99e"
/>
<img width="486" height="746" alt="Coder Toolbox handling the Cancel
button"
src="https://github.com/user-attachments/assets/acab71a6-d29c-4fa9-80ba-3c0095bbdc8f"
/>
<!--
If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.
-->
\`InlineMarkdown\` and \`MemoizedInlineMarkdown\` lived in
\`Markdown.tsx\`
alongside a static \`import { Prism as SyntaxHighlighter } from
"react-syntax-highlighter"\` — the full PrismJS build with ~300 language
grammars. Because \`DashboardLayout\` eagerly imports
\`AnnouncementBannerView → InlineMarkdown\`, every authenticated page
loaded and evaluated the entire Prism/refractor bundle on startup even
though syntax highlighting is only used in secondary views.
This PR moves \`InlineMarkdown\` and \`MemoizedInlineMarkdown\` into
their
own \`InlineMarkdown.tsx\` file that depends only on \`react-markdown\`
and
updates all six consumers to import from the new module.
\`Markdown.tsx\`
keeps the PrismJS import for the full \`Markdown\` component, which is
only reached through lazy-loaded routes.
> 🤖 Generated by Coder Agents
## Summary
Exposes `credential_kind` and `credential_hint` on AI Bridge session
threads, making credential metadata visible in the session detail API.
Each thread in the `/api/v2/aibridge/sessions/{session_id}` response now
includes:
- `credential_kind`: `centralized` or `byok`
- `credential_hint`: masked credential (e.g. `sk-a...pgAA`)
Values are taken from the thread's root interception.
## Changes
- `codersdk/aibridge.go`: Added `CredentialKind` and `CredentialHint`
fields to `AIBridgeThread`
- `coderd/database/db2sdk/db2sdk.go`: Populated from root interception
in `buildAIBridgeThread`
- `SessionTimeline.stories.tsx`: Added fields to mock thread data