mirror of
https://github.com/coder/coder.git
synced 2026-10-31 02:43:21 +08:00
827c11f43a8d255a62c8d1c0de3e4fd7347fd8c6
2801
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
827c11f43a |
docs: update Coder Agents admin navigation to AI Settings (#28572)
## Summary Fixes stale admin navigation on the three Coder Agents platform-controls pages not covered by #28482: the retention pages pointed at tabs that do not exist ("Behavior", the old Agents settings "Lifecycle" tab), and template optimization pointed at the removed **Agents** > **Settings** > **Manage Agents** navigation. Breadcrumbs follow the **Admin settings** > **AI** > **Coder Agents** convention established by #28482. ## Changes - `docs/ai-coder/agents/platform-controls/chat-retention.md`: replace the nonexistent **Behavior** tab with **Admin settings** > **AI** > **Coder Agents** > **Lifecycle**. - `docs/ai-coder/agents/platform-controls/chat-debug-retention.md`: same page, same convention, aligned with chat-retention.md. - `docs/ai-coder/agents/platform-controls/template-optimization.md`: replace **Agents** > **Settings** > **Manage Agents** > **Templates** with **Admin settings** > **AI** > **Coder Agents** > **Templates**. ## Scope note The first revision also fixed `models.md`, `advisor.md`, `chat-debug-logging.md`, and `platform-controls/index.md`. Those pages are covered by #28482 with the same corrections, so this PR reverted them to avoid conflicting edits and adopted that PR's breadcrumb convention instead. Linear: DOCS-715 https://linear.app/codercom/issue/DOCS-715 <details><summary>Analysis evidence</summary> - `site/src/router.tsx` (~735-780): `/ai/settings/{lifecycle,templates,coder-agents}` are the live routes; `/agents/settings/{admin pages}` (~833-890) are `Navigate`-only redirects. - No "Behavior" tab exists in any router revision; `RetentionPeriodSettings` and `DebugRetentionSettings` both render in `LifecyclePageView.tsx`. - Breadcrumb convention **Admin settings** > **AI** verified against `AdminSettings.tsx` / `AISettingsSidebarView.tsx` in #28482. </details> Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
31a976b288 |
chore(docs): update release docs for v2.35.6 (#28577)
Automated docs update for v2.35.6 release. Created by `releasetui`. |
||
|
|
409049d1d8 |
docs: document org-scoped admin configuration for agents (#28482)
## Summary Documents the organization scoping of Coder Agents admin configuration, and corrects the navigation paths, access wording, and default-model instructions that no longer matched the product. Documentation only, no product code changes. Follows #28473, which moved the organization model overrides into an **Organization settings** section on **Admin settings > AI > Coder Agents**. #28473 is merged, so this targets `main`. Closes CODAGT-715 ## Changes - **New page** `docs/ai-coder/agents/platform-controls/organizations.md`, registered in `docs/manifest.json`. Covers the deployment/organization scope split, what an upgrade changes, how an organization without models or MCP servers behaves, who configures each scope, and how model and MCP server sharing works. - **`models.md`**: models are organization-scoped while providers and their credentials stay deployment-wide. Each organization has one default model, and the first model added becomes it. Documents the **Default** badge and the **Set as Coder Agents default model** form option in place of the star action that no longer exists, and points model overrides at **Organization settings**. - **`getting-started.md`**: replaces the Owner-only claim with the actual access model, adds an organization selection step, and uses the **Add model** label. - **`platform-controls/index.md`**: separates deployment-wide controls from organization-scoped controls, documents personal model overrides alongside the model configuration section rather than as an enforcement caveat, and adds `--agents-allowed` plus the `agents-allowed:` search filter. - **`platform-controls/advisor.md`** and **`platform-controls/virtual-desktop.md`**: the Advisor and Virtual desktop settings are cards in the **Deployment settings** section of the **Coder Agents** page, not sidebar entries. **Providers** is a sidebar item, not a tab. - **`platform-controls/mcp-servers.md`**: slug uniqueness is per organization, the **Add server** label is correct, and the page is no longer described as admin-only. - **`architecture.md`**, **`index.md`**, **`tasks-to-chats-migration.md`**: organization scope corrections and organization-scoped model routes. - Breadcrumbs standardized to **Admin settings > AI > ...**, verified against `AdminSettings.tsx` and `AISettingsSidebarView.tsx`. <details> <summary>Review follow-ups</summary> The first revision leaked implementation detail into user-facing pages and contained several claims that did not match the UI. The follow-up commit removed and corrected the following. Removed as implementation detail: - The `chat_model_config` RBAC resource, its actions, and the token scopes. - The `available`, `unavailable_reason`, and `unsupported_providers` response fields, along with the `missing_api_key`, `fetch_failed`, and `user_api_key_required` values. - Role and permission tables, ACL storage mechanics, audit internals, and JSON error bodies on the new organizations page. - Migration mechanics with no effect on what a user does. Corrected against the product: - The star action does not exist. The list shows a **Default** badge and the add/edit form offers **Set as Coder Agents default model** (`ModelRow.tsx`, `ModelFormFields.tsx`). - The first model added to an organization becomes its default automatically (`coderd/exp_chats.go`). An organization without a stored default does not fail every chat, so that claim is gone. - Advisor and Virtual desktop are cards under **Deployment settings** (`CoderAgentsPageView.tsx`). - The MCP servers page does not require deployment configuration edit permission (`AISettingsSidebarView.tsx`). - Button labels: **Add model** and **Add server**. - The organization query parameter is `org`, confirmed by `modelOrganizationSearchParam`. Left alone deliberately: pre-existing defects outside the introduced text, including the `/api/v2/chats` route prefixes, create-chat examples missing `organization_id`, and the pre-existing MCP permissions table. </details> ## Testing - `pnpm run format-docs` and `pnpm run lint-docs` pass. - `make lint/emdash` passes. No emdash or endash added. - `grep -rn "Defaults & overrides" docs/` returns nothing. - Every navigation path, button label, and access claim checked against the corresponding file under `site/src/pages/AISettingsPage/` and `site/src/modules/management/`. --- This pull request was generated by Coder Agents on behalf of @ethanndickson. |
||
|
|
c07bde80c4 |
fix: use medium badges and shorter shared key label on agent API keys page (#28557)
On `/agents/settings/api-keys`, bumps the provider key status badge and the enabled model badges from `sm`/`xs` to the medium (`md`) size, and shortens the status label "Using shared key" to "Shared key". Updates the matching Storybook assertion and the status indicator name in `docs/ai-coder/agents/models.md`. Verified with `tsc` and the page's Storybook tests (15 passing). --- *Created by Coder Agents on behalf of @tracyjohnsonux.* |
||
|
|
79856ebd15 |
docs: reference Coder Agents instead of Coder Tasks in style guide (#28539)
## Summary Updates the Caitlin the Citizen Developer persona in the style guide to reference Coder Agents instead of the deprecated Coder Tasks product. ### Changes * `docs/.style/style-guide/audience-and-scope.md`: replaced both "Coder Tasks" mentions with "Coder Agents" in the Caitlin persona description and Coder surface list. <details><summary>Context</summary> Coder Tasks is deprecated as of v2.34 and replaced by Coder Agents. This change keeps the internal style guide's persona reference aligned with current product naming. </details> --- This PR was drafted by a Coder Agent on behalf of Matt. |
||
|
|
7cf24b1a37 |
docs(docs/ai-coder/agents): document the chat search: full-text filter (#28474)
Documents the `search:` full-text filter in `docs/ai-coder/agents/chat-search-syntax.md` and drafts the release notes copy for chat history search. Describes `main` plus #28319, which must merge first: stemming and stopword removal apply to message content only; titles and PR titles keep `simple` whole-word matching. All claims verified against the implementation. Two statements in CODAGT-727 were inaccurate and are documented as the code behaves: - A value that tokenizes to no searchable words returns an **empty list**, not HTTP 400 (`TestListChats_Search/NoSearchableWordsReturnsEmpty`). HTTP 400 covers only an empty `search:` value, unsupported keys, bare terms, or combining `search:` with `title:`, `pr_title:`, or `pr:` (`coderd/searchquery/search.go`). - Results are **paginated** with the standard chat list cursor (default 50), not a bounded non-paginated set (`GetChats`, `listChats`). Ordering is pinned first, then most recently updated. Also verified: message bodies are indexed by the dbpurge sweep (10-minute tick, up to 5 x 10,000 rows, newest first) while titles and PR titles are searchable immediately; numeric values match exact PR numbers; `title:`, `pr_title:`, and `repo:` retain `ILIKE` semantics. `pnpm run format-docs` and `pnpm run lint-docs` pass. Closes [CODAGT-727](https://linear.app/codercom/issue/CODAGT-727/chat-search-documentation-and-release-notes) <details> <summary>Release notes draft</summary> ### Chat history search You can now search your chat history by what was said, not just by title. The chat list `q` parameter accepts a new `search:` filter that performs full-text search across chat titles, pull request titles, PR numbers, and message content, for example `?q=search:"kubernetes restart"`. Matching is case-insensitive and supports quoted phrases, `OR`, and `-` negation. Message content additionally matches English word stems (`refactor` finds a message containing `refactoring`). `search:` cannot be combined with the `title:`, `pr_title:`, or `pr:` filters; the existing substring filters are unchanged. **Upgrading with existing chat history:** the message search index is populated by a background job (every 10 minutes, up to 50,000 messages per run, newest first). Upgrades are not blocked, and chat reads and writes are unaffected while the index fills. Older messages appear in search results progressively; messages indexed before the stemming upgrade match exact word forms until re-indexed. Titles and PR titles are searchable immediately. </details> --- 🤖 This PR was generated by Coder Agents on behalf of @f0ssel. |
||
|
|
378ac91b49 |
fix: stem chat search terms with the english text search config (#28319)
Chat message search used the `simple` text search config, which folds case but does no stemming, so `refactor` did not match messages containing `refactoring`. Message bodies now index and query with the `english` config, which stems both sides. Titles, PR titles, and all other filters are unchanged. Migration `000585` adds a `chat_messages.search_tsv_config` enum column recording which config produced each stored vector and stamps existing vectors `'simple'` (a column-only update; vectors and indexes are untouched, since rewriting `search_tsv` would block message writes on large tables). The dbpurge sweep gains a bounded `ReindexStaleChatMessagesSearchTsv` pass that rewrites stale vectors newest first and stops for the process lifetime once drained. Until a row is rewritten, `GetChats` matches it with the config that produced it, so pre-migration exact-form searches keep working during the drain. Vectors written by old binaries mid rolling upgrade cannot stamp the config, stay pending, and self-heal on the next upgraded sweep. Steady-state DB load is unchanged. The down migration resets `english` vectors to `NULL` so the parent version's existing sweep rewrites them with `simple`. Closes [CODAGT-867](https://linear.app/codercom/issue/CODAGT-867/backend-search-improvements-to-handle-partial-matches) <details> <summary>Decision log</summary> - **`english` stemming over prefix matching.** On a 300k-row corpus a stemmed lookup is a single GIN entry-tree probe, while `term:*` prefix matching unions posting lists of every lexeme sharing the prefix (up to a full scan for short prefixes), is asymmetric, and loses the `websearch_to_tsquery` phrase/OR/negation UX. `pg_trgm` rejected as a heavier index with different semantics. - **Scope to message bodies only** (review). Titles and PR titles keep their `simple` FTS expressions and indexes, identical to main. - **Config-versioned queue over migration-time reset** (review). An unqualified `UPDATE ... SET search_tsv = NULL` rewrites every indexed row in the migration transaction, and an old replica could backfill reset rows with `simple` vectors that permanently leave the `search_tsv IS NULL` queue. The config column makes staleness explicit and both problems disappear. - **Enum over `text`** (review) for the config column. - **Trade-offs.** English stopwords are dropped from message search (stopword-only queries match no messages); stemming is English-specific; titles do not stem; prefix matching remains out of scope. </details> --- 🤖 This PR was generated by Coder Agents on behalf of @f0ssel. |
||
|
|
eedc0ece47 | fix: avoid 500 when generating chat title from rename dialog and remove unused endpoint (#28306) | ||
|
|
26de6140fb | feat: add flag to disable workspace agent context sync (#28522) | ||
|
|
b6d7653231 |
fix: apply MCP server selection when editing a chat message (#28471)
## Problem
Editing a user message in an Agent chat silently dropped the MCP server
selection. The composer renders the MCP picker in edit mode and toggles
update local client state, so the picker displayed the new selection,
but the edit request omitted `mcp_server_ids` at every layer (frontend
request builder, `codersdk.EditChatMessageRequest`, the `PATCH
/chats/{chat}/messages/{message}` handler, and `chatd.EditMessage`). The
chat's persisted selection never changed and the regenerated turn ran
without the newly enabled MCP tools. Two dogfood users hit this within
hours; there is no error anywhere and the UI shows the opposite of the
server state.
## Changes
- `codersdk`: add `MCPServerIDs *[]uuid.UUID` to
`EditChatMessageRequest`, mirroring `CreateChatMessageRequest` (nil
preserves the current selection).
- `chatd`: extract the send path's MCP update block into one shared
`applyRequestedMCPServerIDs` helper (explore-subagent snapshot
immutability guard plus Force On enforcement, Cure53 CDM-02-010) and
call it from both `SendMessage` and `EditMessage`, so enforcement cannot
drift between the two paths.
- `coderd`: extract the send handler's request validation (dedupe,
enabled-in-organization check, persisted-ID exemption) into
`normalizeRequestedChatMCPServerIDs` and wire it into the edit handler,
which now threads the selection into `EditMessageOptions`.
- Frontend: the edit request now includes `mcp_server_ids:
[...effectiveMCPServerIds]`, exactly like the send path, making the
picker's displayed state real.
- `make gen` artifacts (swagger, API docs, `typesGenerated.ts`).
## Tests
Each layer is covered and was proven with independent red toggles
(removing one layer's wiring fails only that layer's tests):
- chatd (`TestEditMessage_MCPServerIDs`): edit applies a provided
selection, nil preserves it, an emptied list cannot remove a `force_on`
server, and explore subagent chats keep the spawn-time snapshot.
- API (`TestPatchChatMessage/MCPServerIDsApplied`,
`MCPServerIDsInvalidRejected`): persistence via the endpoint, omission
preserves, unknown IDs get the same 400 as the send path.
- Storybook (`EditAppliesMCPServerSelection`): toggling a server on
during an edit puts it in the edit request payload.
Note: the `AgentChatPage.stories.tsx` story "Queued For Capacity After
Polling" fails locally on current main as well (verified against the
main baseline with this branch's changes reverted); it is unrelated to
this diff.
Remote dogfood UAT ran on the exact head and passed, including proof
that the regenerated turn actually gains the newly enabled MCP server's
tools.
> 🤖 Xum acted on Mike's (@ibetitsmike) behalf. • Model:
`anthropic:claude-fable-5`
<!-- xum-attribution: model=anthropic:claude-fable-5 -->
|
||
|
|
76962273d8 |
feat: add coder organizations edit for default member roles (#28227)
`default_org_member_roles` was only settable through Terraform or a raw
`PATCH /api/v2/organizations/{organization}`. This adds a CLI
equivalent.
```console
coder organizations edit --org acme --default-org-member-roles organization-workspace-access,organization-template-admin
coder organizations edit --org acme --default-org-member-roles "" # grant no roles
```
- The flag replaces the organization's current list rather than adding
to it. It accepts a comma-separated list, may be repeated, and
de-duplicates. An empty value removes every role.
- Removing a role prompts for confirmation, listing the roles being
removed, unless `-y` is passed. Adding roles applies without a prompt.
- The resulting set is printed, so no follow-up command is needed to
confirm what was applied.
- The organization is chosen with `-O/--org` or `$CODER_ORGANIZATION`,
and is implicit when the caller belongs to exactly one organization.
The org update endpoint is behind `FeatureMultipleOrganizations`, so the
command is enterprise/premium only. Non-built-in roles are rejected by
the server, and `dbauthz` already prevents a caller from setting a
default they could not assign to a member individually.
<details>
<summary>Implementation notes</summary>
## Shape
`codersdk.UpdateOrganizationRequest.DefaultOrgMemberRoles *[]string` and
the enterprise `patchOrganization` handler already existed; only the CLI
was missing, and nothing in `cli/` called `UpdateOrganization` at all.
No server or SDK changes are needed.
`organizations edit` follows the partial-update pattern of `coder groups
edit`: check `ParsedFlags().Changed(...)`, populate only the fields the
user set, and leave the rest nil so the handler preserves them. Passing
no flags is an error.
Two earlier shapes were tried and dropped:
- **A `default-org-member-roles` entry in the `organizations settings`
registry.** Every sibling entry unmarshals an SDK type that maps 1:1 to
a dedicated endpoint; this field has no such endpoint. Reusing
`UpdateOrganizationRequest` for the piped JSON meant a stray `name`
field could rename the org, and the documented `show > file`, edit, `set
< file` workflow failed on the default organization, whose name is
reserved.
- **A `--default-org-member-roles` flag on `organizations create`.**
`create` cannot set display name, description, or icon either, so a
roles-only flag there is asymmetric. It would also require
`CreateOrganizationRequest` to grow the field plus server-side
validation.
## Known sharp edge
`serpent.StringArray` resets its slice when any flag value is empty, so
`--default-org-member-roles a --default-org-member-roles ""` clears the
list rather than keeping `a`. This is pinned by a test and called out in
a code comment. The confirmation prompt catches it interactively; a
script passing `-y` would not. A custom flag value that ignores empty
occurrences was prototyped and reverted as more machinery than the
behavior warranted.
## Validation
Manually verified against a local dev deployment with a premium license:
setting, adding, clearing, prompt accepted and declined, duplicate and
whitespace handling, invalid role rejection, and a plain member being
denied. Confirmed with `/authcheck` that a member loses workspace-create
access when the roles are cleared and regains it when restored, and that
the change is recorded in the audit log.
## Follow-ups worth considering
- `--name`, `--display-name`, `--description`, and `--icon` on
`organizations edit`; all already exist on `UpdateOrganizationRequest`.
Rename deserves its own discussion.
- Setting the defaults to `organization-admin` makes every member an org
admin, including the ability to edit the org further, and those users
still render as plain members in member listings. Reachable today via
the API, UI, and Terraform; a warning on privileged roles would help.
- `dbauthz.InsertOrganization` has no `canAssignRoles` check, unlike
`UpdateOrganization`. Harmless while the only caller passes the
deployment default constant.
- `docs/admin/users/organizations.md` still points users at the
dashboard or a raw `PATCH`.
</details>
---
Created by Coder Agents on behalf of @jscottmiller.
|
||
|
|
3bdd395faa |
docs(docs/admin/templates/extending-templates): clarify coder_env merge_strategy does not include host environment (#28466)
`merge_strategy` on `coder_env` only combines values across `coder_env` resources targeting the same name. It does not know about the value already set by the workspace's image or host, such as the base `PATH`. A user hit this in #27501: appending to `PATH` with three `coder_env` resources replaced the host's `PATH` entirely, since none of them referenced the existing value. Clarify this in the docs, and update the `PATH` example to show referencing `$PATH` in one of the resources so the host's original value is kept. Refs #27501 <details> <summary>Investigation notes</summary> - `coderd/provisionerdserver/provisionerdserver.go` builds the merged env starting from an empty map (`MergeExtraEnvs`), only combining `coder_env` resources with each other. - `agent/agent.go`'s `updateCommandEnv` applies the result with `os.ExpandEnv(v)`, which expands `$VARNAME` against the agent's real process environment. - Confirmed with a standalone repro that `value = "$PATH:/usr/local/cuda/bin"` preserves the host's real `PATH` and appends the extra directory, and that this composes correctly across multiple `coder_env` resources with `merge_strategy = "append"` as long as one of them references `$PATH`. - A companion PR updates the `terraform-provider-coder` schema description and example that feed the registry docs: https://github.com/coder/terraform-provider-coder (branch `bpmct/docs-env-merge-host`). </details> --- *Opened by Coder Agents on behalf of @bpmct.* --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
042421694f |
fix: surface duplicate AI provider hostname as warning, not error (#28208)
Two live `ai_providers` rows can share the same `base_url` hostname.
This is an intended configuration (AIGOV-97): the direct routing path
(`/api/v2/ai-gateway/{name}/...`) routes by provider name and fully
supports it. Only the MITM proxy path (`aibridgeproxyd`) cannot
disambiguate, because its `nameByHost` map is 1:1.
`classifyProviderRow` marked the second provider as
`ProviderStatusError`, which surfaced as a misleading log warning and a
`status=error` Prometheus label. The provider is not actually broken; it
is fully routable via the direct path. The error was only visible in
server logs and Prometheus, not in the API or UI.
This change:
- Extracts `BaseURLHostname` into `coderd/aibridged` and eliminates
three duplicate implementations (proxy classifier, chatprovider, and the
API layer now uses it for status checks).
- Downgrades the proxy classifier from `ProviderStatusError` to a new
`ProviderStatusProxyExcluded` status. The provider is not broken; it is
just not reachable via the MITM proxy path.
- Adds `Status *AIProviderStatus` to `codersdk.AIProvider` with
`Warnings []string`, matching the `WorkspaceProxy` precedent.
- Populates warnings on create, update, GET, and list when a provider's
hostname collides with another enabled provider.
- Adds an integration test proving that a duplicate provider is routable
via the direct path but not via the proxy path.
Refs AIGOV-596
Generated by Coder Agents
https://github.com/user-attachments/assets/bf323837-5121-4d1b-ab19-593f1d32374e
|
||
|
|
defd5d48a1 |
docs: clarify that Coder server should run on a separate machine (#27468)
Closes [DOCS-600](https://linear.app/codercom/issue/DOCS-600/make-it-clear-in-the-quickstart-that-you-should-install-the-coder). The Quickstart guide has readers install and run `coder server` on the same machine they're working from, without calling out that this is only for a quick trial. Coder's actual value comes from remote development, so hosting the Coder server on a separate machine has a lot more power than running it locally. This adds two notes to `docs/get-started/index.md`: - In **Prerequisites**, explaining that this quickstart runs the server locally for convenience, but a real deployment should host it on a separate machine, and linking to the [Install](https://coder.com/docs/install) page. - In **Step 2: Install and start Coder**, a shorter reminder right before the install commands. ---- Created using Coder Agents 🤖 --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
df6a33ad37 |
docs: fix broken extending-templates examples (dynamic-parameters, docker-in-workspaces) (#28100)
## Summary Fixes broken examples on the `extending-templates` pages surfaced by the DOCS-637 runtime drift sweep, and completes the dangling `external-auth` reference. Verified against `main` and the CLI source (`coder templates init --id`, `coder templates push`). ## Changes | Page | Fix | |------|-----| | `dynamic-parameters.md` | The **Dynamic Validation** snippet referenced an undefined `show_cpu_cores` through a stray `count = data.coder_parameter.show_cpu_cores.value ? 1 : 0` line copied from the earlier Hide/Show Options example. Standalone copy failed `terraform validate`. Removed the `count` line (and its misleading "Only show this parameter" comment) so the snippet is self-contained. The valid occurrence in the Hide/Show example (where `show_cpu_cores` is defined) is untouched. | | `docker-in-workspaces.md` | The rootless Podman step used `echo "kubernetes-with-podman" \| coder templates init`, but `kubernetes-with-podman` isn't a built-in starter id, so the command scaffolds nothing. Step 4 now starts from the built-in `kubernetes` starter (`coder templates init --id kubernetes`) and shows the minimal Podman delta inline: the maintained `codercom/example-podman:ubuntu` base image, the `container.apparmor.security.beta.kubernetes.io/dev = unconfined` pod annotation, and a `github.com/fuse` device limit. The starter already runs as UID/GID 1000 with `run_as_non_root`, which rootless Podman needs. Also switches the deprecated `coder templates create` to `coder templates push`. Follow-up from review: swapped `ghcr.io/coder/podman` for the maintained `codercom/example-podman:ubuntu` image and linked its source, added a note that the AppArmor annotation is deprecated on Kubernetes 1.30+ (`securityContext.appArmorProfile`), and added an EKS Auto Mode caveat to the Bottlerocket section. | | `external-auth.md` | The **External Auth Providers** sentence dead-ended at "...in the Coder Control Plane. See" with no link. Completed it with a link to the admin External Authentication page (`../../external-auth/index.md`), the target chosen in review. | ## Dependency The `docker-in-workspaces.md` example now references `codercom/example-podman:ubuntu`, published by coder/images#339. That PR is approved but should merge (and publish the image to Docker Hub) before this PR merges, so the docs don't point at an image that doesn't exist yet. Linear: https://linear.app/codercom/issue/DOCS-645 > This PR was created with AI assistance (Coder Agents). --------- Co-authored-by: Ben Potter <me@bpmct.net> Co-authored-by: Ben Potter <ben@coder.com> |
||
|
|
54d4ae9aca | fix: detect Xum client in aibridge after Mux rename (#28448) | ||
|
|
c275327fb7 |
feat: audit operational agent settings (#28369)
Depends on #28394. Audit changes to seven operational agent settings through a typed `chat_operational_settings` resource. Each handler serializes baseline capture and mutation with a per-setting advisory lock, keeps both operations in one transaction, and records field-specific diffs. Effective no-op requests now skip both the database upsert and the success audit, including absent rows whose effective value is already the default. The change also retains the allowlisted raw `site_configs` query, updates generated audit and API surfaces, adds failure and concurrency coverage, and reuses the existing AuditPage resource-filter story pattern. Relates to CODAGT-720. |
||
|
|
0ef331fcd0 |
feat!: scope chat model override settings to organizations (#28442)
Moves chat model override settings from deployment scope to organization scope, following the org-scoping of chat model configs in #28440. Depends on #28440 (based on `ethan/chat-models-availability-consolidation`). Remote dogfood UAT passed on this change, including multi-organization scenarios on a licensed deployment with real models. ## Problem Chat model configs are now owned by organizations, but the override settings that reference them stayed deployment-wide: admin subagent/title/compaction overrides in `site_configs`, personal overrides in `user_configs`, and the advisor model embedded in the advisor runtime JSON. A single deployment-level override can only point at one organization's model config, so overrides effectively worked for the default organization only, and title generation had to soften its hard-failure contract to tolerate unusable overrides. ## Fix Admin overrides (general, explore, title generation, compaction, advisor) and personal overrides (root, general, explore) become organization-scoped rows in two new typed tables, `chat_organization_model_overrides` and `chat_user_model_overrides`. Composite foreign keys on `(organization_id, model_config_id)` make cross-organization model references unrepresentable. The migration deletes the legacy serialized `site_configs`/`user_configs` override keys instead of migrating them; admins and users re-select models in the new organization-scoped settings. New endpoints live under `/api/experimental/organizations/{organization}/chats/model-overrides` (admin defaults) and `/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides` (personal overrides); the legacy deployment-level routes are removed. chatd resolves overrides via the chat's organization, restoring the hard-failure contract for configured-but-unusable title generation overrides. The deployment-level `AllowUsers` switch and advisor runtime limits stay deployment-scoped. The Coder Agents settings UI gains a per-organization "Defaults & overrides" page under AI Settings > Models, and the personal overrides page gains an organization selector for users in multiple organizations. Advisor telemetry now reports per-organization override rows. **BREAKING CHANGE**: Existing deployment-level overrides are dropped, not migrated. Every organization starts with no overrides after upgrade, and model selections must be re-applied in the new settings. The legacy deployment-level override endpoints are removed. Closes https://linear.app/coder/issue/CODAGT-872 > [!NOTE] > Xum (AI agent) authored this PR on behalf of @ibetitsmike. <!-- xum-attribution: model=claude-opus-4-6 thinking=high --> --------- Co-authored-by: Ethan Dickson <ethan@coder.com> |
||
|
|
3809314b31 |
refactor!: consolidate chat model availability (#28440)
## Summary Consolidate chat model management and runtime availability into a single organization model collection contract. The organization model response now includes provider availability, unavailable reasons, and unsupported-provider details, so callers no longer need a separate availability request. Depends on #28439. ## Problem The organization-scoped API exposes two overlapping representations of the same chat models: ```text GET /api/experimental/organizations/{organization}/chats/models GET /api/experimental/organizations/{organization}/chats/models/available ``` The collection route returns organization-owned model configurations and redacted provider descriptors. The availability route returns a second catalog-oriented response with provider availability and synthetic catalog entries. Callers must fetch, reconcile, and cache both responses even though they describe the same effective set of models. This creates duplicated server logic and separate SDK, OpenAPI, and frontend types. It can also produce inconsistent client state when one request succeeds while the other fails or when the two responses are refreshed at different times. ## Fix Return all model-management and runtime-availability information from the organization model collection route: ```text GET /api/experimental/organizations/{organization}/chats/models ``` `OrganizationChatModelsResponse` continues to return the caller-readable organization model configurations and redacted provider descriptors. Provider descriptors now also include: - `available` - `unavailable_reason` The response additionally includes `unsupported_providers`, allowing clients to explain configurations that the Agents harness cannot use. Frontend model settings, agent creation, existing chat recovery, and model override surfaces now consume this single collection query. This keeps model ownership, caller visibility, provider availability, and unsupported-provider guidance in one cache entry and one authorization path. The default-organization compatibility route remains available: ```text GET /api/experimental/chats/models ``` It now returns the same consolidated `OrganizationChatModelsResponse` contract instead of the former availability response. ## Breaking change This removes the separate organization model availability endpoint: ```text GET /api/experimental/organizations/{organization}/chats/models/available ``` Clients using that endpoint must migrate to: ```text GET /api/experimental/organizations/{organization}/chats/models ``` This PR also removes the corresponding public experimental client and schema surface: - `ExperimentalClient.ChatModelAvailability` - `ChatModelAvailabilityResponse` - `ChatModelProvider` - `ChatModelCatalogEntry` - The generated OpenAPI schema and frontend API/query types for the availability endpoint Callers must use `ExperimentalClient.ChatModels` and `OrganizationChatModelsResponse` instead. Availability is now reported on each `ChatModelProviderDescriptor`, and unsupported providers are returned in `OrganizationChatModelsResponse.UnsupportedProviders`. The default-organization compatibility route retains its URL, but its response schema changes from `ChatModelAvailabilityResponse` to `OrganizationChatModelsResponse`. Direct HTTP clients and generated clients that decode the previous response type must be updated even if they do not use the removed organization-scoped `/available` route. |
||
|
|
a53c6e6366 |
feat: audit chat model config changes (#28439)
Adds audit logging for chat model config creates, updates, deletes, ACL changes, and automatic default-model transitions. Audit records use the config display name or ID as the target and are emitted only after successful transactions. Depends on #27960. |
||
|
|
75ad45109d |
feat: use organization-scoped chat models (#27960)
Move chat model settings to `/ai/settings/organizations/:organization/models`, add an organization switcher, and redirect legacy settings paths. The pages use organization permissions for read, create, update, and delete controls. They use redacted provider descriptors. Agent creation loads models for the selected organization. Existing chats load models for the chat organization. Chat views warn about unavailable historical models, select a usable local recovery model, and disable generation when none exists. Settings that aggregate organizations retain successful results when one request fails. Depends on #27959 _This pull request description was generated by Coder Agents._ |
||
|
|
aefc761fcf |
feat!: enforce organization scope for chat models (#27959)
## Summary Move chat model management and runtime availability to organization-scoped API routes. The API now identifies the organization in every model-management operation, applies chat model RBAC and ACLs within that organization, and returns only models the caller can use there. Depends on #27958. ## Problem Chat model configurations now belong to organizations, but the existing experimental management and discovery APIs are deployment-shaped. Their routes do not identify an organization, SDK methods do not accept an organization ID, and item operations address a model configuration by ID alone. That contract cannot safely expose organization-owned model configurations. It also does not provide the organization-specific provider information, authorization behavior, ACL management, or recovery guidance needed by the settings and chat clients later in the stack. ## Fix Add organization-scoped chat model endpoints under: ```text /api/experimental/organizations/{organization}/chats/models ``` The new API provides: - Organization-scoped model listing and creation. - Model get, update, and delete operations scoped by both organization and model ID. - Model ACL read and update operations. - Organization-scoped runtime availability and provider status. - Redacted provider descriptors for model-management clients. - Chat model RBAC and row ACL enforcement for writes, runtime discovery, and Chatd model selection. - Recovery guidance when a chat's organization has no usable local model. Historical chats can retain a model reference owned by another organization, but runtime selection uses an authorized local default when one is available. Foreign, inaccessible, disabled, and otherwise unusable models are not exposed as usable selections for the organization. The existing collection and availability routes remain as default-organization compatibility endpoints in this layer: ```text GET /api/experimental/chats/model-configs POST /api/experimental/chats/model-configs GET /api/experimental/chats/models ``` These compatibility routes do not provide access to models in non-default organizations. The previous unscoped item update and delete routes are removed because item operations must include the owning organization. ## Breaking change This changes the experimental HTTP API and Go SDK contracts for chat model management. Clients must migrate model-management and availability requests to the organization-scoped routes and provide an organization identifier. In the Go SDK, the following methods now require an organization ID: - `ChatModelAvailability` - `ChatModels` - `CreateChatModel` - `UpdateChatModel` - `DeleteChatModel` `ChatModels` also changes its return value from `[]ChatModel` to `OrganizationChatModelsResponse`, which includes the organization models and the redacted provider descriptors required by management clients. New item and ACL methods likewise require both the organization ID and model ID. The default-organization compatibility routes reduce the immediate impact for collection and availability callers, but they do not preserve the previous item API or SDK method signatures. Generated clients and direct consumers of the experimental API must be updated before adopting this change. _This pull request description was generated by Coder Agents._ |
||
|
|
9bd8899359 |
refactor: clarify chat model resource names (#27958)
Rename the admin-managed SDK resource from `ChatModelConfig` to `ChatModel`. Rename catalog entries from `ChatModel` to `ChatModelCatalogEntry` and replace `ChatModelsResponse` with `ChatModelAvailabilityResponse`. API routes and JSON fields do not change. Experimental SDK and OpenAPI schema names do change. Depends on #27957 _This pull request description was generated by Coder Agents._ |
||
|
|
cbe24a3da5 |
feat: add chat model config RBAC resource (#27957)
Add the `chat_model_config` RBAC resource, actions, and API key scopes. This layer includes the scope migration and generated API types. Model list queries apply token scopes, SQL authorization filters, and row ACLs. Write and default-management handlers keep deployment configuration authorization until the next API layer replaces it. Depends on #27956 _This pull request description was generated by Coder Agents._ |
||
|
|
4ce8e8ed56 |
docs: address AI Gateway documentation review feedback (#28309)
Applies the review feedback from #27794. Prose only. --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
cb52f5339d | feat: show a premium paywall on the external auth settings page (#28435) | ||
|
|
a2afb458c5 |
feat: add premium paywall conversion telemetry (#28425)
**TLDR:** When someone using free Coder taps a "Start trial for free" button, we now write down which page they tapped it on, and whether they went on to actually start the trial. That tells us which locked features make people want Premium. ## Brief summary - Records two things: the button being clicked, and a trial actually being started. - Each click gets a random ID that travels with the person to the trial form, so we can tell who finished from who walked away. - Records a short label for the page (like `appearance` or `audit_log`), never the web address, so no customer names are collected. #28226 added the trial form and has since merged, so this now targets `main`. ## Description Today the "Start trial for free" button just moves you to the Premium page and nothing is recorded, so there is no way to know which feature convinced someone. This adds two events: | Event | Happens when | |---|---| | `cta_click` | Someone clicks "Start trial for free" | | `trial_signup` | A trial licence is actually issued | The click generates a random ID. It rides along to the trial form and gets saved on the signup, so a click and a signup can be matched up later. Clicks with no matching signup are the people who gave up. Trials started without a paywall (for example straight from the sidebar) are labelled `direct`, so "no label" never gets confused with "broken". A few small choices worth knowing: - The ID is kept in browser session storage, not in the web address, so the URL stays clean. It expires after 30 minutes. - The trial signup event is recorded by the server, not the browser, so nobody can fake a signup. - The browser cannot say which kind of event it is sending. It can only report a click; the server labels it. - Only people allowed to start a trial are counted. The server rejects the rest, which matters because a few paywalls show their button to everyone. - Nothing is recorded at all when telemetry is turned off. There is deliberately no "paywall was shown" event. Every admin page visit would report one, which is a lot of noise for little insight. Every paywall in the [Premium Copy Updates](https://www.notion.so/coderhq/Premium-Copy-Updates-3c1d579be59280a59557cabd54fd8a1f) table has a label. The rows that do not are badges and alerts with no button to click. ## Proof Ran a local Coder with no licence and clicked the paywall button on the Appearance page. What the browser sent, and the reply: ```json POST /api/v2/deployment/premium-funnel-events -> 204 No Content {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","variant":"premium"} ``` What the browser saved for the trial form, same ID: ```json {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","createdAt":1787268520857} ``` The click then landed on the Premium page with the trial form. Screenshots of the network panel, the saved value, and the Premium page are attached below. The form itself was not submitted, since that would contact the real licence server. ## Testing - Backend tests: a click is recorded with the server's own label, an unknown page label is refused, and a normal member is refused. - Storybook tests: button clicked and ID saved on all three paywall styles, plus the case where the viewer only sees guidance text and has nothing to click. - Unit tests for the saved ID: expiry, bad data, unknown page label, and the `direct` fallback. - `make gen` is clean, and lint, types, and formatting pass. Two stories fail on `main` today and fail the same way here: `ExternalAuthSettingsPageView > Page` and `PremiumPageView > No License`. Neither file is touched by this branch; both were verified failing on `main` with this branch checked out elsewhere. ## Follow-up coder/coder-telemetry-server#45 saves these events for reporting. It merges after this one. <details> <summary>Plan and decisions</summary> **Goal:** find out which Premium paywall drives trial signups, and tell abandons apart from completions. **Options considered and dropped:** 1. Put `?from=appearance` in the URL. Rejected: messy for users. 2. Use router state. Rejected: lost if the page is refreshed. 3. Let the browser report the signup. Rejected: it could be faked or lost during the redirect. 4. Work out the page from the URL. Rejected: URLs contain organization and template names. 5. Report every paywall impression. Rejected: one event per admin page visit is noise, and the click is the signal. 6. Separate event types and tables. Rejected: makes the matching query harder for no gain. **Event shape:** ```go type PremiumFunnelEvent struct { ID uuid.UUID // event ID; for a click this is also the matching ID EventType string // cta_click | trial_signup, set by the server Source string // appearance, audit_log, ..., direct Variant string // which paywall design was shown AttributionID uuid.UUID // the click this signup came from UserID uuid.UUID CreatedAt time.Time } ``` **Where the code lives:** nothing under `components/` imports from `modules/`, so the paywall components stay presentational and only gain an `onCTAClick` prop. New wrappers in `modules/paywall/` own the reporting, one per paywall style, each taking a fixed `source`. **Self-review:** the `frontend-review` skill caught that reading permissions inside the wrapper forced an auth context into presentational page views, which broke their stories. Every call site already passes `canViewPremium={permissions.viewAllLicenses}` and the button only renders when that is true, so the wrapper no longer reads permissions and the server stays the gate. It also flagged one unattributed paywall on the Security page, now wired up as `browser_only`. </details> > Generated with [Coder Agents](https://coder.com/agents) on behalf of @david-fraley |
||
|
|
f44a3b59d7 |
feat: Premium Page CTA updates and form handling (#28226)
* Premium page has form for signing up for a trial * Input validation and normalization for trial form fields * api endpoint definition to make this processes seamless * license page updates to access `?success=true` queryparam and confetti <img width="1512" height="833" alt="Screenshot 2026-08-17 at 10 08 41 PM" src="https://github.com/user-attachments/assets/1766e724-c63a-4da9-b2bd-a98e6b66cc9b" /> <img width="1510" height="874" alt="Aug-17-2026 22-12-38" src="https://github.com/user-attachments/assets/3a2a0f35-1176-4290-aa0d-b3f7d66c15d6" /> ```mermaid sequenceDiagram autonumber actor Owner participant UI as UI<br/>TrialRequestForm + PremiumPage participant API as API<br/>api.ts + coderd middleware participant Coderd as Coderd<br/>postTrialLicense + trialer participant Licensor as Licensor<br/>v2-licensor + Postgres + pubsub + cache Note over UI: mutation.status = "idle" Owner->>UI: fills 10 fields, checks acknowledgement UI->>UI: Yup validate, strip "acknowledged", submit UI->>API: POST /api/v2/licenses/trial<br/>cookie + X-CSRF-TOKEN Note over UI: mutation.status = "pending" API->>API: verify CSRF, apiRateLimiter,<br/>apiKeyMiddleware sets actor API->>Coderd: dispatch Coderd->>Coderd: audit.InitRequest, authorize license:create,<br/>httpapi.Read validates, HasLicense is false Coderd->>Licensor: POST trial request<br/>deployment_id unspoofable, 10s deadline Licensor-->>Coderd: 200, raw signed JWT Coderd->>Coderd: ParseClaimsIgnoreNbf, uuid.Parse Coderd->>Licensor: InsertLicense, updateEntitlements,<br/>publish PubsubEventLicense, commit audit log Licensor-->>Coderd: database.License Coderd-->>API: 201 codersdk.License API-->>UI: 201 codersdk.License Note over UI: mutation.status = "success" UI->>Licensor: invalidate entitlements + licenses keys UI->>Owner: navigate /deployment/premium?success=true Note over Owner: confetti fires on the success param ``` --------- Co-authored-by: Matt Vollmer <matthewjvollmer@outlook.com> |
||
|
|
6348c83142 |
feat: register public clients without a secret (#28046)
Third in the stack, split out of #27873. This is where dynamic client registration first produces a public client. - A registration requesting token_endpoint_auth_method `none` issues no secret and persists the client_type derived in #28043. The response omits client_secret entirely, per RFC 7591 §3.2.1. - Discovery still advertises only the two secret-based methods. `none` is withheld until the token endpoint honors it, so a conforming client is not told to attempt an exchange that would be rejected. - The app and its secret are written in one transaction. They were two independent inserts, so a failed second one left a committed app that could never authenticate while still holding a registration access token. Pre-existing, but a public client's missing secret row makes the orphaned case unspottable by inspection. - registration_client_uri uses url.JoinPath, fixing a trailing-slash access URL producing //oauth2/clients/{id}. - Docs state both limits above, so the page does not describe a flow that returns 400 until #28047 lands. A client registered here cannot obtain a token yet; #28047 adds that. Dynamic client registration is off by default, so none of this is user-visible until then. Refs https://linear.app/codercom/issue/ENG-3029/oauth2-support-public-client |
||
|
|
c6deadbc6c |
docs: fix broken .style/ link and lift details block out of list item [DOCS-661] (#28180)
## What Two small docs fixes, split out of the offlinedocs Fumadocs migration (#27390) so that PR stays a focused tooling change and these land on their own review track. - `docs/about/contributing/documentation.md`: the Vale style-guide link pointed at `docs/.style/style-guide/`, which is excluded from the published docs corpus, so the relative link 404s on the live site. Repointed to an absolute github.com URL. - `docs/ai-coder/ai-gateway/monitoring.md`: lifted the "Possible client values" `<details>` block out of the list item so it renders as a top-level accordion, and restored a dropped blockquote continuation marker (`>`). ## Why Both are genuine docs bugs on their own: - The `.style/` link is broken on the live site today, because that directory is intentionally excluded from the published corpus. - The `<details>` nested inside a list item renders inconsistently; lifting it to a sibling block is portable across renderers. They are also prerequisites for the offlinedocs Fumadocs migration, whose stricter build-time sync hard-fails on unmapped inter-doc links and only converts a `<details>` to an accordion when it is a list sibling. Landing them here first lets the migration PR drop these two files on its next rebase. ## Validation - `make fmt/markdown` and `make lint/markdown`: clean (via pre-commit-light). - Docs-only change; no CI or build config touched. > This PR was created with AI assistance (Coder Agents). |
||
|
|
d989ae36cd |
docs: clarify who can set AI model prices and how they are keyed (#28401)
The docs named the `ai_model_price:update` permission but never said which role holds it, and nothing anywhere explained that a price is keyed by provider type and model, so every configured provider of that type shares it. This names the Owner role, documents the keying and why `openai-compat` cannot be priced, and repeats both in the CLI's `--provider` descriptions and `update` help. > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
d3b3aeaabc |
docs: refresh workspace-management screenshots with current UI (#28356)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Refreshes the six stale screenshots on the [Workspaces](https://coder.com/docs/user-guides/workspace-management) page (`docs/user-guides/workspace-management.md`) with the current Coder UI, recreating equivalent dummy data (matching template and workspace names) so the images stay recognizable. Linear: DOCS-55 ## Updated images | Image | Section | |-------|---------| | `creating-workspace-ui.png` | Creating workspaces | | `workspace-update.png` | Updating workspaces | | `workspace-automatic-updates.png` | Automatic updates | | `user-guides/workspace-bulk-actions.png` | Bulk operations | | `schedule.png` | Starting and stopping | | `templates/template-variables.png` | Repairing workspaces | Prose is unchanged; this is a screenshot-only refresh. ## Notes / limitations The originals came from production-like deployments with AWS/Azure/Kubernetes templates, multiple users, and aged data. These were reproduced on a local `scripts/develop.sh` (Docker) instance, so: - Template/workspace names, states (Running/Stopped/Failed), the "Selected 3 of 5" bulk selection, `use_kubeconfig=True`/`namespace=default`, and the auto-update = `Always` setting all match. - Instance-derived values cannot match: "used by" counts show `0 developers`, build times show `Unknown`, the connection-latency pill has no value, and the workspace name field is disabled by the template. <details> <summary>How these were produced (decision log)</summary> Followed the `dogfood` skill: 1. Started a local instance with `./scripts/develop.sh` (`v2.36.0-devel`, matching the docs version). 2. Created Docker-backed templates named to match the originals: `data-warehouse`, `analytics`, `api-services`, `frontend-web`, `pod-share-jupyter`, `aws-linux-ephemeral`, `azure-aks-pod`, plus `kubernetes2` ("Develop in Kubernetes"). The JetBrains module was dropped from the Docker template to avoid a plan-time error; `kubernetes2` uses a Coder-only variant declaring the real `namespace` and `use_kubeconfig` variables so it imports with `use_kubeconfig=true`. 3. Created workspaces: `admin/my-frontend` (outdated → update available, with a schedule), `admin/dev` (auto-update = Always), and `kirby/{data-science, aws-large, azure-space, ephem-demo (Failed), kirbs (Stopped)}`. 4. Captured each page with a headless Playwright script, hiding dev-only chrome (the sticky deployment banner, the navbar `::before` hazard stripe, and the `-devel` version chip) and cropping settings pages to the form. Images are 2x for crispness. 5. Each screenshot was visually verified against the original for content parity. </details> |
||
|
|
d58788cb23 |
fix: clarify AI model price configuration (#28372)
## Description Model prices displayed in the Agents model form are read-only because they are managed by AI Gateway. Add guidance and a link to the model price configuration documentation, and rename the related documentation subsection to **Configure model prices**. > [!NOTE] > Generated with Coder Agents by @ssncferreira. |
||
|
|
50ecd6959f |
feat: add a source filter to the AI model price list (#28329)
## Description With prices from the price book and prices set through the API both in the table, `list` showed only the price in effect and gave no way to tell where it came from. There was also no way to see the price book's entry for a model that had been overridden, since the listing resolves to the custom price. This adds a `source` column to the output and a `--source` filter for narrowing to one source, or to every row with `all`. Note: `--source` selects rows rather than models. A model carrying both a price book price and a custom one appears under either value, so the two filtered listings do not sum to the unfiltered one. `--source all` reports both rows at once. ## Changes - Add `Source` to `codersdk.AIModelPrice` and `AIModelPricesFilter`, and map it in `db2sdk`, which previously dropped it. - Filter `GetAIModelPrices` by source before resolution, so the price book's row for an overridden model stays reachable. - Accept `all` as a source, which reports every price a model holds instead of resolving to one. - Accept a `source` query parameter on the list endpoint, rejected with a 400 when outside the enum. - Add `--source` to the list command and a `source` column to its output. - Document the column and the filter, and lead the price section with `coder exp ai-model-prices list`. - Distinguish a deployment's default prices from the price book that ships with each release. Closes https://linear.app/codercom/issue/AIGOV-593 > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
19d1ee9fc8 |
feat: support custom prices for models in the price book (#28328)
## Description The experimental CLI could only price models the price book does not cover, so an operator with a negotiated rate for a model Coder already prices had no way to record it. The restriction existed because a single row served both the price book and prices set through the API, and the startup seeder would overwrite an override on the next restart. Each source now keeps its own row (https://github.com/coder/coder/pull/28204), so that no longer applies. This removes the restriction: a price set through the CLI takes effect over the price book for any model, and the book stays underneath it. ## Changes - Remove the validation that rejected models the price book covers. - Delete `prices.IsDefaultPriced` and the index behind it, which existed only for that check. - Generalize the `coder exp ai-model-prices update` help. - Document that a custom price overrides the price book and does not follow its updates. - Rework the spend approximation callout around what a custom price can and cannot correct. Closes https://linear.app/codercom/issue/AIGOV-594 > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
2f996d765a |
feat: remove task management MCP tools (#28354)
Removes the six task-management tools from the MCP tool surface in `codersdk/toolsdk`: - `coder_create_task` - `coder_delete_task` - `coder_list_tasks` - `coder_get_task_status` - `coder_send_task_input` - `coder_get_task_logs` Both the coderd-hosted MCP server (`coderd/mcp`) and the CLI `coder exp mcp server` derive their tool list from `toolsdk.All`, so removing the registry entries removes the tools from both servers with no server-side changes. The MCP server documentation (`docs/ai-coder/mcp-server.md`) is updated to drop the task-management entry from the tool overview. ## What stays - `coder_report_task` is intentionally kept: it is the agent-side status-reporting mechanism (`WithTaskReporter`), already excluded from the hosted MCP server, and required by `coder exp mcp server`, Claude Code configuration, and scaletest. - The `codersdk` Task SDK methods (`CreateTask`, `DeleteTask`, `Tasks`, `TaskLogs`, etc.) and the Tasks feature (CLI, UI, API) are untouched; this PR only removes MCP tool registration. - The task-only `taskIDDescription` helper is deleted; `userDescription` stays (used by `coder_create_workspace`). The code change is deletion-only: 970 removed lines across `toolsdk.go` and its test file, no added behavior. ## Validation - `go build ./...`, `go test ./coderd/mcp/`, and targeted `golangci-lint` pass. `go test ./codersdk/toolsdk/` passes all tests; the package-level goleak failure (agentssh goroutines) reproduces identically on unmodified `main` and is unrelated to this diff. - `pnpm run lint-docs` passes on the docs change. - Remote dogfood UAT ran against the toolsdk change and passed: `tools/list` on both the hosted MCP endpoint and `coder exp mcp server` confirms the six tools are absent and the remaining surface is intact (34 hosted tools; CLI adds `search`/`fetch`), a `tools/call` of `coder_get_authenticated_user` succeeds, and calling a removed tool returns `-32602 unknown tool`. `coder_report_task` still registers in the CLI server when `CODER_MCP_APP_STATUS_SLUG` is set. - The failing `Pixel / Review` commit status is unrelated: this PR contains no `site/` changes, and the same status fails on the last 8 `main` commits including this PR's merge base (`676b9bb06bf`). > Mux acted on Mike's behalf to create this pull request. |
||
|
|
2529a54cfc |
chore(docs): update release docs for v2.36.1 (#28357)
Automated docs update for v2.36.1 release. Created by `releasetui`. |
||
|
|
24b83dc727 |
feat: add organization picker to MCP server settings pages (#28150)
Adds an organization picker to the MCP server settings pages so
multi-org deployments can manage every organization's MCP servers
through the UI, not just the default organization's. Closes CODAGT-714
(scoped to MCP servers only; model configs stay deployment-wide until
CODAGT-709 lands).
## Stack Context
Top of the CODAGT-711/717/712/806 stack: MCP server configs are
org-scoped with RBAC, audit logging, per-config ACLs, and owner-scoped
user tokens. This layer makes the admin UI org-aware, resolving the
"default org only" limitation documented in the bottom PR.
## Summary
- The MCP servers list and add pages resolve the working organization
from an `org` search param (falling back to the default org), and render
an organization picker built on the existing `OrganizationAutocomplete`.
The picker only appears when more than one organization is visible, so
single-org deployments see no change.
- Selecting an organization rewrites the search param, refetches the
list for that org, and threads through to the add page so creation posts
to the selected organization.
- The update page now loads the server by ID (new organization-scoped
`GET /api/experimental/organizations/{organization}/mcp-servers/{id}`
frontend binding) instead of listing the default org and searching
client-side, so servers from any organization can be edited. Post-save
navigation returns to the owning organization's list via the server's
`organization_id`.
- Removes the now-unused `getDefaultOrganizationId` helper.
- Review hardening: organization display labels are disambiguated with
the unique org name when effective display names collide (picker
options, trigger text, and accessible name). Access is no longer
restricted to deployment admins: the pages, sidebar item, and nav entry
accept org-scoped `mcp_server_config` permissions, the picker lists only
organizations the user can view, content and affordances (add, edit,
delete) are gated on the selected organization's permissions, and
deployment admins keep full access everywhere.
## Testing
Storybook interaction stories cover: picker hidden with one org,
switching orgs refetches and swaps list contents, add posting to the
URL-selected org, add posting after an in-form org switch (asserting
typed form state survives the switch), and update loading by ID.
Red-green verified by breaking the search-param resolution and observing
the new stories fail.
> Mux acted on Mike's behalf for this PR.
|
||
|
|
8203b2ebaf | feat!: hide Coder Tasks behind the enable-ai-tasks flag (#28008) | ||
|
|
7ca7c30f40 |
feat: add group and user ACLs to MCP server configs (#27944)
Adds group and user ACLs to org-scoped MCP server configs so
organizations can restrict specific MCP servers to subsets of members,
mirroring the template ACL pattern.
## Summary
- Migration adds `group_acl`/`user_acl` JSONB columns (nested `{"<id>":
{"permissions": [...]}}` shape) and seeds every existing config with its
organization's Everyone group read entry (the Everyone group ID equals
the org ID), so member access is unchanged by default. Creation seeds
the same entry.
- The blanket org-member read grant from the base PR is replaced by ACL
evaluation: Rego requires org membership for every ACL grant, and
`GetAuthorizedMCPServerConfigs` compiles ACL-aware SQL filters.
- New `ActionShare` (org admins) gates `GET|PATCH
/api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}/acl`
(nested under the organization like the rest of the config surface).
PATCH validates principals against the config's organization, merges
sparse updates under a row lock, stamps `updated_by`/`updated_at`, and
is audited as a Write with `Old` captured before authorization. A config
deleted concurrently between the middleware fetch and the locked
re-fetch is concealed as 404, matching the update and delete handlers.
ACL columns are tracked in the audit table.
- ACL management is available in all editions (no enterprise
entitlement), documented in the MCP servers page. Revoking an ACL does
not retro-strip already-selected configs from existing chats; new
selection is blocked at chat create.
- Force On respects the ACL: the forced set is loaded as the chat owner,
so a `force_on` server whose ACL denies the owner never attaches at
create, send, or generation time.
- No rolling-upgrade machinery: upgrades run in scheduled maintenance
downtime, so the migration only backfills existing rows; the API sets
the Everyone read grant explicitly on every insert.
Stacked on #27943. Part of the MCP org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-712
UAT: verified on a dogfood instance with two members and a custom group:
Everyone-seed default visibility, group grant with Everyone removal
(non-member loses list/fetch/selection), foreign-principal rejection,
non-admin share denial (audited 403 / concealed 404), user_acl restore,
and audited ACL diffs.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
|
||
|
|
299e72ad30 |
feat: audit MCP server config changes (#27943)
Adds enterprise audit logging for MCP server config create, update, and
delete, with strict secret redaction. MCP configs hold credentials
(OAuth2 client secrets, API keys, custom headers), so admin changes to
them need an audit trail.
## Summary
- `enterprise/audit/table.go` gains an `MCPServerConfig` entry
enumerating every column: `oauth2_client_secret`, `api_key_value`, and
`custom_headers` are `ActionSecret` (never appear in diffs); dbcrypt
`*_key_id` bookkeeping, IDs, and timestamps are ignored; the remaining
config fields, including the endpoint URL fields, are tracked so
auditors can see which endpoints a config points at.
- Type registration in `coderd/audit` (diff, request, resource target
with org attribution), `codersdk/audit.go`, and a `resource_type` enum
migration.
- Handlers wire `audit.InitRequest`: create records `New`; update and
delete record `Old` from the param middleware before the
write-authorization check, so a readable-but-not-writable caller
produces an audited 403 while read-denied callers stay concealed as
unaudited 404s.
- Tests: create/update/delete audit entries, write-denied and
delete-denied 403 auditing, cross-org concealment producing zero
entries, and a serializer-level regression test proving none of the
three secret classes can reach a serialized diff.
- Review round: MCP config audit entries link to
`/ai/settings/mcp-servers/{id}`, audit table comments are trimmed per
review, and a fault-injection test pins that a config row surviving a
failed post-discovery credential update still gets its creation audit
entry.
Stacked on #27942 (org-scoped MCP configs). Part of the MCP
org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-717
UAT: verified on a trial-licensed dogfood instance: audit entries for
the full CRUD lifecycle with correct actor/org/target, redacted secrets
in the update and OAuth2 create diffs, and a full plaintext scan of the
audit dump finding zero secret leaks.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
|
||
|
|
f7a0de7a11 |
fix: harden org-scoped MCP config chat gating, updates, and visibility (#28065)
Hardens the org-scoped MCP server config surface from #27942 with fixes and regression pins that are independent of the core cutover: - Keep chats sendable after a selected MCP server is disabled (persisted selections are exempt from message-time rejection). - Keep cached MCP selections usable after a background refetch error (gate the composer on missing data, not `isSuccess`). - Merge PATCH updates onto the current row so unset fields are not clobbered. - Give auditors the full management view of MCP configs their audit logs reference (site and org auditor roles). - Regression pins: frozen OAuth2 callback path, cross-org concealment for item routes, OAuth callback token binding, and disconnect responses indistinguishable from nonexistent config IDs. - Storybook: semantic textbox queries in MCP loading stories; docs note that the MCP settings page needs deployment access. ## Stack context Part of the MCP org-separation stack (CODAGT-711 org scope -> apidocs -> hardening -> CODAGT-717 audit -> CODAGT-712 ACLs -> CODAGT-806 token RBAC). Split out of #27942 to keep the core cutover reviewable; each change here builds on the org-scoped routes and chat gating introduced below it. > Mux (AI agent) authored this PR on Mike's behalf. <!-- mux-attribution: model=claude-fable-5 thinking=high --> |
||
|
|
dd43574990 |
refactor: remove AI add-on badge, seat column, and add-on wording leftovers (#28004)
Removes all "AI add-on" labeling from the UI, plus two leftover "AI
Governance add-on" wording items from the licensing repackaging (AI
Governance is now included with Premium).
- Remove the **AI add-on** badge from the group settings AI budget
section and the AI Governance add-on card on the Licenses page.
- Remove the **AI add-on** column (check icon showing AI seat
consumption) from the Users and Organization Members tables, along with
its help popover, the `AISeatCell` component, and the
`shouldShowAISeatColumn` entitlement helper.
- Update related Storybook stories.
- Drop "add-on" from the AI cost control route comments in
`enterprise/coderd/coderd.go` and the `coder exp ai-model-prices`
prerequisite in `docs/ai-coder/ai-gateway/cost-controls.md` (leftovers
not covered by #28075/#28077/#28268).
The `has_ai_seat` API field is left intact.
<details>
<summary>Removal scope</summary>
Badge usages removed:
- `site/src/pages/GroupsPage/GroupSettingsPageView.tsx`
-
`site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/AIGovernanceAddOnCard.tsx`
AI seat column plumbing removed:
- `site/src/pages/UsersPage/UsersTable.tsx` and `UsersPage.tsx`
- `site/src/pages/OrganizationSettingsPage/OrganizationMembersTable.tsx`
and `OrganizationMembersPage.tsx`
- `site/src/modules/users/AISeatCell.tsx` (deleted)
- `AiAddonHelpPopover` in `site/src/modules/users/UserHelpPopovers.tsx`
- `shouldShowAISeatColumn` in
`site/src/modules/dashboard/entitlements.ts`
Add-on wording leftovers:
- `enterprise/coderd/coderd.go`: 7 route comments (comments only, no
behavior change)
- `docs/ai-coder/ai-gateway/cost-controls.md`: model prices CLI
prerequisite
</details>
---
🤖 This pull request was generated by Coder Agents on behalf of
@tracyjohnsonux.
|
||
|
|
f2bc9ab1f5 |
docs: complete swagger annotations for organization-scoped MCP routes (#28064)
Adds the missing swagger annotations for the eight organization-scoped MCP server config routes introduced in #27942 and checks in the regenerated API artifacts (`coderd/apidoc`, `docs/reference/api`). No behavior changes: 58 hand-written annotation lines, the rest is generated output. ## Stack context Part of the MCP org-separation stack (CODAGT-711 org scope -> apidocs -> hardening -> CODAGT-717 audit -> CODAGT-712 ACLs -> CODAGT-806 token RBAC). Split out of #27942 to keep the core cutover reviewable; these routes live under `/api/experimental`, where main already ships several MCP handlers without annotations, so the base PR is consistent with existing precedent until this lands. Closes nothing on its own; documentation completion for CODAGT-711. > Mux (AI agent) authored this PR on Mike's behalf. <!-- mux-attribution: model=claude-fable-5 thinking=high --> |
||
|
|
443e3b9b80 |
feat!: org-scope MCP server configs with RBAC (#27942)
Moves `mcp_server_configs` from deployment scope to organization scope
so each organization fully controls the MCP servers its members can use
with Coder Agents.
## Summary
- Migration: adds `organization_id` (NOT NULL, FK) and keeps existing
rows as the default organization's originals with credentials intact.
Other organizations start with no MCP servers and configure their own;
nothing is copied across organizations. Chats outside the default
organization keep any now-cross-organization `mcp_server_ids` entries;
the runtime already ignores IDs that do not resolve in the chat's
organization, so no data rewrite is needed. Slug uniqueness becomes
`(organization_id, slug)`.
- RBAC: new org-scoped `ResourceMCPServerConfig` with regosql converter
and `GetAuthorizedMCPServerConfigs`; org admins get in-org CRUD, org
members get read (replaced by ACL evaluation in the follow-up ACL PR in
this stack).
- API: all config routes nest under the organization, matching
templates: `POST|GET
/api/experimental/organizations/{organization}/mcp-servers` and
`GET|PATCH|DELETE .../mcp-servers/{mcpserverconfig}` (plus
`oauth2/connect`), resolved by a read-only param middleware that
conceals read-denied and cross-organization access as 404. Two routes
stay on the frozen `/api/experimental/mcp/servers/{mcpServer}` block:
the OAuth2 callback (the redirect URI baked into existing AS-side client
registrations) and `oauth2/disconnect`, which must remain reachable by
users removed from the organization so they can still revoke their token
grant.
- Chat runtime: selection validation and generation resolve configs
strictly by IDs, enabled state, and the chat's organization in SQL;
requested duplicates are normalized; invalid or cross-org IDs are
rejected with the precise ID list. IDs already persisted on a chat are
exempt from message-time rejection so disabling a selected server never
blocks sends; generation skips servers that are no longer usable.
- Frontend: API layer and admin settings pages target the new endpoints.
The admin page manages the default organization's servers; the org
picker is tracked separately (CODAGT-714).
- Security hardening from review: OAuth user grants are additionally
bound to `oauth2_revocation_url` (changing it invalidates grants, and a
racing OAuth callback gets 409 instead of recreating a grant).
Stack-wide SSRF protection for MCP config-directed traffic was split
into its own PR at the top of this stack (#28242) to keep this diff
reviewable; this PR keeps main's existing discovery IP-range guard.
OAuth2 auto-discovery now completes before the config row is inserted: a
failed discovery persists nothing, and there is no provisional row that
concurrent updates could race against.
Two follow-up PRs in this stack were split out to keep this diff
reviewable: #28064 completes the swagger annotations for the moved
routes (main already ships these experimental MCP handlers unannotated),
and #28065 carries hardening fixes and regression pins on top of the
cutover.
- Force On enforcement (landed on main mid-review) is org-scoped: the
forced set is read per chat organization
(`GetForcedMCPServerConfigsByOrganization`), so another organization's
`force_on` server never attaches to a chat.
## Breaking changes (experimental API)
The MCP server config endpoints move from the deployment-scoped
`/api/experimental/mcp/servers` block to organization-nested paths:
`POST|GET /api/experimental/organizations/{organization}/mcp-servers`
and `GET|PATCH|DELETE
/api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}`
(plus `oauth2/connect`). The old paths are removed, so API consumers
must supply an organization. Two routes intentionally stay on the frozen
`/api/experimental/mcp/servers/{mcpServer}` block: the OAuth2 callback
(its redirect URI is baked into existing AS-side client registrations)
and `oauth2/disconnect` (must remain reachable by users removed from the
organization). These endpoints are under `/api/experimental`, so no
deprecation window is provided.
## Rolling upgrades
During a rolling deploy, an old replica creating an MCP config can fail
the new `NOT NULL organization_id` constraint until it is upgraded
(reads are unaffected: old binaries' generated queries select their own
column lists). This matches the repo's existing precedent for additive
NOT NULL migrations (000562) and affects only the admin config-create
path in the upgrade window.
Upgrades are expected to run in scheduled maintenance downtime with the
database locked during migration, so the migration ships no
rolling-upgrade compatibility machinery. The down migration deletes
organization-created configs (their chat references are cleaned by the
000510 delete trigger) and restores deployment-wide slug uniqueness.
Part of the MCP org-separation stack (CODAGT-711 -> CODAGT-717 audit ->
CODAGT-712 ACLs -> CODAGT-806 token RBAC).
Closes https://linear.app/codercom/issue/CODAGT-711
UAT: validated end to end on a two-org dogfood deployment, including a
real pre-migration to post-migration upgrade, cross-org isolation
(404s), same-slug-two-orgs, chat selection gating, and a live MCP tool
call through the org-scoped generation path. The migration was later
revised to keep existing rows in the default organization only (no
per-organization copies); that revision is covered by the migration test
suite.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
---------
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
|
||
|
|
7ff1278ab3 |
feat: track Junie as an AI Gateway client (#28266)
## Problem Junie CLI works with AI Gateway, but every Junie session is recorded as `Unknown`. Junie sends `User-Agent: Junie:SNAPSHOT` (observed directly from the CLI), which `GuessClient` does not match, so usage, spend, and audit views cannot attribute Junie traffic. There were also no docs for pointing Junie at AI Gateway. ## Fix - Detect the observed `junie:` user-agent prefix and add the `Junie` client value. - Add the Junie icon so sessions render with a logo instead of the fallback question mark. - Add a Junie client page and list it in the compatibility table. - Add `Roo Code` and `Charm Crush` to the documented `client` filter values. Both were already implemented but missing from the list. ## Supported configurations | Provider | API type | Endpoint | |-----------|-------------------|--------------------------| | OpenAI | `OpenAIResponses` | `/openai/v1/responses` | | Anthropic | `Anthropic` | `/anthropic/v1/messages` | BYOK works on both through `extraHeaders` with `X-Coder-AI-Governance-Token`. Preview: https://coder.com/docs/@matifali-junie-ai-gateway-client/ai-coder/ai-gateway/clients/junie > 🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑💻 |
||
|
|
e003014ff8 |
docs: remove space in GitHub-style callout markers [DOCS-680] (#28305)
## What Normalize the one GitHub-style callout marker that had a space after `!` to the canonical tight form. - `docs/user-guides/workspace-access/jetbrains/gateway.md`: `> [! WARNING]` -> `> [!WARNING]` A full-corpus scan confirmed this was the only spaced marker. ## Why GitHub alerts, the hosted docs (coder.com), and the offlinedocs Fumadocs renderer all require the tight `> [!WARNING]` form. The spaced form rendered as literal `[! WARNING]` text instead of a styled alert, so this is a genuine rendering fix on GitHub and coder.com, not an offline-pipeline accommodation. Surfaced by the offlinedocs Fumadocs migration review (CRF-60 on #27390). Tracked separately from the mixed-case uppercasing fix (DOCS-681), per request. Linear: [DOCS-680](https://linear.app/codercom/issue/DOCS-680/remove-the-space-after-in-github-style-callout-markers-across-docs) > This PR was created with AI assistance (Coder Agents). |
||
|
|
2ee6f459b2 |
docs: uppercase mixed-case GitHub-style callout markers [DOCS-681] (#28304)
## What Uppercase the mixed-case GitHub-style callout markers in the docs corpus to the canonical all-caps form. - `docs/install/rancher.md`: `> [!Important]` -> `> [!IMPORTANT]` - `docs/user-guides/shared-workspaces.md`: `> [!Important]` -> `> [!IMPORTANT]` - `docs/user-guides/workspace-access/index.md`: `> [!Note]` -> `> [!NOTE]` A full-corpus scan confirmed these were the only mixed-case markers. ## Why GitHub alerts, the hosted docs (coder.com), and the offlinedocs Fumadocs renderer all require all-caps alert types (`[!NOTE]`, `[!IMPORTANT]`, and so on). The mixed-case form rendered as literal text instead of a styled alert (on some pages directly next to a correctly-rendered all-caps alert), so this is a genuine rendering fix on GitHub and coder.com, not an offline-pipeline accommodation. Surfaced by the offlinedocs Fumadocs migration review (CRF-60 on #27390). Tracked separately from the space-after-`!` fix (DOCS-680), per request. Linear: [DOCS-681](https://linear.app/codercom/issue/DOCS-681/uppercase-mixed-case-github-style-callout-markers-across-docs) > This PR was created with AI assistance (Coder Agents). |
||
|
|
023f61626d |
feat: default AI Gateway sessions list to a 24h time range (#28256)
The AI Gateway sessions list page takes 5-13 seconds to load because `ListAIBridgeSessions` scans all `aibridge_interceptions` rows (~1M on dogfood) when no time filter is set. This PR defaults the list to the last 24 hours of sessions, reducing the scan by roughly two orders of magnitude without any backend or migration changes: the `started_after`/`started_before` filters already exist end-to-end in SQL, searchquery, and the API. The default range is held in component state (not the URL) and merged into every query payload including prefetches, so the unbounded query never runs on page load. A time range filter in the filter bar lets users override the window explicitly, which doubles as a forensic tool. Picking a range writes quoted RFC 3339 timestamps into the existing filter query. There are no presets and no unbounded "all time" mode, so the fast path is the only path. Existing "All sessions"/"My sessions" presets reset the filter query, which resets the time window to the default 24 hours; that is intentional. Also makes the five filter triggers a uniform width and left-aligns the new picker so the search input keeps room on wide viewports. Depends on #28255 (the DateTimeRangeFilter component) and #28254 (filterQuery serialization). Part of [AIGOV-580](https://linear.app/codercom/issue/AIGOV-580/ai-gateway-sessions-page-takes-5-10-seconds-to-load) --- _Generated by Coder Agents on behalf of @johnstcn._$ --- **Stack:** #28254 (filterQuery fix) \u2192 #28255 (component) \u2192 #28256 (sessions page) |
||
|
|
7268cada94 |
docs: remove JetBrains Fleet references (#28301)
## Summary The `jetbrains-fleet` module was removed from [registry.coder.com](https://registry.coder.com) and `coder/registry`, but the docs still pointed users at Fleet and at the now-dead module page. ## Changes - Removed `docs/user-guides/workspace-access/jetbrains/fleet.md` and its screenshot - Removed the Fleet entry from `docs/manifest.json` - Dropped Fleet from the supported IDE lists in `jetbrains/index.md` and `workspace-access/index.md` - Glossary: replaced the Fleet link with Toolbox - Contributing guide: replaced the dead `jetbrains-fleet` registry link with `jetbrains` ## Validation - No remaining Fleet references in `docs/` (the only match left is Tailscale's "global fleet of DERP relays") - `docs/manifest.json` parses, `pnpm run lint-docs` reports 0 errors Preview: https://coder.com/docs/@docs-remove-jetbrains-fleet/user-guides/workspace-access/jetbrains > [!NOTE] > `site/static/icon/fleet.svg` and its entry in `site/src/theme/icons.json` are intentionally left in place. Removing the icon would break existing templates that reference that path. > The deleted page will 404 until a redirect is added in the website repo. > 🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑💻 |