mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
8203b2ebafc89f97cc4723fe482dde1567522cf5
2763
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. 🧑💻 |
||
|
|
71b5bc398f |
docs: fix broken callout on the AI Gateway Monitoring page (#28303)
The callout on this page is broken, as flagged by Atif. This PR moves the callout out of the `<details>` block to avoid the broken formatting. Fixes DOCS-679 <!-- 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. --> |
||
|
|
fc6d6babfa |
docs: add markdown_url front matter so the About page links to /docs.md (#28281)
The docs About page renders at `/docs/about`, but its Markdown source is the docs root `README.md`, whose Markdown twin is `/docs.md`. The path-derived mapping (`/docs/about` -> `/docs/about.md`) points the "Copy page / view as Markdown" affordance and the sitewide `rel="alternate"` link at a URL that 404s for this page. Add a `markdown_url: /docs.md` front-matter override to `docs/README.md`. It is inert for rendering and for `/docs.md` itself (the body still begins at `# About`); it only populates the front matter the docs site reads to advertise the correct Markdown alternate. Reader side: coder/coder.com#1013 (DOCS-638). Neither change breaks without the other, so they can merge in either order. Refs DOCS-678 > This PR was created with AI assistance (Coder Agents). |
||
|
|
31e95f7096 | docs: fix prebuilt-workspaces example syntax and defaults (#28088) | ||
|
|
ddf2d33665 |
docs: update Tallyman Agent Time reporting (#28275)
Moves Coder Agents usage reporting into the Licensing & Usage page and updates the documentation to describe Agent Time and the `hb_agent_runtime_v1` Tallyman payload. Removes the superseded Usage Data Reporting page and its manifest entry, and removes the obsolete AI Governance link to that page. PR generated with Coder Agents |
||
|
|
97cb722fb8 |
fix: show no budget instead of unlimited for empty group AI budget (#27993)
An empty per-member AI budget on the group settings page previously displayed "unlimited budget" with a "Members in this group have no spending cap." alert. Unlimited spend only applies when the everyone group is the sole group, so this messaging was misleading everywhere else. ## Changes - Empty budget now shows "This group has **no budget** set. View docs" with no info alert. "View docs" links to [Effective group resolution](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#effective-group-resolution) using the versioned `docs()` helper. - Removed the "Members in this group have no spending cap." alert entirely. - Input placeholder changed from `unlimited` to `no budget`. - Updated the `AIBudgetUncapped` story expectations to match. ## Unchanged - Entering an explicit `$0` still shows the "A $0 limit disables AI access for this group." alert, as before. - Saving with an empty field still sends `null` to the budget API; backend semantics are untouched. Story tests pass: `pnpm test:storybook src/pages/GroupsPage/GroupSettingsPageView.stories.tsx` (7/7). --- *This PR was generated by Coder Agents on behalf of @tracyjohnsonux.* |
||
|
|
63641b98c8 |
fix: treat a missing serve endpoint as a fatal dial error (#27864)
Adds 404 as a terminal error for establishing DRPC connection. A standalone AI Gateway pointed at a coderd that does not expose `/api/v2/ai-gateway/serve` gets a 404, which the connect loop classified as transient and retried forever. Redialing cannot fix a missing endpoint. 404 now is treated as terminal handshake failure. `--url` is expected to point directly at coderd, so a 404 from an intermediary is not distinguished. Refs https://linear.app/codercom/issue/AIGOV-320/write-connection-tests --- Generated with Coder Agents. |
||
|
|
34e95c46bf |
docs: note JetBrains client attribution in AI Gateway (#28296)
Noticed while testing Junie that JetBrains AI Assistant sends `User-Agent: ktor-client`, the stock Ktor default, so AI Gateway records these sessions as `Unknown`. Admins who enable Gateway for governance won't see their JetBrains attributed in the sessions view or the `client` filter. Documents the behavior on the JetBrains client page. Nothing to fix on our side, matching `ktor-client` would misattribute any other Ktor-based application. |
||
|
|
a441b03d70 |
feat: add yaml config option to standalone AI gateway (#28258)
`coder ai-gateway start` now accepts a `--config` / `-c` flag (and `CODER_CONFIG_PATH`) to load configuration from a YAML file. |
||
|
|
ddcffd8248 | docs: remove inaccurate Agent Firewall filesystem protection claim (#28286) | ||
|
|
821d91fabd | fix: log tailnet tunnel authorization decisions (#27819) | ||
|
|
166d92ba73 |
fix: bound request body size on JSON API endpoints (#28168)
## Summary `httpapi.Read` decoded request bodies with no size limit, so a single request could allocate memory without bound. This adds a 4 MiB default ceiling, leaves the endpoints that legitimately need more explicitly exempted, and counts the rejections so a limit set too tight is visible. This is the first of three PRs split out of #28048, covering the endpoints that answer in `codersdk.Response` shape. The OAuth2 decode paths (RFC 6749, RFC 7591) and the SCIM ones (RFC 7644) answer in their own error shapes and follow in separate PRs, along with the lint rule that pins the invariant. Closes PLAT-463. Remediates SEC-416 (CWE-770, CVSS 7.5) and SEC-392. ## Problem `httpapi.Read` calls `json.NewDecoder(r.Body).Decode(value)` with no ceiling, and no middleware in the chain bounds body size. The exposure is pre-authentication: login, OTP, and first-user creation all read a body before any authorization decision is reached. The existing rate limiter bounds request *rate*, which is orthogonal to the memory a single admitted request may consume. ## Fix `Read` is split into `Read` and `ReadLimit`. `ReadLimit` wraps `r.Body` in an `http.MaxBytesReader` and keeps the existing decode and validate logic; `Read` delegates to it with a new `DefaultMaxRequestBodyBytes` of 4 MiB, which covers the 124 remaining non-test callers at a single site. `http.MaxBytesReader` composes as tightest-wins, so the handlers that pre-wrapped their own bodies pass their limit to `ReadLimit` rather than wrapping, and each keeps its previous ceiling byte for byte. That matters most for the bulk secrets import at `8 * MaxSecretsFileBytes`: an unconditional wrap inside `Read` would have silently halved it to the default. `TestImportUserSecretsBodyLargerThanDefaultLimit` is the regression guard for that specific failure, and `TestMaxBytesReaderNesting` pins the composition behavior the whole requirement rests on. Every rejection site calls `httpapi.RecordRequestBodyLimit`, which names the limit that tripped on the request's existing log line and marks the request so `coderd_api_requests_too_large_total{reason="request_body"}` counts body rejections apart from the 413s coderd answers for other causes, such as agent log storage overflow. A limit set too tight for a legitimate payload therefore surfaces without waiting for a user report. The limit is a constant rather than a deployment option: an operator raising it to unblock something would reopen the vulnerability as configuration, where a security scan will not find it. A legitimate 413 is answered with a targeted `ReadLimit` on that endpoint. ## Behavior change `POST /api/v2/files` now answers 413 rather than 400 when a request body exceeds `HTTPFileMaxBytes`. It installed that bound already but reported the rejection as a read failure, which leaked the stdlib `http: request body too large` string through `Detail` and kept the largest limit in the tree off the metric. The separate 413 for an oversized expanded archive is unchanged. The task log snapshot endpoint now answers 413 rather than 400 when its 64 KiB cap is exceeded. Routing it through `ReadLimit` also changes its decode-failure message from "Failed to decode request payload." to "Request body must be valid JSON.", which is what every other endpoint answers. Its tests are updated to match both. `coderd_api_requests_too_large_total` is new, so there is no existing query to migrate. It counts the 413s coderd answers, labeled `method`, `path`, and `reason`. `reason="request_body"` is a rejection by one of the limits above; `reason="other"` is a 413 that has nothing to do with body size, such as agent log storage overflow. ## Reading this The commits are ordered to be read in sequence. Commits 1 and 2 are the security fix; commits 3 to 5 are the observability consequences, and commit 3 is the one that touches dashboards. Commit 7 documents the limit on the REST API reference index. Commits 6 and 8 add and revert an exhaustive `@Failure 413` annotation pass, which buried the fix under its regenerated swagger, and cancel out. |
||
|
|
5f6eeda588 |
docs: add Licensing & Usage page and reorder agents manifest (#28263)
Adds a new Licensing & Usage page under `docs/ai-coder/agents/` covering the difference between Community and AI Premium licenses, how Agent Time is measured, and what happens when concurrency or usage limits are reached. Reorders the Coder Agents section in `docs/manifest.json` to the following sequence: Getting Started, Architecture, Platform Controls, Extending Agents, Models, Tools, Chat Sharing, Search Syntax, Licensing & Usage, Tasks to Chats API Migration. --- PR generated with Coder Agents --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
d3f08b1983 |
feat: audit chat system instructions changes (#27668)
Adds an audit record for administrative events on the deployment-wide
chat instruction settings (system prompt, the include-default toggle,
and the plan-mode instructions), per CODAGT-719 and operator decision
D5. Each endpoint records under a stable identity: resource type
`chat_instruction_settings`, a fixed resource ID and a human-readable
target ("System prompt", "Plan mode instructions"), so two changes to
one setting share an ID and history-by-setting works. A real change
exports a Write entry with the old-to-new text visible; a
value-identical PUT still upserts and still returns 204 but records
nothing.
Attempts are recorded, not only transitions. Identity is assigned before
the authorization check, so a denied PUT exports a 403 row with an empty
diff (no request content reaches it), a validation failure exports a 400
row, and a write failure exports a 500 row, each with an empty diff; an
operator can tell "nothing changed" from "something changed and capture
degraded" by the status code.
The write path stays authoritative. The advisory lock and, on plan-mode,
the transaction exist only to serve change-detection; if any of that
machinery fails (lock, begin, commit, rollback), the handler runs main's
idempotent write path directly and derives the response from it, so a
member-visible failure of audit-only infrastructure can never replace
main's successful response. Accepted consequence: when the lock cannot
be taken, two concurrent identical writes can produce two rows instead
of one. That is audit degradation, which is allowed; changing a member's
response is not. Write failures keep the exact response the endpoint
produced before this wiring (transaction error for the system prompt,
which was always transactional; the raw write error for plan mode, which
was not), and the full transaction error is logged so rollback failures
cannot vanish.
<details>
<summary>CODAGT-66 plan entry: S1 (verbatim)</summary>
**S1 `feat: audit chat system instructions changes`** (CODAGT-719; base:
main)
- Struct: `database.ChatSystemPromptSettings{ID uuid.UUID; SystemPrompt
string; IncludeDefaultSystemPrompt bool; PlanModeInstructions string}`
in `coderd/database/types.go` (ticket-sketched shape; one struct, both
endpoints).
- Registration: union entry (diff.go), table.go entry (`id`
ActionIgnore, other three ActionTrack), `AuditActionMap` Write-only;
four request.go cases (`ResourceTarget` "", `ResourceID` from struct,
`ResourceType` new enum value `chat_system_prompt_settings`,
`ResourceRequiresOrgID` false with the "Artificial ID / deployment
singleton" comment convention).
- Migration: `ALTER TYPE resource_type ADD VALUE IF NOT EXISTS
'chat_system_prompt_settings';` comment-only no-op down (000558 shape);
number picked at push per the numbering constraint.
- codersdk: constant + prose `FriendlyString` ("chat system prompt
settings"); `TestAuditDBEnumsCovered` forces both. `coderd/audit.go`
presentation switches: rely on safe defaults (no link, generic
description); no FE changes (filter label falls back to capitalized
value; acceptable per precedent).
- Wiring `putChatSystemPrompt` and `putChatPlanModeInstructions`:
InitRequest with Action Write; artificial `ID: uuid.New()` on `New` only
when a change is detected; no-op suppression by leaving both aReq sides
unset (nil resource IDs skip the log, request.go skip rule); the write
path itself stays byte-identical (upserts still run unconditionally).
- `putChatSystemPrompt` (writes two keys conditionally in one existing
tx): inside that tx, read the pair via `GetChatSystemPromptConfig` for
`Old`, perform the conditional writes exactly as today, then RE-READ the
pair for `New`. The re-read is load-bearing:
`include_default_system_prompt` is computed from the toggle row AND the
prompt, so a prompt-only write can flip the effective value without the
request carrying the pointer. `PlanModeInstructions` stays zero on both
sides.
- `putChatPlanModeInstructions` (no tx exists today): wrap its
read-upsert in `InTx` (behavior-preserving: same single write);
`Old`/`New` populate only `PlanModeInstructions`; the two system-prompt
fields stay zero on both sides; no cross-key reads.
- Change detection compares the populated payload fields only (never the
artificial ID).
- Tests: handler-level coderdtest with `audit.NewMock()` asserting Write
entry on change and NO entry on a value-identical PUT, for both
endpoints (this also exercises `ResourceRequiresOrgID` end to end); the
fallback-flip case (no explicit include-default row, nonempty prompt set
to empty, effective boolean flips: entry emitted with the boolean diff);
diff assertions (old->new prompt text tracked, not secret) in
`enterprise/audit/diff_internal_test.go`; `TestAuditableResources`
passes by construction.
- Bookkeeping at PR open: correct CODAGT-719's no-op premise ("matches
the existing 204-on-unchanged behavior" does not exist on main;
suppression is new, write path unchanged).
- Review focus: Old capture and the New re-read inside the tx (three of
four existing singletons never set Old; do not copy them; and the
computed include-default value makes a naive New construction wrong);
the skip-on-no-op mechanism; prompt text deliberately visible in diffs.
</details>
Note: the plan excerpt above predates operator decision D5 (2026-07-30),
which this PR implements: the resource type is
`chat_instruction_settings` (not `chat_system_prompt_settings`), each
setting carries a stable ID and a display-name target (not a per-write
artificial ID and an empty target), no-op suppression runs through
`InitRequestWithCancel` (not the nil-ID skip), and attempts (denied,
failed, capture-degraded) record rows with real statuses and empty
diffs. Ticket bookkeeping for CODAGT-719 was corrected on Linear at
kickoff: the ticket's "matches the existing 204-on-unchanged behavior"
premise does not exist on main; suppression is new, and the write path
is unchanged.
> 🤖 This PR was created with the help of Coder Agents, and _will be_
reviewed by a human. 🏂🏻
---------
Co-authored-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com>
|
||
|
|
affeeaf9c8 |
feat: extend agent chat MCP tools for remote UAT evidence loops (#28233)
Extends the Agent-chat MCP tools so an unattended UAT evidence loop can
fetch artifacts, monitor long runs, and find prior runs without burning
model context.
## Backend
- New `chat_files_token` crypto key feature (migration 000571) with
rotator support and a dedicated signing keycache on coderd.
- `POST /api/experimental/chats/files/{file}/download-url`
(authenticated) mints a short-lived (5 min) signed URL and returns it
with `sha256`, `size_bytes`, `name`, `mime_type`, and `expires_at`.
- `GET /api/experimental/chats/files/{file}/download?token=` (no session
token) redeems the signed URL: verifies the JWS, requires the token's
`file_id` to match the path, and re-checks the minting user's RBAC
access live at redemption. Clients can `curl -o` artifacts with zero
credentials in the URL consumer.
- `ChatFileMetadata` gains `size_bytes` (via `octet_length`, no bytes
fetched).
## MCP tools (`codersdk/toolsdk`)
- `coder_download_chat_file`: by `file_id` or `chat_id`+`file_name`;
returns the signed URL plus checksum and size instead of base64.
- `coder_await_chat`: blocks (bounded `wait_secs`, 1-120) until a chat
leaves `running`/`interrupting`, using the existing watch stream with
subscribe-before-read.
- `coder_list_chats`: label, query, and limit filtering; chat
projections now include labels.
- `coder_get_chat_messages`: `after_id` forward cursor with
`next_after_id` (exact incremental reads), plus per-message `files`
metadata so artifact-bearing messages are identifiable.
- `coder_get_chat`: file listings now include `size_bytes` and
`created_at`.
- `coder_list_templates`: exposes `agents_allowed` for pre-flight
checks.
## Testing
- coderd: mint/redeem happy path with an unauthenticated client,
expired/tampered/file-mismatched tokens, auth still required on the
plain file endpoint, non-owner mint rejection.
- toolsdk: harness + integration coverage for all new/changed tools,
including signed-URL redemption with checksum verification,
forward-cursor exactness, await transition/timeout paths, and label
filtering.
- Remote dogfood UAT (dev.coder.com Coder Agent) passed all six
acceptance scenarios end to end over both MCP transports.
Note: `go test ./codersdk/toolsdk/` has a pre-existing goleak flake on
main (leaked `agentssh` non-PTY session goroutines from SSH exec tests;
reproduced 3/3 on clean `b4971bc49f1`). It is unrelated to this diff.
> Mux acted on Mike's behalf to create this PR.
<!-- mux-attribution: model=claude-sonnet-4-6 thinking=high -->
|
||
|
|
7724ee281a |
feat: defer MCP tool schemas behind a find_tools search (#28225)
## Summary When the `mcp-tool-search` experiment is enabled, chatd stops inlining connected MCP tool schemas into every generation. It instead exposes a built-in `find_tools` tool whose description carries a compact catalog of the deferred tools, and only ships full JSON schemas for tools the model has activated by searching or by calling them directly. Closes [CODAGT-760](https://linear.app/coder/issue/CODAGT-760). ## Problem Tool-heavy agent configurations (GitHub, Linear, Notion, and dev-tooling MCP servers) inline over 100k tokens of tool schema definitions into every generation. Initial uncached requests reached ~216k tokens with time-to-first-token close to nine minutes, while the model typically invokes only a handful of tools per turn. ## How it works - `decideMCPToolSearch` defers external and workspace `.mcp.json` MCP tools whenever the experiment is enabled. Native, dynamic, provider, skill, and transport tools are never deferred. - `find_tools` embeds a server-grouped catalog in its tool description (degrading to names-only, then counts-only, then a constant-size summary past a context-scaled size cap) and scores keyword matches across tool names, descriptions, parameter schemas, and server metadata. Queries can scope to one server with a `server:` prefix, and exact `names` arguments always activate. - Activation state is ephemeral: it is re-derived each generation from surviving chat history (`find_tools` results and direct calls to deferred tools), so activations naturally lapse when compaction summarizes them away. Aggregate activated schema weight is capped at 10% of the context window, shedding the least recently activated schemas first; `find_tools` shares that budget across parallel calls in one step. No new persistence. - Deferred tools stay registered for execution, so the model can call a cataloged tool directly without searching first; the schema is activated for subsequent steps. - Fail-open: the experiment being disabled, an empty candidate set, or an MCP tool named `find_tools` all disable deferral, leaving today's behavior byte-identical on the wire. - Prometheus counters/histograms track `find_tools` calls, matches, activations, and deferred token weight. - The conversation timeline renders `find_tools` calls with a collapsed search summary and expandable match list, falling back to the generic renderer on malformed payloads. ## Validation - Unit tests for the catalog, matcher, experiment-gated decision, and activation derivation; end-to-end chatd generation tests covering search-then-call, direct-call activation, experiment-off wire parity, compaction lapse, and subagent tool gating. - Storybook interaction tests for the timeline rendering and malformed-payload fallback. - Remote dogfood UAT on dev.coder.com passed: deferral with a real MCP server and Anthropic model, direct calls without prior search, activation persistence across turns, experiment-off parity, and clean UI/console. > Disclosure: Mux (AI agent) authored this PR on Mike's behalf. |
||
|
|
27e3d0fb00 |
Change all windsurf.com links to devin.ai links (#28270)
This avoids the redirects from our docs to Devin's docs. <!-- 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. --> |
||
|
|
0db25caad6 |
docs(docs/.style/style-guide): fix self-violations found by audit (#27855)
Builds on #27849 and #27852 (both merged). Runs every style guide page through the guide's own rules, including the STE-derived rules from #27852, and fixes the violations in the guide's prose and **Do** examples. **Don't** examples keep their intentional violations. Three parallel audit passes produced roughly 120 findings; this PR applies the accepted ones. Objective defects fixed: an unbalanced quotation mark on the audience page, a stale "in this PR" reference in the README, a `console` **Do** example whose command and output had been collapsed onto one line, inline `> [!NOTE]` markers that GitHub renders as literal text instead of callouts, "a `onClick`", and two stale Vale rule references. Two **Do** examples modeled banned or wrong prose: the audience page's example contained the exact `*Audience: ...*` metadata line the same page bans, and a word-choice example had Coder running its own login command. Rule-adherence fixes: US-quotation comma/period placement throughout, banned idioms and figurative language ("wall of commas", "silently rots", "when in doubt", "stretch goal", "bleeding-edge", the Churchill "put up with" example), simplicity words and vague qualifiers ("easy", "straightforward", "typically", "almost always", "often"), directional "above", framing paragraphs under bare headings, run-in bold leads split to one sentence per source line, prose semicolons split into sentences, 6-item prose enumerations reduced, and end-of-page "Related" sections renamed to **Learn more** per the guide's own heading rule. **One policy call for docs-team review**: the digits-everywhere rule now scopes out numbers that describe language itself ("a contraction joins exactly two words") and `one` as a determiner or pronoun. The alternative was rewriting every determiner as a digit ("give each paragraph 1 topic"), which makes the prose worse. With the scoped rule, the remaining real counts were converted to digits. Deliberately not changed: "lands"/"land" as release vocabulary, attributed claims inside the Latin-abbreviations `` block, persona-sketch color on the audience page (writer-facing planning vocabulary), and the "What's a workspace" heading example. Linear: DOCS-650 --- > This PR was created with AI assistance (Coder Agents). |
||
|
|
119f2b1dd9 |
feat: limit concurrent chat agents with pooled admission (#27902)
Limits concurrent chat generation on capped deployments to 5 root chats and 10 delegated subagent chats. The pools are deployment-wide and independent, so delegated work can continue while root capacity is full. The default caps live in AGPL code. Enterprise contributes only a licensing unlock, so unlicensed deployments stay capped and cannot fail open. Licensed deployments are uncapped while Agent Hours usage stays below an explicit hard limit. Deployments without a hard limit remain uncapped, and reaching the Agent Hours allocation only triggers warnings. Admission happens before a worker takes chat ownership. Capped deployments serialize admission across replicas with a transaction-scoped advisory lock and derive active and queued state from current ownership plus fresh runner heartbeats, rather than persisted queue markers or per-replica state. The acquisition query returns a bounded, pool-interleaved candidate set instead of ranking the whole backlog; a migration replaces the acquisition index with a pool-aware one. Refused chats stay running but unowned, and interrupt requests bypass admission so users can stop queued or over-cap chats. The single-chat API derives `queued_for_capacity` from live pool state; list endpoints do not report it. The UI polls that value every 5 seconds while a chat is running and shows a callout when the chat is waiting for capacity. Updates the administrator documentation and deployment-wide Prometheus gauges for active and queued agents. Replica-level values must be aggregated with `max`, not `sum`. > Mux updated this PR on Mike's behalf. |
||
|
|
062c0fdd3b |
docs: rebrand Windsurf doc page to Devin Desktop (#28205)
## Summary Cognition (maker of Devin) rebranded the Windsurf Editor as Devin Desktop on June 2, 2026, after acquiring it from Codeium in July 2025. Our docs still referred to the editor as Windsurf and linked to a dead `codeium.com` domain. ## Changes - Renamed `docs/user-guides/workspace-access/windsurf.md` to `devin-desktop.md`, rewritten to lead with Devin Desktop branding, note the Codeium -> Windsurf -> Devin Desktop history, and use current links (`windsurf.com`, `docs.windsurf.com`) instead of dead `codeium.com` ones. - Updated `docs/manifest.json` and `docs/user-guides/workspace-access/index.md` to reference the new page. - Updated remaining Windsurf mentions to Devin Desktop in `docs/ai-coder/ide-agents.md`, `docs/ai-coder/index.md`, `docs/reference/glossary.md`, and `docs/ai-coder/ai-gateway/clients/index.md`. - Added `windsurf.com`/`devin.ai` to `.github/.linkspector.yml` ignore patterns; both rate-limit repeated automated requests with 429s (same class of issue as the `codeium.com`/`marketplace.visualstudio.com` fix in #28203). - Switched every module reference from `windsurf` to the new `devin-desktop` registry module (`docs/about/contributing/modules.md`, the three `get-started/customize-your-template/*.md` Terraform tutorials, and the main doc page's module link), since the new module actually renders `display_name = "Devin Desktop"` / `slug = "devin-desktop"` in the UI (the old `windsurf` module hardcodes "Windsurf Editor"). <details> <summary>Scope notes / sequencing</summary> The `devin-desktop` module referenced here is being added in [coder/registry#1050](https://github.com/coder/registry/pull/1050) (not yet merged/released). That PR is itself gated on [coder/coder#28214](https://github.com/coder/coder/pull/28214) (whitelisting the `devin:` URI scheme) shipping in a released Coder version first. This docs PR can merge independently, the module link will 404 until #1050 is released, same as any docs-ahead-of-registry-release sequencing. The Terraform code samples now show `module "devin-desktop"` because that module's `display_name`/`slug` are properly parameterized (unlike `windsurf`, which hardcodes "Windsurf Editor"/`windsurf` regardless of what's passed in), so the docs stay accurate to the rendered UI. </details> ## Validation - `make lint` (docs lint, markdownlint, repo checks) passes. - Manually verified the new outbound links (`docs.windsurf.com`) return 200; `windsurf.com`/`devin.ai` are rate-limited (429) from this environment too, hence the added ignore patterns. Stacked on #28203 (targets that branch so the diff here stays scoped to the rebrand; will retarget to `main` once #28203 merges). > 🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑💻 |
||
|
|
db3566c1a3 |
chore: correct AI Gateway metric provider label and cardinality notes (#28220)
The cardinality notes in `aibridge/metrics/metrics.go` assume the `provider` label takes one of three values, and two for the key pool metrics. That was accurate when the notes were written: `provider` is the provider instance name, and the name defaulted to one of the three provider types aibridge supports. Instances can now be given their own names, so the label takes any configured name and the series counts scale with the number of configured providers rather than being capped at a fixed number. The monitoring docs are also updated to make clear that `provider` is the provider instance name. Comments and documentation only, no behaviour change. Follow-up to #28210. |
||
|
|
fa8ffe4eda |
feat: report agent runtime hours usage in entitlements (#27985)
Populate `FeatureAgentRuntimeHours.Actual` on every entitlements refresh for licenses that grant the feature. A new `GetTotalUsageHBAgentRuntimeV1` query sums `runtime_ms` over the license's usage period, reading `usage_events` directly: `hb_agent_runtime_v1` is exactly one row per hourly bucket deployment-wide with `created_at` at the bucket start, enforced by the unique partial index introduced in #27983. The measurement reuses the shared `measureUsage` policy from #27984 through a new `AgentRuntimeMsFn` closure (usage publisher subject): failures publish the stable `LicenseAgentRuntimeUsageUnavailableErrorText` and log the cause. Usage is floored to whole hours, matching the unit of the `agent_runtime_hours_*` claims, and at most one warning is emitted per refresh: reaching the allocation supersedes the advisory soft limit. The dashboard renders the soft-limit advisory muted without a sales link and treats the runtime usage-unavailable text as a diagnostic. **Precise usage.** `Feature.ActualMs` (JSON `actual_ms`), set only for `agent_runtime_hours`, carries the exact stored milliseconds backing the floored `Actual` so clients can render fractional hours (e.g. `10.3`). It has the same freshness as `Actual`; the whole-hour warning thresholds are unchanged. **Unlimited licenses.** A license minted with the unlimited (`-1`) allocation decodes to an enabled feature with a nil `Limit` (#27984), so the warning write-back now guards the allocation dereference: no thresholds can exist for an unlimited license, so no runtime hours warning is ever emitted, while `Actual` is still measured and published. `Feature.Compare` is unchanged; for usage-period features the issued-at/end dates decide first, so a metered feature outranks an unlimited one only on an exact timestamp tie, an edge pinned by a `TestFeatureComparison` case and documented on `decodeAgentRuntimeHours`. **Grandfathered premium licenses.** Premium licenses without `agent_runtime_hours_*` claims are now granted the feature disabled with a zero limit over the license term, identical to an explicit `allocation: 0`: usage is measured and published for every Premium deployment, and chatd's pooled admission (#27902) caps concurrent agentic chats until a license with a positive allocation is added. The default carries a fixed early `UsagePeriod.IssuedAt` (2026-08-01, the same mechanism as the managed-agents default) so any license actually carrying the claims outranks it in the `AddFeature` merge regardless of the licenses' relative issue dates; the constant must stay earlier than the earliest legitimately issued claim-bearing license. Zero allocations (explicit or grandfathered) emit no deployment-wide warning banner: those deployments are steered by the in-page upgrade CTA and the concurrency cap. Enterprise licenses are unchanged. Part 3 of a 3-PR stack splitting up #27796 (see there for review history). Stack: #27983 → #27984 → this PR. Closes CODAGT-852. |
||
|
|
b5d18bb9c9 | feat: add redirect URL override for external auth (#28082) | ||
|
|
95328f1ead |
fix: label unpriced token usage metric by provider name and type (#28210)
## Problem The `provider` label was inconsistent between AI Gateway metrics. Every metric emitted by the gateway labels `provider` with the provider instance name, for example `anthropic-eu`, while `coder_ai_gateway_cost_control_unpriced_token_usage_records_total` used the provider type, for example `anthropic`. The two could not be correlated on `provider`. The metric was also inconsistent with itself: the path where a provider fails to resolve labelled by instance name, and the path where a model has no price labelled by type. The type is still worth exposing, since prices are keyed on `(provider_type, model)` and that is what an operator needs to add a price. ## Changes - Label the metric with `provider` (the instance name, consistent with the other gateway metrics) and add `provider_type` (the configured type the price is keyed on). - Use `unknown` for `provider_type` when the provider does not resolve to a configured type. - Log the unresolved-provider case at `warn` instead of `info`. A missing price is an expected steady state, but a provider that cannot be resolved is not. - Update the metrics docs and the `metricsdocgen` fixture. Closes [AIGOV-574](https://linear.app/codercom/issue/AIGOV-574) > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
ea8ba0c678 |
refactor: remove MUI and Emotion (#27821)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell. Until we meet again. ## Stack - #27636 - #27718 - #27719 - #27722 - #27723 - #27724 - #27728 - #27730 - #27732 - #27762 - #27763 - #27786 - #27787 - #27788 - #27789 - #27790 - #27791 - #27817 - #27820 - #28009 ## Final removal (`c39b664`) Removes the last of MUI and Emotion now that every surface has been migrated: - **Dependencies**: drops `@mui/material` and `@emotion/{cache,css,react,styled}` from `package.json` / `pnpm-lock.yaml`, and deletes the `@types/emotion.d.ts` and `@types/mui.d.ts` module augmentations. - **Theming**: replaces the Emotion `CacheProvider`, MUI `ThemeProvider` / `StyledEngineProvider`, and `CssBaseline` in `ThemeProvider` with a lightweight `theme/context.tsx` that exposes `ThemeContextProvider` and a `useTheme` hook. - **Global styles**: moves the base `body` styles (background, text color, font, antialiasing) that `CssBaseline` previously provided into `index.css`, and drops the temporary MUI modal/popover scrollbar-gutter workaround. - **Cleanup**: removes the MUI → shadcn / Emotion → Tailwind migration guidance from `site/AGENTS.md`, updates the Storybook `preview.tsx`, and adjusts assorted components (`Command`, `Slider`, `Switch`, `Tabs`, `SyntaxHighlighter`, timing charts) and theme files to consume the new context instead of MUI/Emotion. |
||
|
|
a005e5cd22 |
feat: add username and email user search filters (#27922)
## Summary User search can now resolve exact `email:` and `username:` terms through `GET /api/v2/users` instead of only supporting fuzzy free-text matches. The database query already had exact email and username filters; this wires the public search parser and API handler to those filters so clients can ask for a single user by email without fetching every user or depending on substring matching. This is the API half of coder/terraform-provider-coderd#403: that provider PR adds `data.coderd_user.email`, and this PR gives it an efficient exact lookup path. ## Testing - `go test ./coderd/searchquery -run '^TestSearchUsers$' -count=1` - `go test ./coderd -run '^TestGetUsersFilter$' -count=1` - Live API test: - Built local enterprise Coder from this branch. - Started Coder on `http://127.0.0.1:39991` against a clean Postgres database. - Created `lookup-target@example.com`. - Verified `GET /api/v2/users?q=email:LOOKUP-TARGET@EXAMPLE.COM&limit=2` returned exactly one user: ```json { "count": 1, "users": [ { "id": "efc6f909-ce0a-4731-bd2f-6e4df417aaa7", "username": "lookup-target", "email": "lookup-target@example.com" } ] } ``` ---   --------- Co-authored-by: Ethan Dickson <ethanndickson@gmail.com> |
||
|
|
58de9ab8f8 |
docs: correct broken CLI commands and flags from drift sweep (#28098)
## Summary Corrects broken CLI commands and flags surfaced by the DOCS-637 full-corpus runtime drift sweep. Each fix was verified against the generated CLI reference (`docs/reference/cli/*`) and, where relevant, `codersdk` source. ## Changes | Page | Fix | |------|-----| | `docs/user-guides/workspace-access/index.md` | `coder port forward` → `coder port-forward` (the space form is unrecognized; the command is hyphenated). | | `docs/ai-coder/github-to-tasks.md` | Remove `coder templates list --org your-org-name` in two spots — `templates list` has no `--org` flag (`unknown flag: --org`). | | `docs/admin/infrastructure/scale-utility.md` | `--cleanup-timeout 15min` → `15m` — Go durations reject the `min` unit (`invalid duration: unknown unit "min"`). | | `docs/admin/integrations/dx-data-cloud.md` | `coder users list > users.csv` emitted a whitespace table, not CSV. Emit JSON and convert to real CSV with `jq`, mirroring the API tab on the same page and using the same columns as the default table view (`username,email,created_at,status`). | ## Notes / judgment calls - **dx-data-cloud (CSV):** the page genuinely needs CSV (the DX CSM imports a CSV, and the API tab already produces one via `jq ... @csv`). `coder users list` only supports `--output table|json`, so the CLI tab now produces real CSV via `jq` rather than switching the page to JSON. - **scale-utility `:109` left as-is:** `--target-users 0:100` is prefixed with "For dashboard traffic:", which correctly scopes it to the `scaletest dashboard` subcommand, so it is not drift. - **Excluded — sessions-tokens `--lifetime=720h`:** the sweep flagged this because the throwaway SUT capped token lifetime at 168h, but `--max-token-lifetime` defaults to `876600h` (~100 years), so the example is valid on a default deployment. The `CODER_MAX_TOKEN_LIFETIME` dependency is also already documented in the page's "Set max token length" section. No change needed. Linear: https://linear.app/codercom/issue/DOCS-641 > This PR was created with AI assistance (Coder Agents). |
||
|
|
b0e93b6e3b |
docs: correct nginx X-Forwarded-Proto and certbot instructions flavor (#28086)
## What Two fixes to the nginx reverse-proxy tutorial. ### `X-Forwarded-Proto` (line 137) The config set: ```nginx proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; ``` `$http_x_forwarded_proto` is the value of a client-supplied request header, which a client can spoof and which is usually empty for a direct request. In an nginx TLS-terminating reverse proxy this should be `$scheme`, which nginx sets from the actual connection (`https`). Using the raw client header can break Coder's scheme detection and secure-cookie handling. ### Certbot link flavor (line 57) The Certbot instructions link used `?ws=apache` in an nginx guide; changed to `?ws=nginx` so readers get nginx instructions. Surfaced by the runtime drift sweep; verified against `main`. Linear: [DOCS-642](https://linear.app/codercom/issue/DOCS-642/docs-fix-reverse-proxy-nginx-x-forwarded-proto-dollarscheme-certbot) > This PR was created with AI assistance (Coder Agents). |
||
|
|
1d189cc204 |
docs: fix P2/P3 typos and syntax errors from drift sweep (#28101)
## Summary High-confidence textual subset of the DOCS-637 **P2/P3** drift batch (31 findings total). These 8 fixes are pure typo / grammar / syntax corrections verified directly against the doc source, so they carry no risk of misreconstructed command output. ## Changes (6 files) | Page | Fix | |------|-----| | `docs/admin/templates/extending-templates/variables.md` | Remove doubled word: "file in in the template directory" → "file in the template directory". | | `docs/admin/networking/port-forwarding.md` | Grammar: heading "From an coder_app resource" → "From a coder_app resource". | | `docs/user-guides/workspace-access/index.md` | Malformed heading "Through with the CLI" → "Through the CLI". | | `docs/about/contributing/modules.md` | Conventional-commit example missing the required space: `feat(git-clone):add` → `feat(git-clone): add`. | | `docs/ai-coder/tasks-migration.md` | Add missing closing double-quotes on Terraform `source`/`version` in two snippets that would fail `terraform` parsing. | | `docs/admin/users/idp-sync.md` | Role Sync section said "group sync settings" (copy-paste from the Group Sync section); remove an invalid trailing comma from a JSON output example. | ## Deferred (remaining ~23 P2/P3 items, not in this PR) The rest of the batch is stale **command-output** samples (column/schema changes, sample values) and items that need a content decision (e.g. `--psk` now deprecated in favor of `--key`; `--address` deprecated; an undocumented retention flag). Those need live-output reconstruction or a call on direction, so they're left for follow-up work, consistent with the issue's "handle after the P0/P1 fixes land" guidance. One catalog row (`reverse-proxy-nginx.md:57`, certbot `ws=apache`) is already handled by #28086 and is excluded here. Linear: https://linear.app/codercom/issue/DOCS-646 > This PR was created with AI assistance (Coder Agents). |
||
|
|
5b97d99a48 |
docs: fix Helm TLS/ingress value keys in admin/setup (#28087)
## What
Fix the Helm values in the TLS setup step of
`docs/admin/setup/index.md`. The documented keys are silently ignored by
the chart, so TLS appears configured but isn't.
## Changes
- `coder.tls.secretName` (singular) → `coder.tls.secretNames` (a list).
The chart key is `secretNames`.
- `coder.ingress.secretName` / `coder.ingress.wildcardSecretName` →
nested under `coder.ingress.tls.secretName` /
`coder.ingress.tls.wildcardSecretName`, where the chart actually reads
them.
- Added `coder.ingress.tls.enable: true` so the ingress-termination
example actually enables TLS.
All keys verified against `helm/coder/values.yaml` on `main`
(`coder.tls.secretNames`,
`coder.ingress.tls.{enable,secretName,wildcardSecretName}`). Surfaced by
the runtime drift sweep. The example now parses to the correct chart
structure.
Linear:
[DOCS-643](https://linear.app/codercom/issue/DOCS-643/docs-fix-helm-tlsingress-value-keys-in-adminsetup-secretnames)
> This PR was created with AI assistance (Coder Agents).
|
||
|
|
3145cc8386 |
docs: fix prometheus metric name and slack webhook backtick (#28085)
## What Two small monitoring-doc fixes surfaced by the runtime drift sweep. ### `docs/admin/integrations/prometheus.md` The native-histograms list showed `coderd_template_coderd_template_workspace_build_duration_seconds` (doubled `coderd_template_` prefix). The correct metric name, per the metrics table earlier on the same page and the generated metrics, is `coderd_template_workspace_build_duration_seconds`. ### `docs/admin/monitoring/notifications/slack.md` The `CODER_NOTIFICATIONS_WEBHOOK_ENDPOINT` export ended with a stray backtick: ``` export CODER_NOTIFICATIONS_WEBHOOK_ENDPOINT=http://localhost:6000/v1/webhook` ``` On paste, bash treats the trailing backtick as an unterminated command substitution and errors. Removed it. Both reproduced during the runtime drift sweep and verified against `main`. Linear: [DOCS-647](https://linear.app/codercom/issue/DOCS-647/docs-fix-monitoring-examples-prometheus-metric-name-slack-webhook) > This PR was created with AI assistance (Coder Agents). |
||
|
|
f3fd4c4a77 |
docs: remove invalid --yes flag from coder template version promote (#28084)
## What Remove the invalid `--yes` flag from the `coder template version promote` command in the CI/CD publishing example. ## Why `docs/tutorials/testing-templates.md` documents, in the GitHub Actions "Promote template version" step: ``` coder template version promote --template=$TEMPLATE_NAME --template-version=... --yes ``` The `promote` subcommand has no `--yes`/confirmation flag, so the command exits with `unknown flag: --yes` and breaks the documented CI workflow. This is a golden-path (automation) breaker. Verified against the generated reference `docs/reference/cli/templates_versions_promote.md` (flags are only `--template`, `--template-version`, `-O/--org`), and reproduced against a live deployment during the runtime drift sweep. The command is non-interactive, so no confirmation flag is needed. ## Change Single line: drop ` --yes`. Linear: [DOCS-640](https://linear.app/codercom/issue/DOCS-640/docs-remove-invalid-yes-flag-from-coder-template-version-promote) > This PR was created with AI assistance (Coder Agents). |
||
|
|
043bebb7bc |
docs: add Coder Desktop stale-tunnel recovery and improve macOS log capture (#26735)
## What Adds a **Recovering from a stale tunnel** section to the Coder Desktop user guide, with separate macOS and Windows procedures, and tightens the existing macOS log-collection instructions. ## Why Users in the field have hit a state where Coder Desktop's menu bar / tray shows **Coder Connect** as enabled but the embedded tunnel is no longer working: * `workspace.coder` fails to resolve (`No such host`), or * DNS returns stale `fd60:627a:a42b::/48` addresses that no longer route, causing `coder ssh`, file sync, and the directory picker to hang. Related issues: * coder/coder#26669 — `ExistsViaCoderConnect` false positives when Coder Desktop has stale DNS * coder/coder-desktop-windows#171 — Tray reports Coder Connect as healthy while tunnel/DNS is broken Until the underlying state-management gap is fixed in the apps, the docs should give users (and support) a safe, repeatable way to recover without rebooting. ## Changes `docs/user-guides/desktop/index.md`: 1. **New "Recovering from a stale tunnel" section** under Troubleshooting: * **macOS:** stop the VPN configuration with `scutil --nc stop`, quit the app via `osascript`, restart the helper daemon in place with `launchctl kickstart -k system/com.coder.Coder-Desktop.Helper`, flush DNS caches, then relaunch. * Includes a warning to **not** use `launchctl bootout`, which removes the daemon from launchd's system domain entirely and is not re-bootstrapped on app relaunch. * Includes a verification step using the built-in sentinel hostname `is.coder--connect--enabled--right--now.coder` (defined in `tailnet/conn.go` as `IsCoderConnectEnabledFmtString`) so users don't need a workspace name to confirm the tunnel is healthy. * Uses `dig @fd60:627a:a42b::53` (explicit server) and `dscacheutil -q host -a name` because plain `dig` does not respect the macOS system resolver. * **Windows:** stop the app and `Coder Desktop` service, flush DNS, restart, then verify the NRPT rule and Wintun adapter. Notes that `ipconfig /flushdns` does not reset the embedded resolver and that filtering agents (e.g., Zscaler) may still shadow `.coder` lookups. 2. **macOS log-collection improvements:** * Switch the predicate from `subsystem == "com.coder.Coder-Desktop"` to `subsystem BEGINSWITH "com.coder.Coder-Desktop"` so the export captures the app, helper daemon, and network extension (which all log under prefixed subsystems). * Add a `log stream` example for live tailing while reproducing an issue. ## Verification * `npx markdownlint-cli2 docs/user-guides/desktop/index.md` — 0 errors. * macOS recovery steps were validated end-to-end on a real install (the `kickstart -k` form, in particular, was confirmed to restart the helper without breaking the install, unlike `bootout`). --- Created on behalf of @mdanter --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: Atif Ali <atif@coder.com> Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> Co-authored-by: Matyas Danter <mdanter@gmail.com> |
||
|
|
e1fa247e59 | feat: redirect to the template builder after first time setup (#27670) | ||
|
|
8d4d0b35dd |
feat: add Coder Agents chat tools to the MCP toolsdk (#28025)
Exposes the experimental Coder Agents chats API through the MCP tool registry, so MCP clients (the hosted `/api/experimental/mcp/http` server and `coder exp mcp server`) can start and drive server-side coding agents. New tools in `codersdk/toolsdk`, all thin wrappers over existing `codersdk.ExperimentalClient` methods: | Tool | Wraps | |---|---| | `coder_create_chat` | `CreateChat` (prompt, optional org, model config, labels) | | `coder_get_chat` | `GetChat` (status, last error, last turn summary, workspace, files) | | `coder_get_chat_messages` | `GetChatMessages` (user-facing parts, chronological, cursor pagination, queued prompts) | | `coder_send_chat_message` | `CreateChatMessage` (queue or interrupt busy behavior) | | `coder_interrupt_chat` | `InterruptChat` | | `coder_archive_chat` | `UpdateChat` with `archived: true` | | `coder_list_chat_model_configs` | `ListChatModelConfigs` (enabled configs with default flag) | Both MCP servers register tools from `toolsdk.All`, so no additional wiring is needed. Responses are trimmed to what an MCP caller needs (IDs as strings, user-facing transcripts) rather than full SDK payloads. No new endpoints and no database changes. Also adds MCP [prompts](https://modelcontextprotocol.io/specification/2026-07-28/server/prompts) for the chat workflows, defined once in `codersdk/toolsdk` and registered by both servers: | Prompt | Purpose | |---|---| | `coder_agents_delegate` | delegate a task to a Coder Agents chat and monitor it to completion | | `coder_agents_check` | check the status and recent activity of an existing chat | Each prompt declares the tools its workflow needs; the stdio server skips prompts whose tools are excluded by `--allowed-tools`. Tests run the tools against a chat-enabled coderdtest instance (fake OpenAI-compatible provider plus in-process AI bridge), covering the full lifecycle, an interrupt against a blocked turn, pagination cursors, permission-dependent model config filtering, and argument validation. Prompt coverage spans SDK rendering, the hosted `prompts/list`/`prompts/get` round trip, and the stdio server including allowlist gating. > Mux created this PR on Mike's behalf. |
||
|
|
3426f83a27 |
docs: use approximate spend and add Everyone group tip for AI Gateway cost controls (#28012)
## Summary Updates the AI Governance Cost Control docs in two ways: - **Terminology:** aligns the docs with the UI, which now labels spend as **approximate** rather than **estimated**. Renames the `Estimated spend` term, the "How spend is calculated" section (and its anchor and references), and updates the surrounding prose. - **Everyone group tip:** adds a note that, because the organization's `Everyone` group includes every member, its **Members** tab is a quick way for an admin to look up any user's effective group. This complements the existing Get user AI spend API endpoint, since there is no dedicated cost control page today. Related: https://github.com/coder/coder/pull/27977 --- _This PR was created by Coder Agents on behalf of @ssncferreira._ --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
2d9b6eda8f |
feat: add experimental CLI to price unpriced AI models (#27926)
## Description AI Gateway computes the cost of an interception from `ai_model_prices`, which is seeded on every server start from a price book embedded in the binary. A model the price book does not cover records a NULL cost, so its spend is invisible to cost reporting and is not enforced against budgets. The only fix was to wait for a Coder release that added the model. This adds an experimental CLI, backed by an experimental HTTP endpoint, for pricing those models. Models the price book already covers are rejected, because the seeder re-applies the book on every start and would overwrite an operator price. Support for custom pricing will be handled in https://linear.app/codercom/issue/AIGOV-589/extend-experimental-cli-command-to-set-custom-ai-model-prices. ## Commands ``` coder exp ai-model-prices list [--provider] [--model] coder exp ai-model-prices update [file|-] [--provider] [--model] [--input-price] [--output-price] [--cache-read-price] [--cache-write-price] [--yes] ``` ## Changes - Add `GET` and `POST /api/experimental/ai/model-prices`, gated behind the AI Bridge entitlement and the existing `ai_model_price` RBAC resource. - Add a `GetAIModelPrices` query with optional `provider` and `model` filters applied in SQL. - Validate the whole request before writing anything, so one bad entry cannot leave the table half updated, and report every problem at once. - Reject prices for models the embedded price book already covers, through a new `prices.IsDefaultPriced`. - Add the `coder exp ai-model-prices` command with `list` and `update`. `update` accepts a JSON document or the single-model flags and prints a plan, asking to confirm unless the document is piped in or `--yes` is passed. - Consolidate the supported provider list into `coderd/aibridge/prices/providers` so the price generator and the server share one definition. - Add `codersdk` types and client methods for both endpoints, and bound the request body at 1 MiB. - Document the command in the AI Gateway cost controls page. Closes https://linear.app/codercom/issue/AIGOV-567/experimental-cli-command-to-set-prices-for-unpriced-ai-models > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
e92fd8e96f |
chore: retire mark3labs/mcp-go dependency (#28061)
## Stack Context PR 6 of 6 in a stack that migrates every Coder MCP surface from the archived `github.com/mark3labs/mcp-go` library to the official `github.com/modelcontextprotocol/go-sdk` v1.7.0. Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061 ## Why With every production surface migrated, this PR removes the mark3labs dependency entirely and converts the remaining test fixtures. - Migrates the remaining mark3labs test fixtures (coderd MCP e2e tests, chatd fixtures, mcpclient fixtures, and the Force On MCP policy tests) to official stateless SDK servers. - Removes `github.com/mark3labs/mcp-go` from `go.mod` and drops the corresponding dependabot ignore entry. Zero references remain repo-wide. - Updates the MCP docs for the 2026-07-28 protocol: stateless Streamable HTTP behavior, the supported 2024-11-05 through 2026-07-28 protocol range, and explicit non-features (resources, prompts, structured output, elicitation, MCP Tasks). - The e2e ping assertion is removed because MCP 2026-07-28 removed the ping method. > Mux created this PR on Mike's behalf. |
||
|
|
f0c17291b3 |
feat: unhide --oidc-redirect-url server option (#28072)
Unhides the `--oidc-redirect-url` / `CODER_OIDC_REDIRECT_URL` server option so it appears in `coder server --help` and the deployment configuration docs. - Removed `Hidden: true` from the option in `codersdk/deployment.go` - Regenerated CLI golden files and docs via `make gen` --- > Generated with Coder Agents on behalf of @Emyrk |