mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
0b4095085ef182d6aefb57cfd5fbc7546ac2868e
15511
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0b4095085e |
fix: report combined member limit in group AI spend (#27589)
## Problem The organization groups page showed each group's AI budget as the group's per-member limit, so the total it displayed was effectively group members × group budget. That ignores per-user budget overrides charged to the group, so a group where one member has an override reported a limit that doesn't match what its members can actually spend. ## Changes - Add `total_spend_limit_micros` to the organization groups AI spend payload, the combined budget of the members attributed to the group, with each member's override replacing their share. - Return `null` for the total when the group has no budget, since its members spend without a cap. - Both the organization groups and single group spend endpoints report the new field, as they share the same query. - Use the total as the denominator on the groups page AI budget column. Depends on #27568 |
||
|
|
e96d8646e2 |
fix(coderd): give chat message ids an append-order guarantee (#27495)
Chat message ordering was derived from `created_at`, which is `now()` and therefore the transaction start time. That makes it unusable as an append-order column for two independent reasons: every row in one `InsertChatMessages` batch shares a single timestamp, and two concurrent transactions can commit in the opposite order to the one they started in. This PR gives `chat_messages.id` a real append-order guarantee and moves the history reads onto it. ## Changes **`InsertChatMessages` had no input-order guarantee.** Callers index the returned slice by input position. That only worked because PostgreSQL happens to evaluate the `BIGSERIAL` default in row order. Ids are now allocated up front and the k-th smallest is assigned to input index k, so the pairing does not depend on where the column default is evaluated. Returned rows are explicitly `ORDER BY id`. **Three history reads now order by `id`.** | Query | Was | Now | |---|---|---| | `GetChatMessagesByChatID` | `created_at ASC` | `id ASC` | | `GetChatMessagesByRevisionForStream` | `created_at ASC, id ASC` | `id ASC` | | `GetLastChatMessageByRole` | `created_at DESC, id DESC` | `id DESC` | `GetChatMessagesByChatID` paginated by `id` while ordering by `created_at`, which is incoherent on its own terms. The other two matter because of who consumes them. The stream query supplies incremental updates on the same socket that emits a full `GetChatMessagesByChatID` snapshot on history reset, so once that snapshot moved to `id` the two disagreed under timestamp skew. `GetLastChatMessageByRole` returns an id that is then used as an id cursor, both as `AfterID` when synthesizing tool cancellations and as `chats.last_read_message_id`, where a stale anchor leaves later assistant messages permanently unread. A tie-breaker would not have fixed either one. It only resolves equal timestamps; leading with `created_at` is the actual defect. **`GetLastChatMessageByRole` loses its index, so this adds one.** `ORDER BY created_at DESC, id DESC` could take an ordered scan of `idx_chat_messages_chat_created`. Nothing in the schema can supply `ORDER BY id DESC LIMIT 1` for a given `chat_id` and `role`, so the planner switches to a backward scan of the primary key and filters every newer row in the table, scanning all of it when the chat has no message in that role, which is the routine case for a fresh chat. Migration `000559` adds `(chat_id, role, id DESC) WHERE deleted = false`, the same shape as the existing `idx_chat_messages_user_prompts`. This matters because the query is hot: it runs on every stream connect and disconnect, and once per turn when synthesizing tool cancellations. `GetChatMessagesForPromptByChatID` has the same defect and is fixed in the stacked PR, because its compaction boundary change is semantic and deserves a separate review. Auto-archive stays timestamp-based deliberately: it measures activity, not order. Wrapping the insert in a CTE (needed because `INSERT` cannot take `ORDER BY`) makes sqlc synthesize `InsertChatMessagesRow`. It is structurally identical to `ChatMessage`, so the call sites use a direct struct conversion that stops compiling if the two ever diverge. ## Testing Behavior tests write `created_at` values inverted against id order, so a reader that leads with `created_at` returns the batch backwards. All three queries were verified red by reverting the `ORDER BY` and regenerating: the stream query returned `[3,2,1]` for `[1,2,3]`, and `GetLastChatMessageByRole` picked id 1 instead of id 3. `TestInsertChatMessagesOrderContract` asserts against the generated SQL, covering what a behavior test cannot: PostgreSQL evaluates the id default in row order anyway, so a batch still looks ordered once the guarantee is removed. `TestChatMessagesSequenceCacheIsOne` guards the cross-batch half of the invariant. Ids follow chat row lock order only while the sequence hands out one value at a time; sequence cache blocks are per session, so with a cache above one a backend holding stale cached values can lock second and still commit lower ids. Bumping a sequence cache is an ordinary throughput tweak, and it would silently corrupt history order. The index was checked on a 200k row fixture. Without it, the zero-match lookup filters all 200,000 rows over 2763 buffers; with it, the plan is an index scan with both `chat_id` and `role` in the index condition, no sort node, and 3 buffers. Note that the within-batch mapping does not depend on the cache size. It is established by `ROW_NUMBER() OVER (ORDER BY id)` over the allocated ids, so it holds regardless of `nextval` evaluation order. ## Note on the deleted subagent hand-sort The subagent history reader's hand-sort stays deleted, but calling it redundant was imprecise. It sorted by `created_at` then `id`, so it is only equivalent to `id` ordering when the two agree. When they disagree the old code selected a different "latest assistant". This is a deliberate behavior change to match the new invariant, not dead-code removal. > Opened by Mux on behalf of Mike. |
||
|
|
06ceb4253d |
feat: add agent runtime hour license claims and entitlement feature (#27459)
Licenses can now carry three agent runtime hour claims:
`agent_runtime_hours_allocation`, `agent_runtime_hours_limit_soft`, and
`agent_runtime_hours_limit_hard` (unit: hours). They surface as the new
usage-period feature `agent_runtime_hours` in `GET
/api/v2/entitlements`, where `limit` carries the allocation and the new
optional `soft_limit` / `hard_limit` fields on `codersdk.Feature` carry
the thresholds.
Invalid combinations reject the entire license via `validateClaims`
(both at upload and when computing entitlements for stored licenses):
soft/hard without allocation, negative allocation, soft outside `0 <=
soft < allocation`, or `hard < allocation`.
Soft and hard limits are not comparison inputs in `Feature.Compare`;
they ride along with whichever license wins (newest `iat`, existing
behavior). None of the three claim names is a feature name, so old
servers ignore them via the existing unknown-claim tolerance, protecting
rollout of licenses minted with the new claims.
The claim name constants defined in `enterprise/coderd/license` are the
canonical contract for `github.com/coder/license` (X1).
Part of
[CODAGT-837](https://linear.app/codercom/issue/CODAGT-837/a1-agent-runtime-license-claims-and-entitlement-feature).
Blocks B4 (usage wiring + warnings), C1 (hard-limit admission gate), F1
(licenses page), A4 (managed-agent coexistence), X1 (licensor).
Out of scope, handled by follow-up issues: `Actual` usage wiring,
threshold warnings, admission gating, premium defaults, and FE surfacing
beyond regenerated types.
<details>
<summary>Implementation plan and decision log</summary>
## Decisions (confirmed by jaayden, 2026-07-23)
1. **Claim names / unit:**
- `agent_runtime_hours_allocation` - allocation (unit: hours, int64)
- `agent_runtime_hours_limit_soft` - soft limit
- `agent_runtime_hours_limit_hard` - hard limit
- None of the three claim names is itself a `FeatureName`; all three map
to the single new usage-period feature `agent_runtime_hours`
(`FeatureAgentRuntimeHours`), mirroring how `managed_agent_limit_soft`
mapped onto `managed_agent_limit`. Old servers therefore ignore all
three claims via the `FeatureNamesMap` check.
2. **Reject-license.** Invalid claim combinations reject the whole
license via `validateClaims` (upload returns 400 via
`ParseClaimsIgnoreNbf`; already-stored licenses produce an `Invalid
license ... parsing claims` entitlements error and contribute nothing).
## Design notes
- `codersdk.Feature` had a `SoftLimit` field until
|
||
|
|
d072aa7bd0 |
feat(site): confirm before batch stopping workspaces (#27631)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
## What
Bulk stopping workspaces from the workspaces table currently fires
immediately with no confirmation, whereas the single-row **Stop** action
and the bulk **Delete** / **Update** actions all show a dialog first.
This adds a confirmation dialog to the bulk **Stop** action so it
matches the rest.
## How
- Added `BatchStopConfirmation`, a small `ConfirmDialog` wrapper
mirroring the wording of the single-workspace stop confirmation but
pluralized for the selected count.
- Wired it into `WorkspacesPage`: `onBatchStopTransition` now opens the
dialog (`setActiveBatchAction("stop")`) instead of calling
`batchActions.stop(...)` directly, and the actual stop runs on confirm.
Added `"stop"` to the `BatchAction` union.
No change to the underlying `batchActions.stop` behavior (still only
stops `running` workspaces).
<details>
<summary>Reviewer notes</summary>
Before: `onBatchStopTransition={() =>
batchActions.stop(checkedWorkspaces)}` — no confirmation.
After: opens `BatchStopConfirmation`; confirm calls
`batchActions.stop(checkedWorkspaces)` then clears the active action,
consistent with how `BatchDeleteConfirmation` and `BatchUpdateModalForm`
are handled.
</details>
---
_Opened as a draft. Disclosure: this PR was generated by Coder Agents on
behalf of @jakehwll._
|
||
|
|
fbac602456 |
feat!: add admin-controlled dynamic client registration toggle (#27316)
`POST /oauth2/register` (RFC 7591 Dynamic Client Registration) has exactly one gate today: `ExperimentOAuth2`, a static, process-lifetime flag that wraps the entire `/oauth2/*` route tree as an all-or-nothing switch. That flag is scheduled for removal at GA, which would leave DCR with zero admin control at all once it is gone. Add a persistent, DCR-specific `oauth2_dcr_enabled` deployment setting, independent of the experiment system, so admin control over DCR survives GA. `POST /oauth2/register` checks the flag and rejects new registrations with an RFC 7591-shaped `403` when disabled; discovery metadata (`GET /.well-known/oauth-authorization-server`) conditionally omits `registration_endpoint`. A new audited `GET`/`PUT /api/v2/oauth2-provider/settings` endpoint lets an owner toggle it live, no restart required. The setting defaults to disabled, matching the canonical design proposal; disabling only stops new self-registrations, clients that already registered continue to authorize and exchange tokens normally. Address issue described in [ENG-3056](https://linear.app/codercom/issue/ENG-3056/oauth2-dcr-admin-configurable-enabledisable). ## Where this sits in the request path ```mermaid sequenceDiagram autonumber participant A as Admin participant S as coderd participant DB as site_configs<br/>(oauth2_dcr_enabled) participant C as OAuth2/MCP Client Note over A,S: Admin toggles DCR (new) A->>S: PUT /api/v2/oauth2-provider/settings<br/>{dynamic_client_registration_enabled: false} S->>S: authorizeContext(ActionUpdate, ResourceDeploymentConfig) S->>DB: UPSERT oauth2_dcr_enabled = false S-->>A: 200 OK (audited) Note over C,S: Client discovery + registration afterward C->>S: GET /.well-known/oauth-authorization-server S->>DB: GetOAuth2DCREnabled (system ctx, every request, no cache) DB-->>S: false S-->>C: 200 metadata, registration_endpoint omitted C->>S: POST /oauth2/register S->>DB: GetOAuth2DCREnabled (system ctx, every request, no cache) DB-->>S: false S-->>C: 403 invalid_request,<br/>"Dynamic client registration is disabled" Note over C,S: A client that registered before the change is unaffected C->>S: GET /oauth2/authorize?client_id=... Note over S: no DCR-enabled check on this path S-->>C: 200 (proceeds normally) C->>S: PUT/DELETE /oauth2/clients/{client_id} (RFC 7592 self-management) Note over S: no DCR-enabled check on this path either S-->>C: 200 (proceeds normally) ``` ## Files changed: manual vs. generated Reviewers should focus on the **manual** files. The **generated** ones are `make gen` output that follows mechanically from the manual changes and don't need direct review. <details> <summary><b>Manual files (26)</b> — click to expand, grouped the same way as "Suggested review order" below</summary> **1. Database** | File | What changed | |---|---| | `coderd/database/queries/siteconfig.sql` | New `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled` query pair on the existing generic `site_configs` table. No schema change. | | `coderd/database/dbauthz/dbauthz.go` | RBAC check (`rbac.ResourceDeploymentConfig`) on the two new query methods; extends the `subjectSystemOAuth2` system-actor role with read-only `ResourceDeploymentConfig` access, needed so the public discovery/registration endpoints can read the flag via `dbauthz.AsSystemOAuth2`. | | `coderd/database/dbauthz/dbauthz_test.go` | RBAC assertion coverage for `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled` in the method-coverage test suite. | **2. Request gating (the actual feature)** | File | What changed | |---|---| | `coderd/oauth2provider/registration.go` | The actual gate: `CreateDynamicClientRegistration` reads the flag first and returns an RFC 7591-shaped `403` when disabled (defaults disabled if never configured). | | `coderd/oauth2provider/registration_test.go` | New unit test, `TestCreateDynamicClientRegistration_DCREnabled`: calls the handler directly (no HTTP server), covering enabled / explicitly disabled / never-configured. | | `coderd/oauth2provider/metadata.go` | `GetAuthorizationServerMetadata` conditionally omits `registration_endpoint` from discovery metadata when DCR is disabled. | | `coderd/oauth2provider/metadata_test.go` | New unit test, `TestGetAuthorizationServerMetadata_DCREnabled`: same three states, for the discovery handler. | **3. Admin settings endpoint** | File | What changed | |---|---| | `codersdk/oauth2.go` | New `OAuth2ProviderSettings` SDK type plus `Client.OAuth2ProviderSettings`/`PutOAuth2ProviderSettings` methods. | | `coderd/oauth2.go` | New `oauth2ProviderSettings`/`putOAuth2ProviderSettings` admin handlers (audited via `audit.InitRequest`); updates the `GetAuthorizationServerMetadata` call site to pass `api.Database`. | | `coderd/coderd.go` | Registers `GET`/`PUT /api/v2/oauth2-provider/settings`. | | `coderd/oauth2_provider_settings_test.go` | New test file: admin `GET`/`PUT` round-trip, default-disabled-before-any-`PUT`, and `403` for a non-owner on both `GET` and `PUT`. | **4. Audit wiring** | File | What changed | |---|---| | `coderd/database/types.go` | New `database.OAuth2ProviderSettings` audit-only struct (mirrors `NotificationsSettings`). | | `coderd/audit/diff.go` | Adds the new struct to the `Auditable` type union. | | `coderd/audit/request.go` | Adds the new struct to all four dispatch switches (`ResourceTarget`, `ResourceID`, `ResourceType`, `ResourceRequiresOrgID`). | | `codersdk/audit.go` | New API-facing `ResourceTypeOAuth2ProviderSettings` constant and its `FriendlyString` case. | | `enterprise/audit/table.go` | Field-level audit action map (`ActionTrack`/`ActionIgnore`) for the new struct. | | `coderd/database/migrations/000546_audit_oauth2_provider_settings.up.sql` | Adds `oauth2_provider_settings` to the `resource_type` Postgres enum, required for the audit wiring above (`resource_type` is a real enum, not a Go-only value). | | `coderd/database/migrations/000546_audit_oauth2_provider_settings.down.sql` | No-op (`ALTER TYPE ... ADD VALUE` can't be reverted). | **5. Test-suite ripple from the disabled-by-default flip** | File | What changed | |---|---| | `coderd/oauth2provider/oauth2providertest/helpers.go` | New shared test helper, `EnableDCR`, since DCR now defaults to disabled and many pre-existing tests need it turned on to register a client. | | `coderd/oauth2_test.go` | Adds `TestOAuth2DynamicClientRegistrationDisabled` (registers a client, disables DCR, verifies new registration is rejected while the existing client's self-management, authorize, and token exchange all keep working); calls `EnableDCR` in every pre-existing test that registers a client. | | `coderd/oauth2_error_compliance_test.go` | Calls `EnableDCR` in every test that registers a client, so RFC-error-format assertions aren't masked by the new disabled-by-default gate. | | `coderd/oauth2_metadata_validation_test.go` | Same: `EnableDCR` added to every registration-dependent test. | | `coderd/oauth2_security_test.go` | Same. | | `coderd/oauth2provider/validation_test.go` | Same (near-duplicate of `oauth2_metadata_validation_test.go` in a different package). | | `coderd/oauth2provider/provider_test.go` | Same. | | `coderd/mcp/mcp_e2e_test.go` | Same, for the MCP end-to-end dynamic-registration flow test. | </details> <details> <summary><b>Generated files (12)</b> — from <code>make gen</code>, no need to review directly</summary> `coderd/apidoc/docs.go`, `coderd/apidoc/swagger.json`, `coderd/database/dbmetrics/querymetrics.go`, `coderd/database/dbmock/dbmock.go`, `coderd/database/dump.sql`, `coderd/database/models.go`, `coderd/database/querier.go`, `coderd/database/queries.sql.go`, `docs/admin/security/audit-logs.md`, `docs/reference/api/enterprise.md`, `docs/reference/api/schemas.md`, `site/src/api/typesGenerated.ts`. </details> ## Suggested review order ### 1. Database Establishes the persisted setting and its RBAC rule; everything else builds on `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled`. 1. `coderd/database/queries/siteconfig.sql` — the two new queries. Same boolean-encoding pattern as the existing `oauth2_github_default_eligible` key right above them in the same file. 2. `coderd/database/dbauthz/dbauthz.go` — the RBAC wrapper for those two queries, plus the `subjectSystemOAuth2` role extension (search this file for `ResourceDeploymentConfig`, it appears in both spots). 3. `coderd/database/dbauthz/dbauthz_test.go` — asserts the RBAC checks from (2) actually fire. ### 2. Request gating (the actual feature) Where `POST /oauth2/register` and discovery metadata change behavior. 1. `coderd/oauth2provider/registration.go` — the primary gate. Read this first; it's the feature. 2. `coderd/oauth2provider/registration_test.go` — its new unit test, exercising the gate's three states directly against the handler. 3. `coderd/oauth2provider/metadata.go` — the same gating pattern applied to the discovery `GET` endpoint. 4. `coderd/oauth2provider/metadata_test.go` — its new unit test. ### 3. Admin settings endpoint How an owner flips the setting live. 1. `codersdk/oauth2.go` — the `OAuth2ProviderSettings` SDK type and `Client` methods first; this is the public contract everything below implements against. 2. `coderd/oauth2.go` — the `GET`/`PUT` handlers themselves. 3. `coderd/coderd.go` — route registration, to see where those handlers get wired in. 4. `coderd/oauth2_provider_settings_test.go` — round-trip and permission tests. ### 4. Audit wiring Plumbing required so step 3's `PUT` is auditable; mechanical except for (3). 1. `coderd/database/types.go` — the audit-only struct; everything else in this layer exists to plumb it through. 2. `coderd/audit/diff.go` — adds it to the `Auditable` type union (the compiler enforces this one). 3. `coderd/audit/request.go` — the four dispatch switches; the one part of this layer worth reading closely. 4. `codersdk/audit.go` — the API-facing resource type constant. 5. `enterprise/audit/table.go` — the field-action map. 6. `coderd/database/migrations/000546_audit_oauth2_provider_settings.{up,down}.sql` — read last; a consequence of needing a new `resource_type` enum value for (1)-(5), not a design decision of its own. ### 5. Test-suite ripple from the disabled-by-default flip 1. `coderd/oauth2provider/oauth2providertest/helpers.go` — the new `EnableDCR` helper. Read first to understand the fix pattern before seeing it applied repeatedly. 2. `coderd/oauth2_test.go` — next, since it also contains the new `TestOAuth2DynamicClientRegistrationDisabled`, not just `EnableDCR` call sites. 3. The rest, in any order, they're mechanical repeats of the same one-line addition: `coderd/oauth2_error_compliance_test.go`, `coderd/oauth2_metadata_validation_test.go`, `coderd/oauth2_security_test.go`, `coderd/oauth2provider/validation_test.go`, `coderd/oauth2provider/provider_test.go`, `coderd/mcp/mcp_e2e_test.go`. ## Explicitly out of scope Per the design proposal: rate limiting on `POST /oauth2/register` (tracked separately), retroactively affecting already-registered clients when DCR is disabled (this only gates new self-registration), and an Initial Access Token requirement (a separate, follow-up ticket). |
||
|
|
ce769680ba |
feat(site): hide workspace resources when lacking workspace-create permission (#27278)
Context: experiment `minimum-implicit-member ` added the ability to set the default member-roles at a per-organization level. This, along with the related PR stacked listed below, will be used to enable "gateway accounts", which are accounts that are entitled to use the AI Gateway but not create or use workspaces. The Workspaces tab is intentionally left visible for now. Hides the "New workspace" button and the empty-state creation CTA on the Workspaces page for users who cannot create a workspace in any organization, and guards the creation page itself. Adds a shared `createWorkspace` authorization check (`workspace` resource, `create` action, `owner_id: me`, `any_org: true`) to `site/permissions.json` and threads the result through `WorkspacesPageView`, `WorkspacesTable`, and `WorkspacesEmpty`. Users without the permission see an empty state explaining they don't have permission to create workspaces instead of a dead-end CTA. The create CTAs on the Templates pages were already gated by per-organization checks; this brings the Workspaces page in line. `CreateWorkspacePage` is also gated: it adds an org-scoped `createWorkspaceForUserID` check to its existing authorization batch and wraps the view in `RequirePermission`, so a direct URL shows the standard denial dialog instead of a form that 403s on submit. Users who can create workspaces for others (`createWorkspaceForAny`) still see the form. To see this behavior, enable the experiment. As an admin, visit Organization -> Roles, and remove "Organization Workspace Access" from the default roles. Login as a user that is not granted workspace access via a member role. Storybook coverage: `CannotCreateWorkspace` (empty state + hidden button), `CannotCreateWorkspaceWithWorkspaces` (button hidden while the table renders), `CannotCreateWorkspaceWithFilter` (pins the filter empty state's priority over the no-permission one), and `PermissionDenied` for the CreateWorkspacePage gate. The Go SSR permissions test also asserts the new `createWorkspace` entry. ## Stack This PR is independent but related to the gateway-accounts stack: 1. **#27279**: permission-based license seat counting. Behind the `permission-based-licensing` experiment and gated on the AI Governance add-on, `user_limit` counts only users the RBAC engine authorizes to create workspaces. 2. **#27280**: adds the `organization-ai-gateway-access` org role carrying the AI Bridge interception permissions (extracted from the member floors, backfilled into org default roles by migration) and enforces it at AI Gateway authentication; bridge usage stops claiming AI Governance seats under the experiment. 3. ~~**#27281**: gates workspace ACL grants on matching member-level capability (each granted action only takes effect while the recipient holds that action in the org), so workspace sharing is ineffective for (and rejected toward) users without workspace capabilities, evaluated live on every authorization.~~ Tabled - excluded from the gateway-accounts MVP. This PR (#27278) stands alone: it hides the Workspaces page create CTAs for users without workspace-create permission and can merge in any order. |
||
|
|
efbf802319 |
feat: add bulk secret import upload to Add secret dialog (PLAT-240) (#26725)
Adds a file dropzone to the create branch of the Add secret dialog (final PR in the PLAT-240 stack, after #26723 and #26724). The browser reads the file, derives the format from the extension (`.env`/`.json`/`.yaml`/`.yml`), and imports via `POST /secrets/batch`; per-entry backend errors surface in an alert and the success toast flags secrets imported without an env name. Storybook play stories and vitests cover the flow. Also documents the upload flow in `docs/user-guides/user-secrets.md`. Closes https://linear.app/codercom/issue/PLAT-240 > Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder. |
||
|
|
0b2a6cac78 |
feat: add coder secret import for bulk secret files (#27534)
Adds `coder secret import <file>` to bulk-import dotenv, JSON, or YAML secrets through the existing batch API. The command infers the format from the extension or accepts `--input-format`, supports non-interactive stdin, validates files locally before upload, and warns when imported keys cannot be injected as environment variables. Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder. |
||
|
|
1a6a8be96c |
feat: log tailnet tunnels to the connection log (#27423)
Co-authored-by: Chris DiGiamo <cd@anthropic.com> Co-authored-by: Chris DiGiamo <cdigiamo@anthropic.com> |
||
|
|
8cc7f2bb0e |
fix(coderd): reject workspace proxy hostname prefixes (#27544)
A workspace proxy hostname prefix could be accepted as a valid proxy access URL. An authenticated user could then be redirected to an attacker-controlled domain with an application-connect API key in the URL. Require proxy access URL matches to have a hostname boundary after the candidate hostname, allowing only the end of the URL, a port, or a path. Add regression coverage for proxy access URL and wildcard hostname prefixes. Refs: https://linear.app/codercom/issue/PLAT-384 --------- Co-authored-by: Bobby Ho <bobbidinho@gmail.com> |
||
|
|
09a69e624a |
feat: search users by display name (#27398)
Free-text member search previously matched only username and email, so typing a person's display name returned no results even though the UI shows the display name as the primary label. This broadens the free-text `@search` filter to also match `users.name`. The change is in three queries: `GetUsers`, `PaginatedOrganizationMembers`, and `GetGroupMembersByGroupIDPaginated`. This covers every server-filtered surface: the Users page, the Organization Members page, the Group Members page, and the `UserAutocomplete` / `WorkspaceUserAutocomplete` pickers (which query `GetUsers` with `q`). The org member picker (`MemberAutocomplete`) filters client-side via cmdk, so display name is added to its `keywords`. Explicit filters (`name:`, `username`/`email`) and pagination counts are unchanged; the group members count still comes from the filtered `COUNT(*) OVER()` in the same query. Refs DEVEX-484 Refs DEVEX-565 <details> <summary>Implementation plan</summary> ## Problem Member search (both the global Users page and the Organization Members page) matches only on `username` and `email`. It does not match on the user's display name (`users.name`), even though the Organization Members table shows `name` as the primary title. So typing a person's full name in the search box returns nothing. Today a bare search term (`alice`) is routed to the SQL `@search` filter, which only checks `email`/`username`. Display name is only matched if the user explicitly types `name:alice`, which is undiscoverable. ## Design decision Include `name` in the free-text `@search` condition in the affected SQL queries. A bare term then matches `email OR username OR name`, using the same case-insensitive substring `ILIKE` already in place. This keeps the existing explicit `name:` filter working. Tradeoff: this broadens the meaning of free-text `search` globally (anything using these queries now also matches display name). This is the intended behavior, confirmed against DEVEX-565 (display name search in the user picker). ## Affected files Backend: - `coderd/database/queries/users.sql` (`GetUsers`) - `coderd/database/queries/organizationmembers.sql` (`PaginatedOrganizationMembers`) - `coderd/database/queries/groupmembers.sql` (`GetGroupMembersByGroupIDPaginated`) - `coderd/database/queries.sql.go` regenerated via `make gen` Frontend: - `site/src/components/UserAutocomplete/UserAutocomplete.tsx` (add `name` to client-side cmdk keywords) Tests: - `coderd/coderdtest/users.go` (shared `UsersFilter` helper): added a `DisplayNameSearch` case and extended search-based expectations to include `name`. Exercised by `TestGetUsersFilter`, `TestGetOrgMembersFilter`, and `TestGetGroupMembersFilter`. Docs: - `docs/admin/users/index.md`: documented that free-text search matches username, email, and display name. ## Frontend surface coverage | Surface | Sends | Backend | Query | |---|---|---|---| | Users page | `q` | `GET /users` | `GetUsers` | | Organization Members page | `q` | paginated members | `PaginatedOrganizationMembers` | | Group Members page | `q` | `groupMembers` | `GetGroupMembersByGroupIDPaginated` | | User pickers (server-filtered) | `q` | `GET /users` | `GetUsers` | | Org member picker (client-filtered) | local cmdk | n/a | keyword change | ## Out of scope - Trigram/similarity (fuzzy) matching; keeps `ILIKE` substring semantics. - Sort/pagination ordering (still `LOWER(username)`). </details> --- _Created by Coder Agents on behalf of @aqandrew._ |
||
|
|
e657d2ab9d | chore(scaletest/prebuilds): reduce workspace poll interval to 5s (#27548) | ||
|
|
206938154a | chore: remove emyrk from coderowners of commonly touched rbac (#27596) | ||
|
|
75fd7bc09a |
fix: remove chatd usage limit enforcement (#27535)
This PR surgically removes enforcement of Agents spend limits: - Adjusts the relevant function that checks usage to always return nil - Deletes tests that expect a usage limit error. |
||
|
|
eb905702c8 |
fix(coderd/util/syncmap): match sync.Map semantics in the typed wrapper (#27582)
Fixes CODAGT-869 |
||
|
|
85984ff142 |
feat: add enable/disable support for user secrets (#27537)
Users can now disable a secret to stop it from being injected into workspaces without deleting it, and re-enable it later. Disabled secrets stay visible and editable everywhere they already appear. An enabled secret must have at least one injection target; a secret with no target can be stored only while disabled. Existing target-less secrets are migrated to disabled to preserve current behavior. Support spans the REST API, SDK, CLI, dashboard, and audit log. |
||
|
|
3c61a9a939 |
chore(docs): update release docs for v2.34.7 (#27591)
Automated docs update for v2.34.7 release. Created by `releasetui`. |
||
|
|
be226409b8 | fix: delete the unused ChatMessagePart.Signature field (#27588) | ||
|
|
5f72c1525e |
chore(site): demui <ScheduleForm /> component (#27563)
This pull-request deMUIs the `/settings/schedule` page for users. | Old | New | | --- | --- | | <img width="1041" height="371" alt="PREVIEW_QUIET_HOURS_OLD" src="https://github.com/user-attachments/assets/15e1e285-a33e-475e-beab-924a53152e00" /> | <img width="1041" height="406" alt="PREVIEW_QUIET_HOURS_NEW" src="https://github.com/user-attachments/assets/27daa080-b3e9-4615-9ad3-eefa0d55295b" /> | |
||
|
|
448fe10d82 |
fix: remove @mui/material from formUtils.stories.tsx (#27503)
This pull-request removes the material dependency from
`formUtils.stories.tsx`. Just a lil bit of dead code we can clean up 🙂.
|
||
|
|
8ea2586189 |
feat: add chat lifecycle hook dispatch backend (#27401)
Adds the chat lifecycle hook wire contract and dispatch plumbing, first PR of the lifecycle hooks stack (followed by #27428, #27429, #27430). - `codersdk/x/agenthooks`: event and response wire types, JWT creation and verification with the shared secret (HS256, request body digest, expiry and not-before freshness checks), and an HTTP handler helper so consumers only implement the events they use. The `codersdk/x` location marks the consumer SDK as experimental. - `coderd/x/agenthooks/dispatch`: a stateless dispatcher that signs and posts hook events, enforces a concurrency cap under one configured timeout that bounds both the capacity wait and both post attempts, retries one connection failure with the same JWT, sends a distinctive `coderd-agenthooks/<version>` User-Agent, and records Prometheus metrics. Delivery is at least once; consumers own durable decision state, audit records, and deduplication keyed by the stable payload identifiers. Nothing is persisted by Coder. - Response bodies decode strictly: unknown fields, duplicate JSON keys (including inside `input_override`), and trailing data fail the dispatch closed as protocol errors instead of silently reading as allow. - `coderd/util/xnet`: shared timeout and connection error classification used by the dispatcher retry logic. Transient HTTP/2 stream aborts count as connection errors, so the documented single retry also applies to h2 consumers, which is the shape Go's default transport negotiates against any TLS consumer. Deterministic protocol failures stay terminal. Only the struct form of a stream error is matched, because `net/http` bundles its own HTTP/2 types and `h2_error.go` bridges only that shape. - `scripts/agenthooks-server`: a reference consumer that logs events and demonstrates consumer-owned pre-tool decision deduplication. It requires an explicitly configured JWT audience rather than deriving one from the request, and its startup output names the mode it is running in so an operator can see that the example policy flags need `-log-only=false`. - `scripts/apitypings`: generate TypeScript types for the hook wire contract. Dispatch failures log without the error's stack frames, since a failed dispatch is an expected, operator-visible condition. Nothing dispatches these events yet; chatd wiring lands in #27429. > This PR was written by Mux, an AI coding agent, on Mike's behalf. |
||
|
|
bd5d640f1e |
fix(coderd/database/migrations): resolve duplicate 000554 migration collision (#27581)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. ## Problem `main` currently has **two migrations sharing version `000554`**: - `000554_aibridge_token_usage_spend_export_index.{up,down}.sql` - `000554_legacy_none_login_to_password.{up,down}.sql` (from #26851) Both merged around the same time. #26851 was renumbered to `000554` when `000553` was the latest, but `aibridge_token_usage_spend_export_index` claimed `000554` and merged too, leaving a duplicate version number on `main`. Duplicate migration versions break the migration sequence. ## Fix Renumber the legacy none login migration to the next free slot, `000555`, leaving the aibridge migration at `000554`: - `000554_legacy_none_login_to_password.{up,down}.sql` -> `000555_legacy_none_login_to_password.{up,down}.sql` - `migrate_test.go`: `TestMigration000554...` -> `TestMigration000555...`, `priorMigrationVersion` `553` -> `554`, and the `os.ReadFile` filename. The migration is data-only and unchanged; only its version number moves. `TestMigration000555LegacyNoneLoginToPassword` passes locally. The same collision exists on `release/2.36` via the backport (#27578), which has been renumbered to `000555` to match. Resolves [DEVEX-226] follow-up. [DEVEX-226]: https://linear.app/issue/DEVEX-226 |
||
|
|
2dc850d085 |
chore: bump github.com/valyala/fasthttp from 1.72.0 to 1.73.0 (#27575)
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.72.0 to 1.73.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/valyala/fasthttp/releases">github.com/valyala/fasthttp's releases</a>.</em></p> <blockquote> <h2>v1.73.0</h2> <h2>What's Changed</h2> <ul> <li>test: fix host comparison in FuzzURIParse by <a href="https://github.com/ReneWerner87"><code>@ReneWerner87</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2313">valyala/fasthttp#2313</a></li> <li>perf: avoid redundant scans when parsing request headers by <a href="https://github.com/ReneWerner87"><code>@ReneWerner87</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2312">valyala/fasthttp#2312</a></li> <li>Fix temp file leak in SaveMultipartFile on cross-device rename failure by <a href="https://github.com/itxaiohanglover"><code>@itxaiohanglover</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2311">valyala/fasthttp#2311</a></li> <li>re-enable forcetypeassert by <a href="https://github.com/Harshal96"><code>@Harshal96</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2316">valyala/fasthttp#2316</a></li> <li>test: normalize Go test names by <a href="https://github.com/Harshal96"><code>@Harshal96</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2317">valyala/fasthttp#2317</a></li> <li>feat: prefix sentinel error strings by <a href="https://github.com/Harshal96"><code>@Harshal96</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2319">valyala/fasthttp#2319</a></li> <li>fix(pprofhandler): use exact path matching to prevent debug data exposure by <a href="https://github.com/xbrxr03"><code>@xbrxr03</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2302">valyala/fasthttp#2302</a></li> <li>avoid following a symlink when writing the FS compressed cache by <a href="https://github.com/alhudz"><code>@alhudz</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2321">valyala/fasthttp#2321</a></li> <li>refactor: improve internal interface names by <a href="https://github.com/Harshal96"><code>@Harshal96</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2318">valyala/fasthttp#2318</a></li> <li>fix: lowercase error strings by <a href="https://github.com/Harshal96"><code>@Harshal96</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2320">valyala/fasthttp#2320</a></li> <li>perf: reduce redundant scans and allocations in hot paths by <a href="https://github.com/ReneWerner87"><code>@ReneWerner87</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2322">valyala/fasthttp#2322</a></li> <li>validate domain and path cookie attribute values on parse by <a href="https://github.com/alhudz"><code>@alhudz</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2315">valyala/fasthttp#2315</a></li> <li>chore(deps): bump securego/gosec from 2.27.1 to 2.28.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2328">valyala/fasthttp#2328</a></li> <li>chore(deps): bump actions/setup-go from 6 to 7 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2329">valyala/fasthttp#2329</a></li> <li>fix: preserve pre-set status code in NewFastHTTPHandler by <a href="https://github.com/xbrxr03"><code>@xbrxr03</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2323">valyala/fasthttp#2323</a></li> <li>chore(deps): bump github.com/klauspost/compress from 1.19.0 to 1.19.1 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2331">valyala/fasthttp#2331</a></li> <li>reject backslash '..' traversal in fs handler on windows by <a href="https://github.com/alhudz"><code>@alhudz</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2327">valyala/fasthttp#2327</a></li> <li>fix: reject Windows alternate data stream paths in FS by <a href="https://github.com/dev-willbird1936"><code>@dev-willbird1936</code></a> in <a href="https://redirect.github.com/valyala/fasthttp/pull/2335">valyala/fasthttp#2335</a></li> <li>chore(deps): bump golangci/golangci-lint-action from 9.2.1 to 9.3.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2307">valyala/fasthttp#2307</a></li> <li>chore(deps): bump github.com/andybalholm/brotli from 1.2.1 to 1.2.2 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2308">valyala/fasthttp#2308</a></li> <li>chore(deps): bump github.com/klauspost/compress from 1.18.6 to 1.18.7 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2309">valyala/fasthttp#2309</a></li> <li>chore(deps): bump github.com/klauspost/compress from 1.18.7 to 1.19.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2314">valyala/fasthttp#2314</a></li> <li>chore(deps): bump golang.org/x/sys from 0.46.0 to 0.47.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2326">valyala/fasthttp#2326</a></li> <li>chore(deps): bump golang.org/x/crypto from 0.53.0 to 0.54.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2324">valyala/fasthttp#2324</a></li> <li>chore(deps): bump golang.org/x/net from 0.56.0 to 0.57.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/valyala/fasthttp/pull/2325">valyala/fasthttp#2325</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/itxaiohanglover"><code>@itxaiohanglover</code></a> made their first contribution in <a href="https://redirect.github.com/valyala/fasthttp/pull/2311">valyala/fasthttp#2311</a></li> <li><a href="https://github.com/Harshal96"><code>@Harshal96</code></a> made their first contribution in <a href="https://redirect.github.com/valyala/fasthttp/pull/2316">valyala/fasthttp#2316</a></li> <li><a href="https://github.com/dev-willbird1936"><code>@dev-willbird1936</code></a> made their first contribution in <a href="https://redirect.github.com/valyala/fasthttp/pull/2335">valyala/fasthttp#2335</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/valyala/fasthttp/compare/v1.72.0...v1.73.0">https://github.com/valyala/fasthttp/compare/v1.72.0...v1.73.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/valyala/fasthttp/commit/f83ac8c3560feccaa017ba86dd9b95ad004790f2"><code>f83ac8c</code></a> Optimize bounds checks in hot paths</li> <li><a href="https://github.com/valyala/fasthttp/commit/d9babd95c5f3d26ff26cfc528ca77f165df2f95f"><code>d9babd9</code></a> fix: reject Windows alternate data stream paths in FS (<a href="https://redirect.github.com/valyala/fasthttp/issues/2335">#2335</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/e7cf856857a46388b9e03507b8d4b6d5bc3d69dd"><code>e7cf856</code></a> reject backslash '..' traversal in fs handler on windows (<a href="https://redirect.github.com/valyala/fasthttp/issues/2327">#2327</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/165a4c81aada474f8566a66a5d4c2dee35fee37e"><code>165a4c8</code></a> Improve AppendUnquotedArg with optimised bounds check</li> <li><a href="https://github.com/valyala/fasthttp/commit/7a1349da70685cce500515263c20ec389f8f69e1"><code>7a1349d</code></a> chore(deps): bump github.com/klauspost/compress from 1.19.0 to 1.19.1 (<a href="https://redirect.github.com/valyala/fasthttp/issues/2331">#2331</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/f1ad91d51977febd2730b9e0d79c7d241c5e430c"><code>f1ad91d</code></a> fix: preserve pre-set status code in NewFastHTTPHandler (<a href="https://redirect.github.com/valyala/fasthttp/issues/2323">#2323</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/5f57d8fda4c2c9e3092f82e13f589a92ed48d347"><code>5f57d8f</code></a> chore(deps): bump actions/setup-go from 6 to 7 (<a href="https://redirect.github.com/valyala/fasthttp/issues/2329">#2329</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/3aa940dfd248799878cd9ef46f6dfcec89a37f8d"><code>3aa940d</code></a> chore(deps): bump securego/gosec from 2.27.1 to 2.28.0 (<a href="https://redirect.github.com/valyala/fasthttp/issues/2328">#2328</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/7a012e987a372bf40d3eedf6caeb2f4c6162ff40"><code>7a012e9</code></a> validate domain and path cookie attribute values on parse (<a href="https://redirect.github.com/valyala/fasthttp/issues/2315">#2315</a>)</li> <li><a href="https://github.com/valyala/fasthttp/commit/9cf733f7dc3533354eb6d495fcf0af93bdcb2f03"><code>9cf733f</code></a> perf: reduce redundant scans and allocations in hot paths (<a href="https://redirect.github.com/valyala/fasthttp/issues/2322">#2322</a>)</li> <li>Additional commits viewable in <a href="https://github.com/valyala/fasthttp/compare/v1.72.0...v1.73.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
300ad74f05 |
chore: bump github.com/aws/aws-sdk-go-v2 from 1.42.1 to 1.43.0 (#27576)
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.42.1 to 1.43.0. <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4fef3455fe2dcb5ea3de4e9fbacf889b84c8a255"><code>4fef345</code></a> Release 2026-07-21</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/62754193b1dfd903e741ad656a3e43cda43e3d6c"><code>6275419</code></a> Regenerated Clients</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/f8598305ac9db1544afae331c09df396ee2f7b3e"><code>f859830</code></a> Update API model</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/278591d8fcdcb1f3ec22c3562dcd648b169f4a31"><code>278591d</code></a> Add an option to clients to disable clock skew (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/3483">#3483</a>)</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/d132ac727d15f1c7ff251e0b6739c0e9362ae322"><code>d132ac7</code></a> Fix Clock Skew according to internal specification (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/3472">#3472</a>)</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/03519c98d97b40b31ab42755e4bc6d26e39af5d6"><code>03519c9</code></a> Release 2026-07-20</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/dda3efb63a49f64351f314aa8b6266dee9efbfc2"><code>dda3efb</code></a> Regenerated Clients</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/348cec09e4e0e856665f55f5e548135388a4ef25"><code>348cec0</code></a> Update API model</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/f4fd2723ed647078bced4bdf4b83e6a61c379546"><code>f4fd272</code></a> Release 2026-07-17</li> <li><a href="https://github.com/aws/aws-sdk-go-v2/commit/8e4cbc854bca2c82b351089583123964fb4f392a"><code>8e4cbc8</code></a> Regenerated Clients</li> <li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/v1.42.1...v1.43.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
5b1fdc9d5e |
chore: bump google.golang.org/api from 0.289.0 to 0.290.0 (#27573)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.289.0 to 0.290.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/googleapis/google-api-go-client/releases">google.golang.org/api's releases</a>.</em></p> <blockquote> <h2>v0.290.0</h2> <h2><a href="https://github.com/googleapis/google-api-go-client/compare/v0.289.0...v0.290.0">0.290.0</a> (2026-07-20)</h2> <h3>Features</h3> <ul> <li><strong>all:</strong> Auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3661">#3661</a>) (<a href="https://github.com/googleapis/google-api-go-client/commit/dea11c224ee6b69cc88ebd03460e95cf11f8733c">dea11c2</a>)</li> <li><strong>all:</strong> Auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3663">#3663</a>) (<a href="https://github.com/googleapis/google-api-go-client/commit/93d30d482d74cf2f49ada20c06fcdfa9ceb0ed87">93d30d4</a>)</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md">google.golang.org/api's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/googleapis/google-api-go-client/compare/v0.289.0...v0.290.0">0.290.0</a> (2026-07-20)</h2> <h3>Features</h3> <ul> <li><strong>all:</strong> Auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3661">#3661</a>) (<a href="https://github.com/googleapis/google-api-go-client/commit/dea11c224ee6b69cc88ebd03460e95cf11f8733c">dea11c2</a>)</li> <li><strong>all:</strong> Auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3663">#3663</a>) (<a href="https://github.com/googleapis/google-api-go-client/commit/93d30d482d74cf2f49ada20c06fcdfa9ceb0ed87">93d30d4</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/googleapis/google-api-go-client/commit/b1b12e7c28c287baa23edf8941b2f4a8071c97a9"><code>b1b12e7</code></a> chore(main): release 0.290.0 (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3662">#3662</a>)</li> <li><a href="https://github.com/googleapis/google-api-go-client/commit/153c78e85b81c1a98919bc8abf26a3055996d5ce"><code>153c78e</code></a> chore: expand CODEOWNERS (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3664">#3664</a>)</li> <li><a href="https://github.com/googleapis/google-api-go-client/commit/93d30d482d74cf2f49ada20c06fcdfa9ceb0ed87"><code>93d30d4</code></a> feat(all): auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3663">#3663</a>)</li> <li><a href="https://github.com/googleapis/google-api-go-client/commit/dea11c224ee6b69cc88ebd03460e95cf11f8733c"><code>dea11c2</code></a> feat(all): auto-regenerate discovery clients (<a href="https://redirect.github.com/googleapis/google-api-go-client/issues/3661">#3661</a>)</li> <li>See full diff in <a href="https://github.com/googleapis/google-api-go-client/compare/v0.289.0...v0.290.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
fa15461d67 |
chore: bump github.com/coder/terraform-provider-coder/v2 from 2.18.0 to 2.19.0 (#27574)
Bumps [github.com/coder/terraform-provider-coder/v2](https://github.com/coder/terraform-provider-coder) from 2.18.0 to 2.19.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/coder/terraform-provider-coder/releases">github.com/coder/terraform-provider-coder/v2's releases</a>.</em></p> <blockquote> <h2>v2.19.0</h2> <h2>What's Changed</h2> <ul> <li>chore: add check-latest to setup-go for reliable Go version resolution by <a href="https://github.com/denisra"><code>@denisra</code></a> in <a href="https://redirect.github.com/coder/terraform-provider-coder/pull/529">coder/terraform-provider-coder#529</a></li> <li>build(deps): Bump golang.org/x/mod from 0.36.0 to 0.38.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/coder/terraform-provider-coder/pull/524">coder/terraform-provider-coder#524</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/denisra"><code>@denisra</code></a> made their first contribution in <a href="https://redirect.github.com/coder/terraform-provider-coder/pull/529">coder/terraform-provider-coder#529</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/coder/terraform-provider-coder/compare/v2.18.0...v2.19.0">https://github.com/coder/terraform-provider-coder/compare/v2.18.0...v2.19.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/coder/terraform-provider-coder/commit/fd21951c3e89b52658e58361388540bbd97cd2c2"><code>fd21951</code></a> build(deps): Bump golang.org/x/mod from 0.36.0 to 0.38.0 (<a href="https://redirect.github.com/coder/terraform-provider-coder/issues/524">#524</a>)</li> <li><a href="https://github.com/coder/terraform-provider-coder/commit/756e3a855206aec7151c1bc6f132a259533f7bb6"><code>756e3a8</code></a> chore: add check-latest to setup-go for reliable Go version resolution (<a href="https://redirect.github.com/coder/terraform-provider-coder/issues/529">#529</a>)</li> <li>See full diff in <a href="https://github.com/coder/terraform-provider-coder/compare/v2.18.0...v2.19.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0e104f38e0 |
fix!: deprecate login_type=none, convert existing users to password login (#26851)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell. Deprecates `login_type=none` (legacy passwordless machine users) in favour of premium **service accounts**, and migrates existing accounts off the deprecated path while preserving their identity. Resolves [DEVEX-226]. ## What this does - **Creation is gated** — `POST /users` and `coder users create` reject `login_type=none` (and the deprecated `--disable-login`) unless a service account is requested. - **Existing users are converted** — migration `000554_legacy_none_login_to_password` rewrites legacy non-system, non–service-account `login_type='none'` accounts to `login_type='password'`. Email addresses are **preserved** and existing API tokens remain valid. Admins can set a password if interactive login is desired. ## Why convert to `password` and not `is_service_account`? Migration `000433_add_is_service_account_to_users` adds two CHECK constraints: - `users_email_not_empty`: `(is_service_account = true) = (email = '')` - `users_service_account_login_type`: `is_service_account = false OR login_type = 'none'` Turning a real, email-bearing `login_type=none` user into a service account would require **blanking their email**. Converting to `password` instead preserves the account and its email. > ⚠️ **Breaking / one-way.** The `down` migration cannot restore which users originally had `login_type='none'`. Decision log - **Goal:** move existing `login_type=none` users off the deprecated path while preserving their identity/email. - **Constraint discovered:** the `is_service_account` CHECK constraints (migration `000433`) make a literal `none → service account` conversion require blanking emails, so this PR converts to `password` instead to keep emails intact. - **Implementation:** creation-gating in `cli/usercreate.go` and `coderd/users.go`, matching test updates, plus the `000554_legacy_none_login_to_password.{up,down}.sql` migration. - **CI fix:** the branch was behind `main` and its migration originally numbered `000534`, which collided with main's `000534_drop_chat_model_configs_provider`. Merged `main` and renumbered to `000554` (next free after main's `000553`). `make gen` produces no drift (the migration is data-only). > The service-account conversion alternative (#27182, which blanked emails) was closed in favour of this password-preserving approach. > > Docs follow-up: #27333. [DEVEX-226]: https://linear.app/issue/DEVEX-226 --------- Co-authored-by: Sushant P <zenithwolf1000@users.noreply.github.com> |
||
|
|
ed37483ff7 |
feat: add group AI spend endpoint (#27568)
## Description
Adds `GET /api/v2/groups/{group}/ai/spend`, returning the AI spend limit
and aggregate spend for a single group over the current budget period.
The period is derived from the deployment's configured budget period
rather than being caller-specified, matching the other AI spend
endpoints.
## Changes
- Add the `groupAISpend` handler and route, gated by the
`aigateway-cost-control` experiment and the `AIBridge` feature.
- Reuse the existing `GetOrganizationGroupsAISpend` query with a single
group ID, so no new query or authorization path is introduced.
- Add the `GroupAISpend` codersdk type and client method.
Closes
https://linear.app/codercom/issue/AIGOV-475/implement-apiv2groupsgroupaispend
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
|
||
|
|
e83f018f5f |
feat(site/src/pages/AISettingsPage/SpendPage): announce cost controls move to AI Governance (#27543)
Adds an informational banner to the AI settings **Spend** tab announcing that cost controls features move to AI Governance in v2.37, with a link to the AI Gateway cost controls docs. Banner copy: > Cost controls features will move to AI Governance in v2.37. [Read more here](https://coder.com/docs/ai-coder/ai-gateway/cost-controls) The link uses the existing `docs()` helper from `#/utils/docs`, so it resolves the deployment's configured `docs-url` meta tag and otherwise falls back to a version-pinned `coder.com/docs/@<version>` URL. This matches how sibling AI settings pages link out (for example `GatewayKeysPageView` and `ProvidersPageView`). It points at `/ai-coder/ai-gateway/cost-controls`, the AI Gateway Cost Controls page added in #27570. That page is present on `main` and the URL resolves, so the banner links to live documentation. The banner renders on the main Spend tab. It is intentionally not shown in the per-user spend drill-in sub-view, which returns early from a separate component. ## Validation - Extended the existing `SpendWithLimitsAndUsers` story to assert the banner copy and the resolved docs `href`. Both assertions were verified to fail when the banner is removed and when the link points somewhere else. - Storybook story tests for `SpendPageView.stories.tsx` pass (12 tests), plus `tsc -p .`, `biome check`, and `lint:compiler` clean. - `make pre-commit` passed through the git hooks. > Mux opened this PR on Mike's behalf. |
||
|
|
e96e7cfec2 |
docs(docs): add AI Gateway cost controls placeholder page (#27570)
## Summary Adds a placeholder "Cost Controls" page under AI Gateway in the docs, plus its `manifest.json` navigation entry. This is a stub with a title only; the full content will be written in a follow-up. Relates to [AIGOV-476](https://linear.app/codercom/issue/AIGOV-476/add-documentation-for-ai-bridge-cost-controls). Related to [internal slack thread](https://codercom.slack.com/archives/C096PFVBZKN/p1785150528587409). ## Changes - Add `docs/ai-coder/ai-gateway/cost-controls.md` placeholder page - Register the page in `docs/manifest.json` under AI Gateway (after Monitoring) --- > [!NOTE] > This PR was generated with Coder Agents. |
||
|
|
c3895ff9c0 |
feat: add CSV export for AI spend data (#27491)
## Description
Adds `GET /api/v2/organizations/{organization}/ai/spend/export`,
returning `text/csv` with per-user, per-group, per-model, per-provider
aggregated AI spend. The data is built from the raw AI Gateway token
usage tables rather than the `ai_user_daily_spend` rollup, but stays
consistent with it: spend is attributed through the token usage's
effective group and bucketed by the token usage `created_at`, the same
values the daily rollup derives from.
The period defaults to the current UTC month, narrowed to the configured
AI Gateway retention window when the month begins before retained data
does. Explicit `period_start`/`period_end` params must be provided
together, are interpreted as UTC, and may span at most 31 days. Unlike
the default period, an explicit period that begins before the retention
window is rejected rather than narrowed. Every row echoes the applied
bounds, so a narrowed window is visible in the export.
The endpoint requires organization-level admin permissions.
## Changes
- Add the `ExportOrganizationAISpend` query aggregating
`aibridge_token_usages` joined to `aibridge_interceptions`, scoped to
the organization via the effective group, resolving the username, group
name, and organization name alongside their IDs.
- Add the `exportOrganizationAISpend` handler and route, gated by the
`aigateway-cost-control` experiment and the `AIBridge` feature,
returning the CSV in a single response.
- Add the `ExportOrganizationAISpend` codersdk client method.
- Require organization-wide `ResourceGroupMember` read, since the export
aggregates every user in the organization. The per-row filter stays in
`dbauthz` as defence in depth.
- Escape leading formula characters in the free-text columns, so a model
or provider name recorded from an intercepted request cannot be
evaluated when the CSV is opened in a spreadsheet.
- Add an index on `aibridge_token_usages (effective_group_id,
created_at)`, which the period and group predicates otherwise cannot
use.
Closes
https://linear.app/codercom/issue/AIGOV-293/add-csv-export-for-ai-spend-data
> [!NOTE]
> Generated by Coder Agents on behalf of @ssncferreira
|
||
|
|
16cadcf2c8 |
chore: raise Biome max file size so swagger.json stays formatted (#27569)
Raises Biome's max file size so `coderd/apidoc/swagger.json` keeps
getting formatted.
The raw swaggo output sits just under Biome's 1 MiB default, so adding a
new endpoint tips it over. Biome then skips the file with an info rather
than an error, which means `scripts/biome_format.sh` and `make gen` both
exit 0 while silently leaving it unformatted. Any PR that introduces a
new endpoint ends up with a large spurious diff in `swagger.json` and a
failing `gen` check.
This is what `make gen` prints when it happens:
```
/home/coder/coder/_gen/tmp.NVD90urqoA/swagger.json format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
i The size of the file is 1.0 MiB, which exceeds the configured maximum of 1.0 MiB for this project.
Use the `files.maxSize` configuration to change the maximum size of files processed, or `files.includes` to ignore the file.
Formatted 0 files in 3ms. No fixes applied.
Found 1 info.
format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× No files were processed in the specified paths.
i Check your biome.json or biome.jsonc to ensure the paths are not ignored by the configuration.
i These paths were provided but ignored:
- /home/coder/coder/_gen/tmp.NVD90urqoA/swagger.json
```
Generated output is unchanged on main, so this is config only.
|
||
|
|
ea2ad4d6c6 |
chore(site): premium paywall for <WorkspaceProxyPage /> (#27567)
|
||
|
|
c351280a37 |
feat: add Prometheus metrics for AI Governance cost control (#27490)
## Description Adds Prometheus metrics for AI budget cost control, emitted by the aibridged server under the `cost_control` subsystem (full names are prefixed `coder_ai_gateway_`). - `blocked_requests_total` (counter) — labels: `group_id` - `blocked_users` (gauge) — labels: `group_id` - `unpriced_requests_total` (counter) — labels: `provider`, `model` - `enforcement_duration_seconds` (histogram) — labels: `outcome` ## Changes - Add `GetOverBudgetUsersPerGroup` query (plus dbauthz/dbmetrics/dbmock wiring) to count over-budget users per effective group. - Add a background collector that refreshes the `blocked_users` gauge on an interval, started only when Prometheus is enabled. - Wire `Metrics` through the aibridged server, coderd API, `cli/server.go`, and the enterprise AI gateway handler; recording is nil-safe when metrics are unset. Closes https://linear.app/codercom/issue/AIGOV-296/add-prometheus-metrics-for-cost-control > [!NOTE] > Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira |
||
|
|
bfcfb71860 |
fix: show 'Unset' for missing providers in AI models list (#27400)
## Summary
Frontend-only fixes for the `/ai/settings/models` page:
1. **Provider column displays "Unset"** with an info tooltip when a
model's provider has been deleted, instead of "N/A".
2. **Models without a usable provider display as "Disabled"** in the
list, regardless of the stored `enabled` flag. Covers both missing
(soft-deleted) and disabled providers.
3. **Save button re-enabled when only the provider changes** on the edit
page (previously the button stayed disabled because provider changes
lived outside the formik state).
## Scope
Frontend only. The DB constraint
`chat_model_configs_ai_provider_required_when_active` already prevents a
non-deleted model from having a NULL `ai_provider_id`; CODAGT-709
addresses the server-side cascade when a provider is deleted.
## Changes
- `ModelsPageView.tsx`: two `useMemo` maps (`hasProviderByModelId`,
`providerEnabledByModelId`) passed to `ModelRow`.
- `ModelRow.tsx`: `isEffectivelyEnabled = model.enabled && hasProvider
&& providerEnabled`. When `hasProvider` is false, renders "Unset" with a
standard `InfoIcon` tooltip.
- `ModelForm.tsx`: `canSubmit` OR's in `hasProviderChange` so the save
button enables when only the provider dropdown changes.
- `ModelRow.stories.tsx`: four stories covering baseline,
missing-provider (with tooltip assertion), disabled-provider, and
disabled-model paths.
- `ModelsPageView.stories.tsx`: `OrphanedModelShowsUnset` feeds an
orphaned model through the real derivation (map-miss + `?? false`),
matching the production shape produced by `deriveProviderStates`.
`DisabledProviderModelsStillListed` now asserts the "Disabled" badge.
- `ModelForm.stories.tsx`: `EditUpdateEnabledOnProviderChange` asserts
the save button is enabled when the selected provider differs from the
model's stored provider.
- `testFixtures.ts`: `mockOrphanedModel` fixture representing the
deleted-provider case.
Diff: 7 files, 240 insertions, 9 deletions.
> 🤖 This PR was updated with Coder Agents.
|
||
|
|
2794886688 |
fix: demui <Form /> emotion usage (#27560)
Tiny simple change just moving us over to Tailwind instead of MUI for `<Form />`. Functionally equivalent minus the mismatch between `md` in `Tailwind` (`≥ 768px`) and `MUI` (`≥ 900px`). |
||
|
|
b448de670b |
fix(site/src): refresh provider state after device-flow exchange (#26795)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` ← this PR 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` #18039 (May 2025) upgraded `@tanstack/react-query` from v4 to v5, which [removed `onSuccess`/`onError`/`onSettled` from `useQuery`](https://tanstack.com/query/v5/docs/react/guides/migrating-to-v5#callbacks-on-usequery-and-queryobserver-have-been-removed). The migration updated the `invalidateQueries` argument shape but left the now-dead `onSuccess` in place on `exchangeExternalAuthDevice`, which is consumed by `useQuery` in `ExternalAuthPage.tsx`. The relevant lines from #18039 in `site/src/api/queries/externalAuth.ts`: ```diff queryKey: ["external-auth", providerId, "device", deviceCode], onSuccess: async () => { // Force a refresh of the Git auth status. - await queryClient.invalidateQueries(["external-auth", providerId]); + await queryClient.invalidateQueries({ + queryKey: ["external-auth", providerId], + }); }, ``` Result: after a successful device-flow exchange the `externalAuthProvider` query is never invalidated, so `externalAuthProviderQuery.data.authenticated` stays `false` and the UI is stuck on "Checking for authentication..." until a manual page refresh. This breaks real users who go through the device flow today, not just the e2e suite that re-enables in #26648. This PR drops the dead `onSuccess` (and the `queryClient` param it depended on), and moves the invalidation into a `useEffect` in `ExternalAuthPage.tsx` that fires when `exchangeExternalAuthDeviceQuery.isSuccess` flips true. Matches the existing pattern in `LoginOAuthDevicePage.tsx`. Verified against the failing CI run on #26648 ([job 83970095566](https://github.com/coder/coder/actions/runs/28346242963/job/83970095566?pr=26648)): the trace shows `POST /api/v2/external-auth/device/device` returning `204` (exchange succeeded) followed by no subsequent `GET /api/v2/external-auth/device` to refresh the provider state. With this PR the `isSuccess` effect runs, the provider query refetches, and the UI flips to the authorized state. ## Regression test Added `site/src/pages/ExternalAuthPage/ExternalAuthPage.test.tsx` (the first test for this page). It renders the device flow with a stateful provider handler that returns `authenticated: false` until the exchange `POST` lands, then asserts the UI flips from the "Authenticate with GitHub" polling screen to "You've authenticated with GitHub!" without a manual refresh, and that the provider endpoint is refetched after the exchange. Confirmed red against the pre-fix code (stuck on the polling screen) and green with the fix. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/coder/pull/18039 <details> <summary>Why <code>useEffect</code> rather than a query-level callback</summary> react-query v5 removed `onSuccess`/`onError`/`onSettled` from `useQuery` because the v4 behaviour was unsound: the callbacks fired per-observer rather than per-query, so they ran twice when two components observed the same query and not at all when a component unmounted and cached data was reused. [TKDodo's "Breaking React Query's API on Purpose"](https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose) and the [official v5 migration guide](https://tanstack.com/query/v5/docs/react/guides/migrating-to-v5#callbacks-on-usequery-and-queryobserver-have-been-removed) both recommend `useEffect` on `isSuccess` as the replacement. Alternatives considered: | Option | Why not | | --- | --- | | Side-effect in `queryFn` | Re-adds the `queryClient` dependency we just removed, and fires on every retry and cached re-read, not just first success. | | Convert to `useMutation` | Wrong semantics. This is a polling query with `retry: isExchangeErrorRetryable` and `retryDelay`; mutations are one-shot and don't have retry-on-pending machinery. | | Global `QueryCache` `onSuccess` | Runs for every query in the app; filtering by queryKey or `meta` for a single per-page side effect is more code than the effect it replaces. | | Custom hook wrapping `useQuery` | Only one consumer, so the abstraction would have one caller. | Local precedent: `LoginOAuthDevicePage.tsx` already uses the same `isSuccess` → `useEffect` pattern for the post-success `location.href` redirect. </details> <details> <summary>Why a separate PR</summary> Keeps the bisection signal clean. #26575 fixes the EADDRINUSE flake, #26793 fixes the 404 hook contract drift, this PR fixes the dropped invalidation, and #26648 just flips `.skip`. Each PR addresses one independent root cause that piled up while the externalAuth suite was skipped. </details> |
||
|
|
072b101624 |
chore: setup page cleanup (#24649)
This pull-request addresses a few things that may have made this page less accurate than we would have liked. * Disable the setup form when it is submitting things to the backend. * Migrate `<PasswordField />` over to being backed by `<FormField />` * Don't make use of `<strong />` in the header. Rely on the `font-semibold` as per other headings in the codebase. |
||
|
|
a3d67507e9 |
fix(site): demui <WorkspaceSettingsForm /> component (#27505)
This pull-request removes the imports of `@mui/material/*` from `<WorkspaceSettingsForm />`. |
||
|
|
6c916629c9 |
feat(site): rename "Dismiss warnings" to "Mute warnings" and make health callouts dismissible (#27554)
Fixes a mismatch between the header button's label and its behavior on the Health pages. Today the button reads **Dismiss warnings**, which suggests it will close the in-page callout, but it actually toggles whether the health check surfaces in the top-nav status indicator and shows a bell-off icon in the sidebar. The callout itself has no way to be closed. ### Changes - Rename the toggle to **Mute warnings** / **Unmute warnings** (with matching toast copy) and rename the component + file from `DismissWarningButton` to `MuteWarningsButton`. - Set `dismissible` on the **warning** `<Alert>`s across the Health pages (Access URL, Database, DERP, DERP region, Provisioner Daemons, Websocket, Workspace Proxy) so users can close the callout from the callout itself. `Alert` already supports this via a built-in close button. - Error-severity `<Alert>`s are intentionally **not** dismissible: `HealthLayout` refetches every 30s and reuses the mounted subpage, so allowing dismissal would suppress subsequent (possibly different) error messages until reload. Diagnostics pages should not hide active faults. - Align ProvisionerDaemonsPage's warning callout with the other five pages by setting `prominent`. ### Notes - Callout dismissal is client-side only (matches `Alert`'s existing `useState` behavior). Warning `<Alert>`s are keyed by `warning.code`, so a dismissed warning reappears on reload/remount but survives a refetch. The mute toggle continues to persist server-side via `dismissed_healthchecks`. - Follow-up filed for a pre-existing UX mismatch: the mute also silently drops error-severity sections from the top-nav banner (#27557). Kept out of scope here per requester. - No API or backend changes. --- _This PR was generated by Coder Agents on behalf of @tracyjohnsonux._ |
||
|
|
1ab4ed8db5 |
feat: exclude AI Bridge usage from AI Governance seat counting (#27280)
Under the new `ai-gateway-seat-exclusion` experiment, AI Bridge usage stops counting toward AI Governance seats. ## Seat recording Under the experiment, `RecordInterception` no longer records `ai_seat_state` usage for the initiator: AI Gateway access is licensed by the AI Governance add-on rather than per seat. This experiment is independent of `workspace-capable-licensing` (#27279) so the two licensing behaviors can be enabled separately. Task workspace builds still claim AI Governance seats. ## Manual verification Verified live on a dev deployment (provider chained to dev.coder.com's gateway, model `gpt-5.6-luna`): with the experiment off, the first bridge request from each identity type (admin, plain member, service account) wrote an `ai_seat_state` row (`aibridge` reason); with it on, requests recorded interceptions but left seat state untouched — no new rows, and existing rows' `last_used_at` did not advance. Part of the gateway-accounts feature. ## Stack Part 2 of the gateway-accounts stack: 1. **#27279**: permission-based license seat counting. Behind the `workspace-capable-licensing` experiment and gated on the AI Governance add-on, `user_limit` counts only users the RBAC engine authorizes to create workspaces. 2. **This PR**: stops AI Bridge usage from claiming AI Governance seats under the new `ai-gateway-seat-exclusion` experiment. 3. ~~**#27281**: adds a `use_shared` capability precondition for workspace ACL grants, so workspace sharing is ineffective for (and rejected toward) users without workspace capabilities, evaluated live on every authorization.~~ This will be done in follow-up work when we have time to look into the performance impact. Related but independent: **#27278** hides the Workspaces page create CTAs for users without workspace-create permission. |
||
|
|
6c102cc3f3 |
feat: count only workspace-capable users toward license seats (#27279)
Adds permission-based license seat counting behind the
`workspace-capable-licensing` experiment. When the experiment is enabled
and a valid license carries the AI Governance add-on, the `user_limit`
feature counts only active users the RBAC engine authorizes to create a
workspace, instead of every active user. Users without workspace-create
capability ("gateway accounts", e.g. AI-Gateway-only users) no longer
consume seats.
## How it works
- A new `GetActiveUsersAuthorizationRoles` bulk query returns effective
roles (implied member roles, org default member roles) and group
memberships for every seat-eligible user (active, not deleted, not
system, not a service account), matching `GetActiveUserCount` semantics.
- `license.CountWorkspaceCapableUsers` evaluates `workspace.create`
against the any-organization object form, which covers site-wide grants,
membership grants, and org-scoped bans in one check. Evaluation is
deduplicated on a sha256 of each user's canonical subject JSON (a fixed
sentinel user ID, sorted deduplicated roles and groups), so cost scales
with unique subjects rather than user count, and every subject field
participates in both the evaluation and the key.
- The AI Governance add-on is only known after license claims are
parsed, so `Entitlements()` passes a lazy `WorkspaceCapableUserCountFn`
(following the `ManagedAgentCountFn` precedent) and
`LicensesEntitlements` resolves it when a validated add-on is present.
Each license's `user_limit` claim becomes a candidate pair of limit and
counting mode, the most favorable pair is selected (see Behavior notes),
and the selected pair's limit, entitlement, and count become the
`user_limit` feature's terms; the warnings read the same values.
`license.Entitlements` gains `logger`, `authorizer`, and `experiments`
parameters.
- All custom roles are prefetched in a single query before evaluation
(new exported `rolestore.PrefetchCustomRoles`), and each count emits one
Info log line (capable count, eligible active users, unique subjects,
elapsed) whose presence identifies the counting mode. The count is
bounded by a 60s timeout.
## Behavior notes
- Without the experiment or without the add-on, the legacy
`GetActiveUserCount` path is unchanged.
- When the mode is active, the over-limit and expired-limit warnings say
"workspace-capable users" instead of "active users", since that is what
was counted.
- With multiple licenses, each license's `user_limit` claim forms a
candidate pair of limit and counting mode (workspace-capable for add-on
licenses, all active users otherwise), and the most favorable pair is
enforced: a pair satisfied by its own count wins over any unsatisfied
one, then higher entitlement, then higher limit. One license's limit is
never combined with another license's counting mode, so a small add-on
license can neither borrow a bigger non-add-on limit nor suppress it.
- Licenses in their grace period still gate the count; it reverts to the
legacy count only on hard expiry. While the add-on exists only on
grace-period licenses, a warning tells admins the counting mode will
revert and states the legacy active-user count they will then be
measured by.
- Count errors (database failures, timeout) abort the entitlements
computation, matching the legacy count's error semantics: the refresh
fails and the caller keeps the previous entitlements rather than a
silently different count. One exception: a stored role string that fails
to parse is logged and treated as not workspace-capable instead of
failing the refresh, since authorization fails closed on such roles
anyway.
- The experiment is deliberately not in `ExperimentsSafe`.
Part of the gateway-accounts feature; no behavior changes for
deployments without the experiment.
## Stack
Part 1 of the gateway-accounts stack. Each PR builds on the previous:
1. **#27279 (this PR)**: permission-based license seat counting. Behind
the `workspace-capable-licensing` experiment and gated on the AI
Governance add-on, `user_limit` counts only users the RBAC engine
authorizes to create workspaces.
2. **#27280**: adds the `organization-ai-gateway-access` org role
carrying the AI Bridge interception permissions (extracted from the
member floors, backfilled into org default roles by migration) and
enforces it at AI Gateway authentication; bridge usage stops claiming AI
Governance seats under the experiment.
3. ~~**#27281**: gates workspace ACL grants on matching member-level
capability (each granted action only takes effect while the recipient
holds that action in the org), so workspace sharing is ineffective for
(and rejected toward) users without workspace capabilities, evaluated
live on every authorization.~~ Tabled — excluded from the
gateway-accounts MVP.
Related but independent: **#27278** hides the Workspaces page create
CTAs for users without workspace-create permission.
## Benchmarks
`BenchmarkCountWorkspaceCapableUsers` (in `usercount_bench_test.go`, run
manually with `go test ./enterprise/coderd/license/ -bench
BenchmarkCountWorkspaceCapableUsers -benchtime 5x -run '^$'` — never
executed by CI) measures the count across user-scale and role-diversity
shapes:
| Scenario | Users | ~Unique subjects | per count |
|---|---|---|---|
| Uniform | 1k | 4 | 8.5ms |
| Uniform | 10k | 4 | 71ms |
| Uniform | 50k | 4 | 344ms |
| ManyOrgs (100 orgs) | 10k | ~200 | 112ms |
| CustomRoles (1000 org-scoped roles) | 10k | ~1000 | 168ms |
| UniquePairs (every user a distinct subject) | 10k | ~10,000 | 2.66s |
Summary:
- **Row-side cost is ~7µs per user, linear** (role parsing, subject
canonicalization, and sha256 per row). The bulk query + subject dedupe
handles 50k users in ~350ms; extrapolated 100k ≈ 0.7s. A non-issue at
the 10-minute refresh cadence.
- **Unique subjects are the dominant axis at ~0.26ms each** (role
expansion + one any-organization rego evaluation per subject). The
worst-case scenario — every user a distinct subject — costs ~2.7s at 10k
users, extrapolating to ~13s at 50k.
- **Realistic deployments sit near the cheap rows.** Subject diversity
tracks orgs × role/group combinations, not user count; only per-user
custom roles or per-user org-membership patterns approach the worst
case.
- Caveat encountered while building the harness: the roles query's plan
depends on accurate table statistics. With stale stats (e.g. right after
a bulk user import, before autovacuum ANALYZEs), the planner picks a
nested-loop plan that re-runs the aggregation per user row — a ~300×
regression (1.08s for 1k users). Fresh statistics restore the hash-join
plan; the harness ANALYZEs after seeding, so the numbers above reflect
the healthy plan.
|
||
|
|
00d134ebfd | chore: remove classic parameter UI (#25014) | ||
|
|
d57965ee7f |
chore: regenerate prices.json from models.dev (#27549)
Implements: https://linear.app/codercom/issue/AIGOV-493/update-pricesjson-with-current-model-rates-before-cost-control-release Run `make gen/aibridge-prices` to regenerate `prices.json` from `models.dev`. This update: - adds support for the upstream `claude-opus-5` model; - removes older `openai` models that have been removed upstream, keeping us in sync with `models.dev`. |
||
|
|
2574e6b785 |
feat: notify admins when a user crosses an AI budget threshold (#27415)
Implements: https://linear.app/codercom/issue/AIGOV-289/notify-users-and-admins-on-budget-warning-and-limit-reached Notify admins when a user crosses an AI budget threshold, complementing the user-facing notifications from https://github.com/coder/coder/pull/27346 When a priced interception pushes a user's period spend across the warning (85%) or limit (100%) threshold, the Owners and User Admins now receive an admin notification naming the affected user, alongside the user's own notification. The affected user is excluded from the admin recipients since they already get the user-facing copy. Delivery is best-effort: a failure to enqueue is logged and never blocks recording the interception. The admin templates always show the effective group the spend is attributed to, and note when the limit comes from a per-user override rather than the group budget. Depends on https://github.com/coder/coder/pull/27346 ## Screenshots: <img width="1101" height="440" alt="image" src="https://github.com/user-attachments/assets/eb731088-05c8-47bd-9d06-fc9d07f63a08" /> <img width="468" height="391" alt="image" src="https://github.com/user-attachments/assets/b89b76a6-3fa8-4735-99a2-43e119a7a7e3" /> |
||
|
|
51ac968d5a |
feat: wire up Template Builder session telemetry endpoint (#27124)
`TemplateBuilderSession` telemetry types and telemetry-server ingestion were added in earlier PRs (#25082, coder/coder-telemetry-server#41), but no code ever produced session events. This adds the missing producer. **Backend**: `POST /api/v2/templatebuilder/sessions` reports wizard entry and compose completion events directly via `api.Telemetry.Report()`, using the same inline pattern as `NetworkEvents` and `UserTailnetConnections`. No database migration or `createSnapshot()` changes needed. RBAC requires `policy.ActionCreate` on `ResourceTemplate.AnyOrganization()`, matching the compose endpoint. **Frontend**: The template builder wizard fires `wizard_entry` on page mount and `compose_completion` on create success or failure. A client-generated session ID (UUID) correlates the two events for the same wizard visit, enabling precise funnel analysis and abandonment detection in BigQuery. Duration is tracked via `Date.now()` in the wizard state. Closes https://linear.app/codercom/issue/DEVEX-599 <details> <summary>Implementation plan</summary> ## Root Cause Analysis The DEVEX-599 ticket diagnosis suggested missing DB tables, queries, and `eg.Go` blocks. That diagnosis assumes the DB-backed periodic snapshot path is required. It is not. Investigation shows two telemetry reporting patterns in the codebase: 1. **DB-backed periodic snapshots** (`createSnapshot()` with `eg.Go` blocks): Used for durable entities like workspaces, templates, users. 2. **Direct inline reporting** (`api.Telemetry.Report(&telemetry.Snapshot{...})`): Used for ephemeral events like `NetworkEvents`, `UserTailnetConnections`, `CLIInvocations`. Template builder sessions are ephemeral events, so the direct inline reporting pattern is the correct fit. ## Backend Changes - `codersdk/templatebuilder.go`: `TemplateBuilderSessionRequest` type with `SessionID`, `EventType` enum, `TemplateBuilderSession()` client method - `coderd/coderd.go`: Route registration in `/templatebuilder` group - `coderd/templatebuilder_handler.go`: Handler with RBAC check, request validation, session ID fallback, and inline telemetry report - `coderd/templatebuilder_handler_test.go`: Tests for wizard entry, compose completion, invalid event type, disabled feature, and member RBAC rejection ## Frontend Changes - `site/src/api/api.ts`: `recordTemplateBuilderSession` API method - `site/src/api/queries/templateBuilder.ts`: React Query mutation - `site/src/pages/TemplateBuilder/wizardState.ts`: `sessionId` and `enteredAt` fields, `createWizardState()` factory for per-mount initialization - `site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx`: `sessionId` prop, `useReducer` initializer form - `site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx`: Telemetry calls for wizard entry (on mount) and compose completion (on create success/failure) </details> > 🤖 Generated by Coder Agents --------- Co-authored-by: Coder Agent <agent@coder.com> |
||
|
|
daf655dff8 |
fix(coderd/x/chatd/chaterror): classify aibridge 403 as ChatErrorKindUsageLimit (#27538)
Adds the string `ai budget` to the classifier for `ChatErrorKindUsageLimit`. <img width="809" height="267" alt="Screenshot 2026-07-27 at 18 27 13" src="https://github.com/user-attachments/assets/7e2ba9ae-8168-4fd4-87f6-c4e7dfdc9526" /> Testing notes: - I set the group limit by running `insert into group_ai_budgets values ('<everyone group UUID>', 1, NOW(), NOW());` > Created by a human, trimmed down by a Coder agent. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
95be28850d | refactor(site/src/pages): delete unreachable tool render paths (#27527) | ||
|
|
5af3d95b06 |
test(coderd/rbac): verify workspace creation ban denies any_org create (#27533)
<!-- Created by Coder Agents on behalf of @Emyrk. --> Adds RBAC tests for a user holding both `organization-workspace-access` and `organization-workspace-creation-ban`. - Single org with both roles: the `any_org` workspace create check returns **false**, since the ban's negative permission is the only organization vote. - Member of two orgs, banned in one, workspace-access in the other: `any_org` create returns **true**, since the max vote across organizations wins. - Per-org checks confirm the ban denies create/delete only in the banned org, and non-banned actions (read, update) remain allowed. --- <sub>Coder Agents on behalf of @Emyrk.</sub> |
||
|
|
5699f1cdfb |
fix: retry and cache e2e Coder release downloads to reduce test-e2e ssh flake (#27470)
closes DEVEX-651 ## Summary Fixes coder/internal#218 (`flake: e2e-test / test ssh`). Despite the title, the `ssh with client v2.8.0` / `ssh with agent v2.12.1` cases (`site/e2e/tests/outdatedCLI.spec.ts`, `outdatedAgent.spec.ts`) are not failing because of a bug in SSH. They fail during **setup**, in `downloadCoderVersion()`, which runs `install.sh` to fetch an old Coder release from GitHub. Transient GitHub errors (HTTP 403/503, surfacing as nonzero `curl` exit codes such as 22 or 1) make `install.sh` fail and take the whole ssh test down with it. This is an external-download flake, confirmed by the recurring `install.sh failed with code {22,1}` evidence in the issue thread and Ethan's note ("Networking issues again"). ## Changes 1. **Retry-with-backoff** (`site/e2e/helpers.ts`): `downloadCoderVersion()` now retries `install.sh` up to 5 times with exponential backoff and jitter (~1s, 2s, 4s, 8s). A single transient download failure no longer fails the test. `install.sh` already reuses completed binaries and resumes partial downloads (`curl -C -`), so retries are cheap. 2. **Cross-run cache** (`.github/workflows/ci.yaml`): the `test-e2e` job now persists `/tmp/coder-e2e-cache` with `actions/cache`, so most runs skip the GitHub download entirely. The key is derived from the spec files that pin the downloaded versions, so it invalidates when those versions change. Saves are restricted to `main` (`restore` runs everywhere), matching the existing cache-poisoning convention used for the Vale and golangci-lint caches. Before this change, neither retry, mirror, nor cross-run caching protected this path; the only caching was within a single run. ## Testing - `biome check e2e/helpers.ts` passes. - `tsc --noEmit` introduces no new errors. - CI `test-e2e` exercises the changed path. <details> <summary>Investigation notes</summary> - The failure always originates in `downloadCoderVersion` -> `install.sh` -> `fetch()` (`curl -#fL ... https://github.com/coder/coder/releases/download/vX.Y.Z/...`). - `curl` exit 22 = server returned an HTTP error (403 seen in logs); exit 1 = other transient failure. GitHub also returned 503s across the workflow in some occurrences. - `/tmp/coder-e2e-cache` was not persisted by any `actions/cache` step in `ci.yaml`, so every fresh job re-downloaded from GitHub and was exposed to the flake. - Retry addresses transient failures; the cache removes the dependency on GitHub for most runs. Combined, they target the root cause at two layers. </details> --- This PR was generated by Coder Agents on behalf of @aqandrew. |