Commit Graph

16086 Commits

Author SHA1 Message Date
Jake Howell 20ca63d1bc test(site/src/api): expect v2 MCP ACL path (#28657)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

The MCP server ACL client uses the promoted `/api/v2` route, but its
path assertion still expected the experimental compatibility route.

Update the assertion to match the v2 endpoint and restore the JavaScript
test suite.
2026-08-26 18:51:18 +00:00
McKayla はな 2a8e390f0c refactor: replace most usage of ptr.Ref with new (#28282) 2026-08-26 11:57:56 -06:00
TJ 2eee703ec4 feat(site/src/pages/AgentsPage): add organization filter to compaction settings (#28559)
Adds an organization picker above the compaction thresholds table on
`/agents/settings/compaction`, matching the organization dropdowns in
the agents admin area (`OrganizationAutocomplete`, as used by the Models
and MCP Servers pages). The picker only appears when enabled models span
more than one organization, defaults to the default organization, and
the table shows the selected organization's models. Save tracking still
covers all models so an edited row hidden by the picker is not dropped.

Also removes the organization name text under each model badge (the
organization remains in the accessible labels to disambiguate duplicate
model names) and changes the model badge size from `sm` to `md`.

_This PR was generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-26 17:50:24 +00:00
Michael Suchacz 35cbca080e feat: allow sharing MCP servers with users and groups (#28593)
## Summary

Adds the missing user-facing half of MCP server config ACLs: admins can
now share an MCP server with individual users and groups from the UI,
backed by a new permission-safe candidate-discovery endpoint.

## Problem

The MCP server config ACL backend already existed (share RBAC action,
hydrated `GET .../acl`, sparse `PATCH .../acl`), but there was no
frontend for it. There was also no way to populate a sharing
autocomplete without the generic organization member/group APIs, which
require `organization_member:read` and `group:read` and would wrongly
couple MCP sharing to the workspace-sharing mode (the same bug fixed for
chat models in #28542).

## Changes

Backend:

- New v2-only `GET
/api/v2/organizations/{organization}/mcp-servers/{mcpserverconfig}/acl/available`
returning `codersdk.ACLAvailable`; because this API is new, it is not
mounted under the experimental compatibility prefix. Gated on
`ActionShare` for the specific server config; performs bounded
org-scoped member/group lookups via `dbauthz.AsSystemRestricted`;
excludes system users; supports `q`, `limit`, `offset`, and `after_id`
with template/chat-model autocomplete semantics.
- Tests cover authorization (404 without share), search, pagination,
system-user exclusion, org scoping, and a share-only custom role under
all three workspace-sharing modes (`none`, `service_accounts`,
`everyone`).

Frontend:

- `shareMCPServerConfig` permission plumbing; the MCP servers list,
details page, and AI settings sidebar now admit share-only users (share
does not require update; share-only users cannot edit the server form).
- Share-only access follows the established permission contract: a
bare-share role manages sharing through the list and ACL endpoints (the
detail route still requires read, update, or delete, matching the
existing enterprise permission-matrix tests), and the disabled-config
gate now also admits the share permission so a sharer with read access
can open a disabled server. Top-level navigation (Admin settings menu
and the /ai/settings index redirect) discovers organization-level MCP
sharers and surfaces permission lookup failures instead of silently
falling back.
- "Share server" action on the MCP server edit page opening a sharing
dialog: hydrated ACL grants render from the `GET .../acl` response,
adds/removals are saved as sparse `PATCH` deltas (`"read"` / `""`).
- Principal autocomplete backed exclusively by the new `/acl/available`
endpoint; the sharing flow never calls the generic org member/group APIs
(Storybook enforces this by rejecting those API spies). A discovery
error surfaces under the autocomplete without hiding existing grants.
- Storybook interaction coverage for the dialog (hydrated rendering,
add/remove, sparse deltas, error/cancel/reopen paths, autocomplete
exclusion and failure) and share-only access to the page/form.

Dogfood UAT ran remotely against a dev instance and passed, including
verifying via the network log that candidate discovery only hits
`/acl/available`. The share-only custom role UI flow could not be
exercised there (no premium license on the dev instance); it is covered
by the backend permission-matrix tests.

> 🤖 This PR was authored by Xum (an AI coding agent) acting on Mike's
behalf.
2026-08-26 19:49:43 +02:00
TJ f75854699d fix(site): keep at least 8 characters of the model name visible in the model selector (#28487)
## Problem

When the composer's button area fills up (badges, workspace pill, MCP
servers), the model selector trigger in the chat input collapses down to
just the provider icon and chevron — the model name is truncated away
entirely. The trigger `Button` had `min-w-0`, so the flex chain allowed
the `truncate` label to shrink to zero width.

## Fix

Add an explicit width floor on the trigger button:
`min-w-[calc(8ch+3.125rem)]` — 8ch of label plus the fixed chrome around
it (`px-2` padding, `size-3` provider icon, two `gap-1` gaps, `size-3.5`
chevron).

- The explicit `min-width` replaces the button's automatic minimum, so
it still shrinks and the label still truncates with an ellipsis, but
never below ~8 visible characters.
- The inner structure (`min-w-0` wrapper + `truncate` label) is
unchanged, so truncation behavior and the label–chevron `gap-1` are
identical to before; the chevron always stays inside the pill.
- The badge row's existing `+N` overflow pill absorbs the remaining
width pressure.

No behavior change for the other `ModelSelector` callsites other than
gaining the same floor, which only applies under width pressure.

<details>
<summary>Iteration notes</summary>

An earlier revision removed `min-w-0` from the button and label wrapper
and put `min-w-[8ch]` on the label. That backfired: without `min-w-0`,
the wrapper's automatic minimum became the full nowrap label width, so
nothing could shrink — truncation stopped and the chevron was pushed out
and clipped. The final approach floors the button width explicitly
instead, leaving the shrink/truncate mechanics untouched.
</details>

---
*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-26 17:46:04 +00:00
Michael Suchacz 7394d2cab0 feat(site): use /api/v2 chat API paths (#28498)
## Stack context

This is the final PR in the 3-PR chat API promotion stack: server
compatibility mounts (#28496), codersdk promotion (#28497), and frontend
path updates (this PR).

## Summary

Switch promoted frontend chat and MCP REST and WebSocket calls to
`/api/v2`. Debug runs, virtual desktop streaming, advisor, and
computer-use provider routes remain on `/api/experimental` because those
surfaces were not promoted.

Update the matching tests, stories, helpers, and end-to-end route
expectations. Remote dogfood UAT passed with chat traffic verified on
the v2 routes.

> [!NOTE]
> Xum acted on Mike's behalf in this pull request.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->
2026-08-26 19:24:56 +02:00
TJ 046a532a18 chore(site/src): move deployment docs links inline as View docs text links (#28612)
Moves documentation CTAs that were previously rendered as header buttons
into inline `View docs` external text links under the relevant header or
section copy.

## Pages updated

Deployment settings:
- `/deployment/overview`
- `/deployment/appearance`
- `/deployment/external-auth`
- `/deployment/oauth2-provider/apps`
- `/deployment/network`
- `/deployment/workspace-proxies`
- `/deployment/idp-org-sync`
- `/deployment/notifications`
- `/deployment/userauth`
- `/deployment/security`
- `/deployment/observability`
- `/deployment/premium`
- `/deployment/ai-governance`

Organization and user settings:
- `/organizations/:organization/groups`
- `/organizations/:organization/roles`
- `/organizations/:organization/idp-sync`
- `/organizations/new`
- `/organizations/:organization/provisioners`
- `/organizations/:organization/provisioner-keys`
- workspace sharing settings

Other admin/tool pages:
- `/ai/settings/gateway-keys`
- `/audit`
- `/connectionlog`
- template permissions settings

## Changes

- `SettingsHeaderDocsLink` now renders the shared inline `Link`
component with the default label `View docs`, external icon, and
screen-reader text for new-tab behavior.
- Right-side header actions now remain reserved for real actions such as
`Add application`, `Create key`, or export buttons.
- `/deployment/security`: removed the duplicate Browser-Only Connections
badge and vertically aligned the remaining badge with the section
heading.
- `/deployment/observability`: kept docs links scoped to Audit Logging
and Monitoring, and removed the top-level observability docs link.
- Notification misconfiguration alerts now use the same `View docs`
text-link treatment.
- Storybook stories updated to assert the new link labels and hrefs
where covered.

## Validation

- `pnpm check`
- `pnpm lint:types`
- Targeted Storybook interaction tests for the updated pages.

_This PR was generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-26 17:13:19 +00:00
Susana Ferreira 12d7bf29c8 fix(site): standardize AI provider badge tooltips (#28624)
The AI provider status badges used different tooltip implementations,
and long warning messages could render as a single wide line.

Use the shared tooltip component for both badges, constrain tooltip
width for readable wrapping, and cover the portaled tooltip content and
accessible triggers in Storybook.

Related to #28549.

<details>
<summary>Screenshots</summary>

**Before**

<img width="1274" height="113" alt="before_proxy_warning"
src="https://github.com/user-attachments/assets/3fb6f317-50d5-434c-8713-cbfab60f6550"
/>

<img width="1085" height="116" alt="before_agent_warning"
src="https://github.com/user-attachments/assets/4d0f4a6a-81b6-49bc-8bc0-f4ea63eb9af0"
/>

**After**

<img width="998" height="190" alt="after_proxy_warning"
src="https://github.com/user-attachments/assets/57076e70-eaaf-43b7-b15a-5861ffe5054c"
/>

<img width="974" height="121" alt="after_agent_warning"
src="https://github.com/user-attachments/assets/d27d1940-8ade-4ee2-b8a4-4f368450e6d6"
/>

</details>

> [!NOTE]
> Generated by Coder Agents for @ssncferreira.

---------

Co-authored-by: Jeremy Ruppel <jeremy.ruppel@gmail.com>
2026-08-26 18:03:56 +01:00
Michael Suchacz 973d5a458b feat: promote codersdk chat API methods to Client (#28497)
## Stack context

This is the second PR in the 3-PR chat API promotion stack: server
compatibility mounts (#28496), codersdk promotion (this PR), and
frontend path updates (#28498).

## Summary

Move the promoted chat and MCP SDK methods from `ExperimentalClient` to
`Client` and update them to use `/api/v2`. Methods for routes that
remain experimental stay on `ExperimentalClient`.

Update in-repository callers and generated types. The multi-replica chat
stream relay dials `/api/v2` directly: mixed-version replica sets are
not a supported upgrade path, so no experimental-path fallback is kept
(per review). Remote dogfood UAT passed for the composed stack.

> [!NOTE]
> Xum acted on Mike's behalf in this pull request.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->
2026-08-26 18:46:34 +02:00
Nick Vigilante 7281027dd8 docs: remove internal Slack link from CONTRIBUTING.md [DOCS-684] (#28320)
The `codercom.slack.com` link in CONTRIBUTING.md returns a 403 for the
docs link checker (Slack archive URLs require authentication, so it can
never pass) and advertises an internal Slack channel in a public
contributor doc. The `pr-deploy.yaml` workflow already treats the
channel URL as a secret, so the doc now refers to the channel by name
only.

While in the file, this also fixes a few formatting issues: emdashes
used as punctuation in the dev server list and the backport conflict
note, sample `nix-shell` output where hard-wrapping had garbled the
`cache.nixos.org` URLs, and a misindented closing code fence in the
"Verify Your Session" step.

Refs
[DOCS-684](https://linear.app/codercom/issue/DOCS-684/remove-internal-slack-link-from-contributingmd-and-fix-minor)

---
🤖 Built with AI assistance.
2026-08-26 12:45:33 -04:00
Garrett Delfosse 50f36315fd fix(coderd): accept RFC 9728 resource metadata with array-valued resource (#28592)
GitLab's official MCP server returns `"resource"` as a JSON array in its
`/.well-known/oauth-protected-resource` document, while RFC 9728 §2
defines it as a string. Coder's OAuth2 auto-discovery failed to decode
the response (`json: cannot unmarshal array into Go struct field
protectedResourceMetadata.resource of type string`), blocking
zero-config `oauth2` DCR against GitLab.

This makes `protectedResourceMetadata.Resource` tolerate both a single
string and an array of strings.

Part of
[CODAGT-570](https://linear.app/codercom/issue/CODAGT-570/coder-agents-mcp-client-cannot-connect-to-gitlabs-official-mcp-server).
The other blocker in that issue (GitLab replying 204 to
`notifications/initialized`) was already fixed by the migration from
`mark3labs/mcp-go` to `modelcontextprotocol/go-sdk`, whose
streamable-HTTP client accepts 204.

<details>
<summary>Investigation notes</summary>

CODAGT-570 reported two independent incompatibilities with GitLab CE's
`/api/v4/mcp` server:

1. **204 vs 202 on `notifications/initialized` (blocked all auth
modes):** the bundled `mark3labs/mcp-go` client only accepted 200/202
for notification POSTs. That library has since been removed entirely;
`coderd/x/chatd/mcpclient` now uses
`github.com/modelcontextprotocol/go-sdk v1.7.0`, which accepts both 204
and 202 (and only warns on other codes in non-strict mode, which is the
only mode reachable from Coder). No further change needed.
2. **RFC 9728 `resource` array (blocked zero-config oauth2 DCR):**
Coder's own parser in `coderd/mcp.go` declared `Resource string`. This
lives outside the MCP library, so the SDK migration did not fix it.
Fixed here with a custom `UnmarshalJSON` accepting string or array of
strings. The field is only decoded, never consumed downstream, so
behavior is otherwise unchanged.

</details>

---

Generated by Coder Agents on behalf of @f0ssel.
2026-08-26 12:27:17 -04:00
Nick Vigilante 8afd0020ea docs: fix chat status names and auto-archive behavior claims (#28570)
Two Coder Agents pages describe chat statuses that no longer exist and
attribute auto-archive to the wrong subsystem. The valid status set is
`waiting`, `running`, `error`, `requires_action`, `interrupting`.

- `architecture.md`: a submitted prompt marks the chat `running`, not
`pending`; there is no queued state.
- `chat-auto-archive.md`: the excluded status set is `running`,
`interrupting`, `requires_action`.
- `chat-auto-archive.md`: the job runs in the chatd chat worker on an
hourly tick, not in `dbpurge`.
- `chat-auto-archive.md`: documents the dashboard control at **AI
Settings** > **Coder Agents** > **Lifecycle**, and the 25-title cap on
the digest notification.

API paths on the auto-archive page are intentionally untouched; a
separate PR fixes the `/api/v2` to `/api/experimental` drift.

Linear: DOCS-722 https://linear.app/codercom/issue/DOCS-722

<details><summary>Analysis evidence</summary>

- `codersdk/chats.go`: `ChatStatus` constants are `waiting`, `running`,
`error`, `requires_action`, `interrupting`. No `pending`, `paused`, or
`completed`.
- `coderd/x/chatd/chatstate/transitions.go`:
`sendMessageQueueAndSetStatus` and the send paths apply
`database.ChatStatusRunning`, so a submitted prompt is `running`
immediately. `docs/ai-coder/agents/tasks-to-chats-migration.md` already
states that chats have no separate queued state.
- `coderd/database/queries/chats.sql`,
`GetAutoArchiveInactiveChatCandidates`: `status NOT IN ('running',
'interrupting', 'requires_action')`, plus `archived = false`, `pin_order
= 0`, `parent_chat_id IS NULL`.
- `coderd/x/chatd/auto_archive.go`: `archiveLoop` runs from
`coderd/x/chatd/worker.go` on `opts.ArchiveInterval`;
`coderd/x/chatd/options.go` sets `defaultArchiveInterval = time.Hour`.
Nothing in `dbpurge` performs chat auto-archive.
- `coderd/x/chatd/auto_archive.go`: `chatAutoArchiveDigestMaxChats =
25`; `buildAutoArchiveDigestData` truncates to 25 titles and reports the
remainder as `additional_archived_count`.
- `coderd/x/chatd/options.go`: `defaultArchiveBatchSize = int32(1000)`,
so the existing "up to 1,000 root chats" claim stands.
- `site/src/pages/AISettingsPage/LifecyclePage/LifecyclePageView.tsx`
mounts `AutoArchiveSettings`;
`site/src/modules/management/AISettingsSidebarView.tsx` places the
Lifecycle sub-nav item under Coder Agents.

Validation: `pnpm run format-docs` (no changes) and `pnpm run lint-docs`
(0 errors).

</details>

Generated by Coder Agents on behalf of @nickvigilante.
2026-08-26 11:36:36 -04:00
Mathias Fredriksson 6093705476 fix: use old_text/new_text vocabulary across edit_files (#28626)
The schema has advertised old_text/new_text since #25658, but agent
error strings and hints still said "search string", directing the
model toward field names it is not sending.

Fixes CODAGT-523
2026-08-26 18:35:27 +03:00
Mathias Fredriksson c51ffc7c88 fix(coderd/x/chatd/chatprompt): keep NUL out of jsonb chat content (#28625)
PostgreSQL jsonb rejects \u0000, so persisting Gemini responses failed
with "pq: unsupported Unicode escape sequence": binary thought
signatures reach ProviderMetadata as strings containing NUL, and the
sentinel codec only covered six fields. Real recorded Gemini
signatures contain NUL in 22 of 34 cases.

Classify every string-bearing part field in one table, partNulFields.
Free-form and opaque fields encode NUL reversibly as PUA sentinel
pairs; structured fields (identifiers, paths, URLs) reject it with an
error naming the part, field, and offset, since their consumers cannot
handle NUL anyway. Raw JSON is also normalized for lone surrogate
escapes, which jsonb equally rejects.

A reflection-driven test injects NUL into every field and fails when a
future field is missing from the table. The Gemini regression asserts
the sentinel-encoded row in the database and a byte-exact signature
round-trip.

Known limits: json.Marshal corrupts other invalid UTF-8 upstream of
this codec (CODAGT-986), and chat debug steps have always dropped
silently on NUL prompt text (CODAGT-987, pre-existing).

Fixes coder/coder#25555
Fixes CODAGT-463
2026-08-26 18:21:13 +03:00
Susana Ferreira 276d0a8586 fix: expose effective member AI budgets (#28410)
## Description

The group members page could not distinguish an unlimited Everyone
fallback from a configured budget on the Everyone group when viewing
another group, so it incorrectly displayed `Unlimited` and `Everyone
(not allocated)` despite the configured Everyone budget.

The member spend response now includes `effective_budget` as the
canonical budget that currently applies to the user. The existing
`group_budget` field remains for backward compatibility and is
deprecated in favor of `effective_budget`.

## Changes

- Add `effective_budget` to group member AI spend responses.
- Deprecate `group_budget` in favor of `effective_budget`.
- Display budgeted Everyone as the governing group instead of
`Unlimited` and `Everyone (not allocated)`.
- Add database, API, conversion, and Storybook coverage.

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

> [!NOTE]
> Generated by Coder Agents on behalf of @ssncferreira.
2026-08-26 16:17:07 +01:00
Michael Suchacz 351bb14032 feat: mount chat API routes under /api/v2 (#28496)
## Stack context

This is the base of a 3-PR stack promoting the chat API from
`/api/experimental` to `/api/v2`: server compatibility mounts (this PR),
codersdk promotion (#28497), and frontend path updates (#28498).

## Summary

Double-mount the stable chat and MCP handlers under `/api/v2` while
retaining the existing experimental routes for the one-release
compatibility window decided in CODAGT-921. CODAGT-922 tracks removing
the compatibility mounts.

The shared route builders preserve existing authentication and
middleware behavior. Experiment-gated, debug, tombstone, and legacy
default-organization model routes remain experimental-only. Signed file
URLs, external OAuth callback URLs, and mixed-version replica relays
also remain on the experimental prefix during the transition.

Update Swagger and the generated API reference for the promoted routes,
including the workspace lookup and a runnable raw-body chat file upload
example. Retain internal endpoints outside the published reference,
share chat-file rate limits across both prefixes, enable CORS for the v2
MCP routes, and cover dual mounts plus exclusions with compatibility
tests. Remote dogfood UAT passed for the promoted chat, model, MCP, and
file flows.

> [!NOTE]
> Xum acted on Mike's behalf in this pull request.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->
2026-08-26 16:17:06 +02:00
Ethan 0c0e605521 fix(site): show Manage agents link to organization model admins (#28637)
The Agents settings panel only showed the **Manage agents** link to
users with `editDeploymentConfig`, even though the Coder Agents page it
links to (`/ai/settings/coder-agents`) already admits organization model
admins, as does the AI settings sidebar. Organization model admins had
no way to discover the page from the Agents settings panel.

Gate the link on a new `canAccessCoderAgentsSettings` helper (deployment
edit access or any chat model config access), threaded as a separate
`canManageAgentSettings` prop through `AgentsPageLayout`,
`ChatsSidebar`, and `SettingsPanel`. `isAdmin` is left untouched because
it also controls API-key item visibility and the provider configs query.

Refs CODAGT-74.

<details>
<summary>Implementation plan (Coder Agents)</summary>

### Bug: hidden Manage agents link

Current behavior:

- `AgentsPageLayout.tsx` defines agents administration as
`permissions.editDeploymentConfig` and threads it as `isAdmin` through
`ChatsSidebar`.
- `SettingsPanel.tsx` shows **Manage agents** only when `isAdmin`.
- `CoderAgentsPage.tsx` already allows organization model
administrators, and #28473 explicitly intended the page and sidebar
entry to support deployment access or organization model access.

Design:

- Add `canAccessCoderAgentsSettings(permissions)` in
`site/src/modules/permissions/index.ts`, defined as deployment edit
access or `canAccessAnyChatModelConfig(permissions)`.
- Add a separate `canManageAgentSettings` prop through
`AgentsPageLayout`, `ChatsSidebar`, and `SettingsPanel`.
- Do not widen `isAdmin`, because it also controls API-key visibility
and provider queries.

Tests:

- `ChatsSidebar` story where `isAdmin` is false and organization model
access is true: the link appears and the API-key item stays hidden.
- `AgentsPageLayout` story with `MockNoPermissions` plus
`editAnyChatModelConfig`: the link appears through the full
permission-driven flow.
- The plain-member denial stories stay green.

</details>

---

Generated by Coder Agents on behalf of @ethanndickson.
2026-08-26 13:45:50 +00:00
Michael Suchacz 45f2eb48cc refactor(coderd): restore agents-access cleanup migration (#28587)
## Summary

- restore the `agents-access` data cleanup migration as `000587`
(renumbered from `000585`; main advanced past that number)
- revert the temporary RBAC tolerance for retired role names now that
the migration strips stale grants
- make the migration test discover its predecessor dynamically, since
this branch does not carry main's `000585`/`000586`

## Stack context

Stacked on #28186, which retires the `agents-access` role but
intentionally ships without a migration so it can be backported across
diverged migration numbers. This layer completes the retirement on main:
it cleans up stale role grants in `users.rbac_roles`,
`organization_members.roles`, and
`organizations.default_org_member_roles`, then reverts the interim
leniency (role expansion dropping legacy names, assignment validation
ignoring them) that existed only to keep the migration-less base safe.
`rbac.ReservedRoleName` still reserves the retired name so custom roles
cannot shadow it on rollback. Do not backport this PR alongside the base
PR.

## Rollback note

The down migration restores effective access broadly because it cannot
reconstruct which users previously held the removed role.

## Validation

- `go build ./coderd/... ./enterprise/...`
- full `coderd/database/migrations`, `coderd/rbac/...`, and
`coderd/database/dbauthz` suites
- targeted members tests in `coderd`
- repository pre-commit checks (`lint/go`, `lint/ts`, etc.)

> Mux created this pull request on Mike's behalf.
2026-08-26 15:29:26 +02:00
Hugo Dutka 31146642ba fix(codersdk/toolsdk): select default chat org based on recent usage (#28543)
When `coder_create_chat` omitted `organization_id`, it selected the
first organization membership, which could choose an organization
without an available chat model.

Default to the organization used by the user's most recently updated
chat. For users with multiple organizations and no prior chats, return a
descriptive error requiring `organization_id`; single-organization
behavior is unchanged.

Closes
https://linear.app/codercom/issue/CODAGT-968/fix-mcp-chat-creation-organization-selection

<details>
<summary>Coder Agents disclosure</summary>

This pull request was generated by Coder Agents.
</details>
2026-08-26 14:52:44 +02:00
Susana Ferreira e3e7200cff fix(site): distinguish cross-org budgets (#28596)
## Description

Group member budget actions were disabled whenever another group
governed the member's budget, including groups in the same organization.

Distinguish same-organization and cross-organization effective groups so
administrators can manage overrides from any same-org group page. Keep
the action disabled when the effective group belongs to another
organization.

## Changes

- Add an `otherOrganization` effective-group classification.
- Enable budget management for same-org effective groups.
- Keep budget management disabled for cross-org effective groups.
- Render same-org lookup failures without the cross-org message.

> [!NOTE]
> Generated by Coder Agents on behalf of @ssncferreira.
2026-08-26 13:09:39 +01:00
Michael Suchacz 845790e652 feat: enable Coder Agents for organization members (#28186)
## Summary

- make Coder Agents available to organization members without a separate
built-in role
- remove the obsolete role from backend and frontend role surfaces
- update authorization, UI, documentation, and regression coverage

## Database note

This PR intentionally ships no migration so it backports cleanly across
diverged migration numbers. Stale `agents-access` grants may remain in
user role arrays and org default member roles; the retired name is
treated as a grant of nothing (role expansion drops it, assignment
validation ignores it, and the name stays reserved). A follow-up PR will
add the data cleanup migration.

## Validation

- `make gen`
- `make lint`
- repository pre-commit checks
- targeted RBAC, migration, chat API, enterprise, site, Storybook, and
race tests
- `git diff --check`

## Rollback note

Rolling back the deployment restores the previous behavior directly: no
data changed, and older binaries still resolve any lingering
`agents-access` grants.

> Mux created this pull request on Mike's behalf.
2026-08-26 13:33:13 +02:00
Hugo Dutka 9dda7e7a1b test(cli): wait for expected GitHub auth state (#28622)
The GitHub OAuth server test treated any successful `AuthMethods`
response after restart as ready. In the failing macOS run in CI
(described in the linked Linear issue), the response decoded
successfully but reported GitHub authentication as disabled, even though
startup logged the default GitHub provider configuration.

Wait until the complete GitHub authentication method reaches the
expected state before asserting it. This preserves retries for transport
readiness while also covering successful but incorrect startup
responses.

Closes
https://linear.app/codercom/issue/ENG-3285/flake-testserveroauth2githubdefaultprovidernewdeployment

<sub>Generated by Coder Agents.</sub>
2026-08-26 13:16:29 +02:00
Susana Ferreira 56c614422e fix: clarify AI provider collision warnings (#28549)
AI provider collision warnings were only partially asserted, and the
update coverage did not exercise a provider changing from a distinct
hostname to a colliding hostname.

Assert the complete warning consistently across create, get, list, and
update responses, cover collisions introduced by base URL and
enabled-state updates, and only return the proxy-specific warning when
AI Gateway Proxy is enabled. Clarify that lifecycle status values are
reported by the provider metrics.

Follow-up to #28208.

> [!NOTE]
> Generated by Coder Agents for @ssncferreira.
2026-08-26 11:12:33 +01:00
Jake Howell bd4af31f78 fix(coderd/provisionerdserver): clarify duplicate preset errors (#28530)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Template imports with duplicate preset names currently expose the
underlying PostgreSQL unique constraint error.

Translate that constraint violation into a clear validation message that
identifies the duplicate preset name.

<img width="1110" height="462" alt="image"
src="https://github.com/user-attachments/assets/dc7b86a3-8f85-4694-a0be-aea3d0b53360"
/>


Closes https://github.com/coder/coder/issues/19270
2026-08-26 11:00:31 +07:00
Jake Howell 5008603d08 fix(site): dispose Monaco diff models on unmount (#28503)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Fixes
[DEVEX-736](https://linear.app/codercom/issue/DEVEX-736/template-editor-crashes-becomes-unusable-when-opening-versions).

## Problem

Opening template versions in the template editor leaks Monaco text
models. Each version renders one `DiffEditor` per changed file, and
`SyntaxHighlighter` sets `keepCurrentOriginalModel` /
`keepCurrentModifiedModel`, which tells `@monaco-editor/react` **not**
to dispose the underlying text models when the editor unmounts. Because
no stable model paths are supplied, every visit creates fresh anonymous
models that are never freed. Navigating between versions accumulates
models without bound until the tab lags, spins, and eventually OOMs —
matching the customer reports on 2.34.5.

The flags themselves are still required: removing them makes the library
dispose the models mid-teardown (before the `DiffEditorWidget` is torn
down), which throws.

## Fix

Keep the `keepCurrent*Model` flags, but move the diff editor into its
own `DiffFile` component that owns the model cleanup:

- `DiffFile` captures the `original`/`modified` models in `onMount` and
disposes them in its own unmount effect. Because the effect lives with
the diff editor, it runs whenever the diff editor unmounts — **including
when a file switches diff → plain across versions while
`SyntaxHighlighter` stays mounted** (the `key={filename}` case raised in
review), not only on full unmount.
- Disposal is deferred with `queueMicrotask` so it runs after React's
commit and after `@monaco-editor/react` disposes the widget. Freeing the
models in the same synchronous teardown throws `TextModel got disposed
before DiffEditorWidget model got reset`.

Non-diff files use the plain `Editor`, which already disposes its own
model, so they are unaffected. No editor options or UI behavior change.

## Evidence

Reproduced locally on a template with 32 versions (9 `.tf` files each),
driving 192 in-app version navigations (single SPA session, no page
reloads) and reading `monaco.editor.getModels().length` via temporary
instrumentation.

| | before (pass 1 → pass 6) | after (pass 1 → pass 6) |
|---|---|---|
| retained Monaco models | 532 → **3,142** (unbounded) | ~10–19 (flat) |
| per-navigation render latency | 206ms → **611ms** | ~120–270ms (flat)
|

Before: models climb ~520 per full pass and never release (a sample
taken with zero editors on screen still showed 522 live models). After:
model count and render latency stay flat across the same workload.

## Testing

Added `SyntaxHighlighter.stories.tsx` with a `play` regression test
(`DisposesModelsOnDiffToggle`) that reproduces the exact leak: it
toggles a single surviving `SyntaxHighlighter` between diff and plain
three times and asserts `monaco.editor.getModels().length` drops when
the diff is removed and returns to the baseline each cycle (no
accumulation).

- The test **fails on the unfixed behavior** (disposal removed):
`expected 5 to be less than 4` — models grow instead of being freed.
- With the fix it passes with **zero Monaco teardown errors**:

  ```
$ pnpm test:storybook
src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx
   Test Files  1 passed (1)
        Tests  3 passed (3)
       Errors  0
  ```

- `pnpm check` (biome) — pass
- `pnpm lint:types` (`tsc --noEmit`) — pass
- Manual stress test: model count and render latency stay flat across
192 navigations (see Evidence).

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

**Root cause trace**
- `site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx` →
`TemplateFiles` renders every template file at once.
- Each changed file → `SyntaxHighlighter` → a full Monaco `DiffEditor`.
- `@monaco-editor/react@4.7.0` unmount cleanup does, in effect:
  ```js
  const model = editor.getModel();
  keepCurrentOriginalModel || model.original.dispose();
  keepCurrentModifiedModel || model.modified.dispose();
  editor.dispose();
  ```
With both `keepCurrent*` flags set, neither model is disposed, so they
leak.
- The flags exist to stop a storybook hang caused by that same
disposal-order error, so they cannot simply be removed.

**Options considered**
1. Remove the flags — fixes the leak but re-introduces the mid-teardown
throw. Rejected.
2. Supply stable `originalModelPath`/`modifiedModelPath` so models are
reused — the library reuses by URI, but its value-sync effect is skipped
on the first render, so a reused model renders **stale content** for the
new version. Rejected.
3. Keep the flags and dispose the captured models when the diff editor
unmounts — targeted and correct for the diff → plain → diff case.
**Chosen.**

**Why the cleanup is scoped to a child component**
The first iteration kept the effect in `SyntaxHighlighter` with an empty
dependency array, so it only ran on full unmount. Review correctly
flagged that a surviving instance switching diff → plain would then
orphan the previous model pair. Scoping the effect to the `DiffFile`
child ties it to the diff editor's own lifecycle, closing that gap.

**Why `queueMicrotask`**
Disposing synchronously in the effect cleanup races Monaco's own widget
teardown and throws `TextModel got disposed before DiffEditorWidget
model got reset`. Deferring one microtask lets the widget finish tearing
down first.

**Test-only editor options**
The stories pass `renderGutterMenu: false` / `occurrencesHighlight:
"off"` through `editorProps`. Those Monaco features register delayed
disposables whose teardown throws under jsdom on unmount; disabling them
keeps the test runner clean. They do not affect model count, and
production keeps Monaco's default options unchanged.
</details>
2026-08-26 10:32:08 +07:00
Bobby Ho c9fc5b7ea1 fix: canonicalize API key scope aliases at ingress (#28322)
`rbac.IsExternalScope` accepts `all` and `application_connect`, neither
of which is an `api_key_scope` enum member. The plural `Scopes` field
stored the name as given, so `{"scopes":["all"]}` passed validation and
then 500'd inside `apikey.Generate` with `invalid API key scope: "all"`.
Reachable from `coder tokens create --scope all`, from the spellings
`docs/admin/users/sessions-tokens.md` taught, and from the `codersdk`
constants exported for exactly this purpose.

- Canonicalize inside `apikey.Generate`, in the loop that already
validates each name. One statement covers every caller and all three
input paths: plural `Scopes`, deprecated singular `Scope`, and the
default. Replaces two open-coded switches and the handler's own
per-element copy, so one place decides the stored spelling and the
handler only decides what may be requested.
- Deduplicate in the same pass. An alias and its canonical spelling are
two names going in and one name in the column.
- `ExternalScopeNames()` lists every name `IsExternalScope` accepts,
instead of dropping catalog entries that fail to parse. A curated entry
that cannot be stored used to pass every test and 500 at runtime; it now
fails two.
- Split the 400. A misspelled name and an internal `api_key_scope`
member need different words, since no re-spelling makes the second
requestable. Both rejection sites share one helper that names the case
and links the docs.
- Add reject-path tests for `not_a_real_scope` and `debug_info:read`.
The second is a valid enum member the rbac catalog treats as internal,
so before this both handler guards could be deleted with the suite still
green. A further case pins that plural `Scopes` wins when a caller sets
both fields.
- `TestExternalScopesAreStorable` pins the class rather than the two
known instances: every public rbac scope name must be storable.
`coderd/rbac` cannot assert this itself, since `database` imports `rbac`
and not the reverse.
- Docs use the canonical spellings, link the `codersdk.APIKeyScope`
schema, and say which scopes a token cannot request.

Egress is unchanged: `convertAPIKey` still derives a legacy singular
name on the way out. `ExternalScopeNames()` returns the same 57 names as
before, and `codersdk/apikey_scopes_gen.go` regenerates identically.

`rbac.CanonicalScopeName` merged with #28167, so this applies to `main`
and reviews on its own. The OAuth2 provider ignores scopes entirely
today (`authorize.go:237`, `tokens.go:377`, `tokens.go:520`), so it is
unaffected until those TODOs resolve.

Deferred from review: PLAT-528, PLAT-529, PLAT-530, and PLAT-532.
PLAT-532 covers `{"scopes":[]}` and `{"allow_list":[]}` defaulting open,
which predates this PR.

---------

Co-authored-by: McKayla はな <mckayla@hey.com>
2026-08-25 17:12:51 -07:00
McKayla はな 895c5c77a1 feat: add a template parameter settings page (#27559) 2026-08-25 15:06:30 -06:00
Bobby Ho 2236710bad fix(coderd/notifications): HTML-escape the email template values (#28397)
Follows #28340, now merged.

`smtp.go` renders the notification title through
`PlaintextFromMarkdown`, which strips Markdown **and decodes HTML
entities**, then stores the result in `Labels["_subject"]`.
`html.gotmpl` interpolated that raw into `<title>` and `<h1>`, so an
entity-encoded payload in a user-controlled label arrived as live
markup:

```
template_display_name = &lt;a href="https://attacker.example/login"&gt;Re-authenticate now&lt;/a&gt;

  -> <title>Template "<a href="https://attacker.example/login">Re-authenticate now</a>" deleted</title>
```

Markdown escaping cannot reach this. `&` is not backslash-escapable in
either renderer, and this path never enters gomarkdown, so neither
`html.SkipHTML` nor the `Safelink` added in #28340 sees the string. `{{
.UserName }}` was interpolated raw at the same template, straight from
the unescaped payload the dispatcher receives.

This PR adds `| html` to seven values across eleven positions in
`html.gotmpl`:

| Value | Positions | Why |
|---|---|---|
| `.Labels._subject` | 2 | the injection above, in `<title>` and `<h1>`
|
| `.UserName` | 1 | reaches the template straight from the unescaped
payload |
| `$action.URL` | 1 | rendered from user data at `enqueuer.go:201`;
`EscapedForMarkdown` does not touch `Actions` |
| `$action.Label` | 1 | static today, escaped so it stays safe if that
changes |
| `base_url` | 4 | `--access-url` is scheme-checked only, so a `"`
closes the `href` |
| `current_year` | 1 | cannot carry markup, escaped so the rule has no
exceptions |
| `.NotificationTemplateID` | 1 | same |

`logo_url` and `app_name` were already escaped in #28340. `{{
.Labels._body }}` stays unescaped: it is intentionally gomarkdown
output, and it is the only value in the file that is not escaped.

The action and `base_url` values are defense in depth rather than open
holes. A `"` in an action URL fails closed at enqueue, because the
rendered actions JSON is unmarshalled before use and the quote breaks
that parse; `<`, `>`, `&` and `'` survive but are inert inside a
double-quoted attribute. `base_url` requires an operator to set a
hostile `--access-url`.

Every value is guarded by a test. Removing `| html` from any of the nine
escaped values now fails a named test, verified by removing each pipe in
turn:

- `TestSMTPHTMLTemplateEscapesUntrustedValues` covers `_subject`,
`UserName` and both action values.
- `TestSMTPHTMLTemplateEscapesTrustedValues` covers `base_url`,
`current_year` and `.NotificationTemplateID`, none of which can carry
markup in production, so no golden file would catch their regression.
- `TestSMTPHTMLTemplateEscapesAppearanceHelpers` covers `logo_url` and
`app_name`.

That last point is why the trusted values needed tests rather than
goldens: escaping them costs zero golden churn, so nothing already in
the tree fails when it is removed. Before this PR the same was true of
`$action.Label`, whose escaping could be deleted with no golden diff and
no failing test at all.

The 36 golden files change by entity encoding only, mostly `"` to
`&#34;` and `'` to `&#39;` in subjects. Verified by quoted-printable
decoding every file before and after and confirming the two are
identical once HTML entities are decoded: 36/36 with no semantic
difference. Escaping `base_url`, `current_year` and
`.NotificationTemplateID` added no further churn.

**NOTE**: `$action.URL | html` turns the `&` in the one-time passcode
reset link into `&amp;`. That is the correct encoding of a literal `&`
in an attribute value, and every conformant client decodes it before
navigating, so the request the server receives is unchanged. It is the
only golden change with behavior attached.

Migrating this template to `html/template` was considered and declined;
the reasoning and the conditions that would reverse it are on the CRF-3
review thread.

Refs
https://linear.app/codercom/issue/PLAT-273/markdown-link-injection-into-admin-notification-emails-sec-93
2026-08-25 14:02:33 -07:00
Callum Styan c4d356ff71 feat(cli): add --username-infix selection to scaletest notifications… (#28515) 2026-08-25 12:48:00 -07:00
Callum Styan f4730aed12 feat: add --username-infix to scaletest create-users (#28514) 2026-08-25 12:45:02 -07:00
Nick Vigilante ce59a86a81 docs: fix Coder Agents tool tables, search filters, attachments, template ranking (#28575)
Corrects four independent factual errors in the Coder Agents
documentation, each verified against the current implementation.

- **Missing tools in the tool tables.** `stop_workspace` was absent from
the built-in tool table in `docs/ai-coder/agents/index.md` and the
platform tool table in `docs/ai-coder/agents/architecture.md`, and from
the root-chat-only tool list. `find_tools` was undocumented entirely;
both docs now list it with its `mcp-tool-search` experiment gating.
- **Missing `source:` search filter.**
`docs/ai-coder/agents/chat-search-syntax.md` documented every chat
filter except `source:`, while also stating that unrecognized keys
return HTTP 400. Adds the filter row and an example.
- **Attachments described as images only.** The attachments section
claimed PNG, JPEG, GIF, and WebP up to 10 MB. The upload allowlist also
accepts plain text, Markdown, CSV, JSON, and PDF; the cap is 10 MiB per
upload with at most 50 attachments per conversation, and Anthropic
models cap inline images at 5 MiB. Also notes that providers differ in
accepted file types and that rejected parts are downgraded to text.
- **Template ranking described as popularity sort.**
`docs/ai-coder/agents/platform-controls/template-optimization.md` said
templates are sorted by active developer count. `list_templates` ranks
by query relevance tiers first, then by an affinity score that weights
the user's own recent usage far above organization popularity. Wording
now matches `docs/ai-coder/agents/tools/index.md`, which already
documents the formula, and links to it.

Linear: DOCS-716 https://linear.app/codercom/issue/DOCS-716

<details><summary>Analysis evidence</summary>

| Claim | Source |
|-------|--------|
| `stop_workspace` is a root-chat platform tool registered alongside
`create_workspace` and `start_workspace` |
`coderd/x/chatd/chattool/stopworkspace.go`; registration in
`coderd/x/chatd/chatd.go` (`chattool.StopWorkspace`), root-only gate in
`builtinPlanToolAllowed`, disabled for explore sub-agents in
`allowedExploreToolNames` |
| `find_tools` requires the `mcp-tool-search` experiment and deferrable
MCP candidates | `coderd/x/chatd/chattool/findtools.go`;
`decideMCPToolSearch` in `coderd/x/chatd/mcp_tool_search.go` returns
false without `codersdk.ExperimentMCPToolSearch` or with no candidates;
wired in `coderd/x/chatd/generation_preparer.go` |
| `source:` accepts `created_by_me` and `shared_with_me`; passing both
returns owned plus shared; default is owned only |
`coderd/searchquery/search.go` (`Chats`): filter defaults to `OwnedOnly:
true`; the `source` parser rejects any other value, and both values
together set `OwnedOnly` and `SharedOnly`. The literal value `all`
appears in the doc comment but is not accepted by the parser, so it is
not documented. |
| Attachment allowlist, size caps, and per-conversation limit |
`codersdk/chats.go`: `AllChatAttachmentMediaTypes` covers
`application/json`, `application/pdf`, `image/gif`, `image/jpeg`,
`image/png`, `image/webp`, `text/csv`, `text/markdown`, `text/plain`;
`MaxChatFileSizeBytes = 10 * 1024 * 1024`; `MaxChatFileIDs = 50`;
`AnthropicInlineImageCapBytes = 5 * 1024 * 1024` |
| Anthropic and Bedrock share the inline image cap; rejected file parts
are downgraded to text | `coderd/x/chatd/chatprovider/chatprovider.go`:
`InlineImageCapBytes` and `Model.AcceptsFilePartMediaType` |
| Template ranking is relevance tiers then a composite affinity score |
`coderd/x/chatd/chattool/listtemplates.go`: `rankTemplates` sorts by
query score first; `computeAffinityScore` adds recency-decayed personal
usage at `listTemplatesPersonalWeight = 10.0` to `ln(1 +
active_developers)` at `listTemplatesOrgWeight = 1.0` |

Note: `MaxChatFileIDs` is enforced per chat, not per message
(`coderd/exp_chats.go`: "A chat can reference at most %d attachments"),
so the docs say per conversation.

Validation: `pnpm run format-docs` and `pnpm run lint-docs` both pass (0
errors). The formatter widened the touched tables, which accounts for
the reformatted rows in the diff.

</details>

Generated by Coder Agents on behalf of @nickvigilante.
2026-08-25 15:13:21 -04:00
Nick Vigilante 06c0d3918a docs: correct plan mode MCP tool availability (#28569)
`docs/ai-coder/agents/index.md` claimed that MCP tools are blocked in
plan mode. Root plan-mode chats actually receive external MCP tools for
each server configuration an administrator approved for plan mode; only
workspace MCP tools and plan-mode sub-agents get none.

Corrects the plan-mode tool-availability wording in both places it
appears in `index.md` so it matches
`docs/ai-coder/agents/architecture.md` and the implementation. The tool
table and attachment wording are out of scope here.

Linear: DOCS-724 https://linear.app/codercom/issue/DOCS-724

<details><summary>Analysis evidence</summary>

`coderd/x/chatd/chatd.go`, `filterExternalMCPConfigsForTurn`: in plan
mode, a chat with a parent gets no external MCP configs ("Plan-mode
subagents do not receive external MCP tools"); a root chat keeps every
config whose `AllowInPlanMode` flag is set and returns those IDs as
approved.

`coderd/x/chatd/generation_preparer.go`: the returned
`approvedPlanMCPConfigIDs` flows into `filterToolsForTurn` and
`activeToolNamesForTurn`, so approved external MCP tools remain exposed
for the turn.

`coderd/x/chatd/chatd.go`, `toolAllowedForTurn`: in plan mode a tool
passes only if it is on the built-in plan allowlist or is an MCP tool
whose `MCPServerConfigID()` is in the approved set. Workspace MCP tools
are not in the external config set, so they stay unavailable.

`docs/ai-coder/agents/architecture.md` already documents this behavior;
`index.md` now uses the same framing.

</details>

Generated by Coder Agents on behalf of @nickvigilante.
2026-08-25 19:02:35 +00:00
Michael Suchacz 60b03138ae fix(coderd/x/chatd): truncate overlong generated chat titles instead of rejecting them (#28589)
Chat title generation asks the model for a title in 2-8 words and then
hard-rejects any response longer than 8 words
(`validateGeneratedTitle`). Quickgen pins temperature for repeatable
output, so a model that overshoots the budget for a given conversation
overshoots on every retry: the rename dialog's Generate button returns a
deterministic 500 (`generate manual title: generated title exceeded 8
words`) no matter how often it is clicked, and the automatic
first-message path silently leaves the chat on its fallback title. On
dev.coder.com this rejection fires 1-3 times a day; one chat took 13
consecutive manual failures on 2026-08-25 (14:22-14:27 UTC).

Truncate the normalized title to the 8-word budget in
`normalizeTitleOutput` instead of rejecting it, and keep the empty-title
validation. Both the automatic and manual title paths share this
normalization, so both are fixed.

> Xum (AI agent) authored this change and PR on Mike's behalf.
2026-08-25 17:56:30 +00:00
TJ 74e5a680bd fix(site/src/pages/AgentsPage): use outlined empty state and kebab menu for personal skills (#28560)
Polishes the `/agents/settings/personal-skills` page to match the table
patterns used elsewhere (e.g. the AI Gateway keys page):

- The table now renders in every state, so the content area always keeps
its outline: `TableLoader` while loading, a `TableEmpty` with a Retry
CTA on error (with the `ErrorAlert` above), and a `TableEmpty` with an
Add skill CTA when there are no skills.
- The per-row Download / Edit / Delete buttons are replaced with a kebab
(`EllipsisVertical`) dropdown menu, following the pattern in
`CustomRolesPageView`. The trigger shows a spinner and disables while a
download is in flight, and the Actions column header is now
screen-reader only.

Storybook interaction coverage: `RowMenuActions` opens the kebab and
exercises Download, Edit, and Delete; `DownloadingSkill` asserts the
disabled trigger.

<details>
<summary>Review notes</summary>

Self-audited the diff against the FE1-FE10 rules in
`.claude/docs/FRONTEND_PATTERNS.md`: all rules pass. All 22 stories pass
locally, along with Biome and `tsc`.

</details>

---

Generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-25 10:37:14 -07:00
Nick Vigilante b146e6f262 docs: align Coder Agents docs with GA framing and licensing terms (#28571)
Removes early-access-program framing from the Coder Agents docs, drops a
stale `early access` manifest tag, and standardizes the licensing
wording on "Premium license with the Agent Hours entitlement".

- `docs/ai-coder/agents/getting-started.md`: replaces the "collaborative
evaluation between your team and Coder" and customer-specific Slack
channel feedback path with GitHub issues plus normal Coder support
channels, and rewrites the remaining evaluation-period wording to GA
tone. Rollout guidance (pilot group, cost planning, concurrency) is
kept.
- `docs/manifest.json`: removes `"state": ["early access"]` from
`ai-coder/agents/platform-controls/chat-debug-logging.md`, which is not
experiment-gated.
- Licensing terminology aligned across `getting-started.md`,
`licensing-usage.md`, and `platform-controls/index.md`.

No changes to `docs/reference/api/**` and no API path changes.

<details><summary>Analysis evidence</summary>

**Manifest state tag decisions (all `ai-coder/agents` pages)**

| Page | Tag before | Tag after | Why |
|------|-----------|-----------|-----|
| `platform-controls/chat-debug-logging.md` | `["early access"]` | none
| Routes at `coderd/coderd.go` (`GET`/`PUT /debug-logging`, `GET`/`PUT
/user-debug-logging`) sit outside any `RequireExperimentWithDevBypass`
group, there is no matching experiment in the `codersdk.Experiment`
list, and the page body already states the feature is not
experiment-gated. |
| `platform-controls/advisor.md` | `["early access"]` | unchanged |
Gated by `codersdk.ExperimentChatAdvisor` (`chat-advisor`); routes
wrapped in `RequireExperimentWithDevBypass`. |
| `platform-controls/virtual-desktop.md` | `["early access"]` |
unchanged | Gated by `codersdk.ExperimentChatVirtualDesktop`
(`chat-virtual-desktop`); routes wrapped in
`RequireExperimentWithDevBypass`. |
| `platform-controls/spend-management.md` | `["premium"]` | unchanged |
`premium` is a licensing tag, not a maturity tag, and the page states
budget endpoints require a license that includes AI Gateway. Flagging
for awareness rather than changing it. |
| All other `ai-coder/agents` pages | none | none | No maturity tags to
review. |

**Licensing mechanics**

`enterprise/coderd/license/license.go` decodes
`agent_runtime_hours_allocation`, `agent_runtime_hours_limit_soft`, and
`agent_runtime_hours_limit_hard` into
`codersdk.FeatureAgentRuntimeHours` on Premium licenses.
`enterprise/coderd/x/chatd/agentadmission.go` lifts the concurrency cap
only when that feature is enabled and usage is below the hard limit. So
the entitlement, not the license tier alone, is what removes the cap.
Wording is now "Premium license with the Agent Hours entitlement".

`licensing-usage.md` keeps "Agent Time" as the name of the measured
usage metric (matching `runtime_ms` reporting) and now says explicitly
that Agent Time draws down the Agent Hours entitlement. Renaming the
metric itself was left out of scope; flagging in case the product team
wants one name for both.

**Validation**

`pnpm install --frozen-lockfile`, `pnpm run format-docs` (no table
changes), `pnpm run lint-docs` (499 files, 0 errors).

</details>

Linear: DOCS-718 https://linear.app/codercom/issue/DOCS-718

Generated by Coder Agents on behalf of @nickvigilante.
2026-08-25 13:10:03 -04:00
McKayla はな 29995c9ef8 feat: add a per-template workspace rename setting (#27558) 2026-08-25 11:05:42 -06:00
Nick Vigilante 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.
2026-08-25 12:49:27 -04:00
Marcin Tojek 31a976b288 chore(docs): update release docs for v2.35.6 (#28577)
Automated docs update for v2.35.6 release.

Created by `releasetui`.
2026-08-25 18:33:58 +02:00
Ethan 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.
2026-08-25 17:24:39 +01:00
Ethan 816f6fbeef fix(site): clarify chat model permission labels (#28579)
Renames chat model sharing controls to permissions terminology so the UI
reflects that administrators are managing model access.

Uses “Use” as the user-facing role while preserving the backend `read`
ACL value, and updates the Storybook interactions for the new labels.
2026-08-25 17:23:08 +01:00
TJ 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.*
2026-08-25 16:16:59 +00:00
TJ 94f312dfbd refactor(site): use robot icon for subagent toggle in chat kebab menu (#28561)
Replaces the rotated `GitForkIcon` on the "Show/Hide subagents" item in
the chat kebab menu with lucide's `BotIcon`, matching the robot icon
already used for subagents in the chat stream (`SubagentTool`,
`ToolIcon`).

---

*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-25 16:34:29 +01:00
Bobby Ho 9e8075db0f fix: prevent markdown injection in notifications (#28340)
First of two PRs.

Notification title and body templates are Markdown authored by Coder,
but the label values interpolated into them are user-controlled and were
substituted through `text/template`, which does no escaping. Those
values arrive from user profile fields and from OIDC/GitHub name claims.

This PR:

- Neutralizes Markdown structure in label, data, and `UserName` values
before they reach the template. Applied in `notifier.prepare`, so
already-queued messages are covered and the stored payload keeps its
original values for webhook consumers. Nested `.Data` map keys are
escaped too: one shipped template prints a key, and those keys are
Terraform resource addresses.
- Narrows the notification Markdown grammar to what templates actually
use. `CommonExtensions` enabled Tables, DefinitionLists and MathJax,
each openable from a value and used by no template. Autolink stays off
so a URL in a value cannot become an anchor.
- Enables `html.Safelink`, restricting generated hrefs to safe schemes,
and guards the panic it exposes: `parser.IsSafeURL` slices a destination
before bounds-checking it, so `[docs]()` crashed both renderers.
- Folds line breaks out of the `Subject:` header and encodes it, fixing
a pre-existing RFC 2047 violation for non-ASCII subjects, a forged
encoded-word that let a value choose the displayed subject, and headers
running past RFC 5322's 998-octet line limit.

Escaping is narrow on purpose, split by where each character carries
meaning:

- `` \[]()!<` `` everywhere. Backtick is in this group because a fenced
block's info string is an HTML sink: gomarkdown writes it into
`class="language-..."` unescaped, and `SkipHTML` does not apply to a
`CodeBlock` node, so a value that closes the attribute and the tag
injects live markup.
- `#-+.>|` only in leading position, so values like `bobby-workspace`
and `1.5` are untouched.
- `=`, `~` and `:` are not escapable by both renderers, so the preceding
line break is folded instead. `:` opens a definition list and a GFM
table delimiter row that escaping `|` cannot reach. A value's *first*
line has no preceding break to fold, so a real tilde fence or `===`
underline is escaped there instead, accepting a visible backslash: an
unterminated `~~~` at the start of a title otherwise renders the
Subject, `<title>` and heading empty.
- Leading indentation is truncated to three spaces. Four open an
indented code block and a space has no escape.
- Emphasis characters are left alone. Escaping `_` corrupts label values
such as `user_override` that body templates compare with `eq`, which
silently drops content from the rendered email.

One golden file changes: the resource replacements `body_markdown` now
reads `docker_container\[0\]`, from the map-key escaping above. Every
other golden is byte-identical.

**One known residual**, pinned by a test that fails if it closes:
CommonMark does not process escapes inside a code span, so where a
template wraps a value in one, as the workspace out-of-disk body does,
the escaper's own backslashes reach the reader. That depends on where
the value lands rather than what it contains, which a pre-render escaper
cannot see. This narrows the class rather than closing it.

**#28397 completes the fix and is stacked on this branch. This PR should
not merge without it.**

Escaping here cannot reach the SMTP HTML template's sinks, by design
rather than by oversight: the subject is produced by
`PlaintextFromMarkdown`, which strips exactly the backslashes added
here, and `html.gotmpl` then interpolated the result through
`text/template`. On this branch alone, a label value still reaches the
Subject, `<title>` and `<h1>` as live markup. #28397 escapes at those
sinks with `| html`, which is the only place the information needed to
escape correctly exists.
2026-08-25 08:12:00 -07:00
Jakub Domeracki be9f8592eb ci: use dedicated release App token to publish releases (#28553)
## Problem

The `Publish release` step fails with `HTTP 403: Resource not accessible
by integration` because the default `GITHUB_TOKEN` cannot create the
release tag under the active tag-create protection ruleset.

## Change

Use a dedicated GitHub App token for the `Publish release` step:

- Add a `Generate release App token` step using `secrets.RELEASE_APP_ID`
/ `secrets.RELEASE_APP_PRIVATE_KEY`.
- Switch only that step's `GITHUB_TOKEN` to the minted App token.

## Required before merge (admin)

1. Create the release GitHub App (least privilege) and install it on
`coder/coder`.
2. Configure `RELEASE_APP_ID` / `RELEASE_APP_PRIVATE_KEY`.
3. Add the App to the tag-create protection ruleset bypass list.

Refs coder/security-automation#297.
2026-08-25 16:32:01 +02:00
Garrett Delfosse 7fc9a6e449 fix: revert update on chat messages table (#28552)
Reverts commit a2459f8dda from #28319
("fix(coderd/database): stamp existing vectors simple and match config
explicitly").

Note: #28319 was squash-merged, so this reverts only that commit's diff,
not the entire PR.

This directly edits a migration on main due to issues with the
previously merged migration taking too long on large tables.
2026-08-25 10:14:41 -04:00
Matt Vollmer 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.
2026-08-25 10:03:25 -04:00
Garrett Delfosse 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.
2026-08-25 09:09:10 -04:00
Garrett Delfosse 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.
2026-08-25 09:08:52 -04:00
Ethan 6284964b5f test(coderd): remove flaky audit transaction case (#28468)
The `TransactionAfterNoChange` case in
`TestChatRetentionDays_AuditInfrastructureFailureRejectsWrite` was flaky
in [Flake Check run
32717900411](https://github.com/coder/coder/actions/runs/32717900411).
Its generic `InTx` wrapper armed a shared fail-next flag, so an
unrelated background transaction could consume the failure and let the
target PUT return 204 instead of the expected 500.

Remove the shared transaction wrapper and replace the endpoint-level
case with deterministic unit coverage of
`auditedChatOperationalSettingWrite`. The mock transaction executes the
no-op callback successfully and then returns an error, preserving
coverage that audit suppression happens only after the transaction
succeeds without involving API background transactions.
2026-08-25 12:59:19 +01:00
Ethan 16e0c4dba7 fix(site/src/pages/AgentsPage): remove redundant model warning (#28484)
The empty chat form showed a generic no-model warning directly above the
existing provider and model setup notice, repeating the same
unavailable-state information.

Remove the redundant warning and update the affected stories to assert
the remaining actionable setup notice.
2026-08-25 12:30:24 +01:00