mirror of
https://github.com/coder/coder.git
synced 2026-10-31 02:43:21 +08:00
cf97b5647953bb0b6d5b49a6205310f26480fb17
16014
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cf97b56479 |
fix(coderd/x/chatd): refresh context after workspace restart (#28441)
Workspace lifecycle tools clear a chat's agent binding while retaining its pinned workspace context. When the chat binds to the replacement agent, the missing previous agent ID caused chatd to skip re-pinning and continue using stale instructions, skills, and MCP definitions. Re-pin when an unbound chat still carries pinned context, while preserving the existing first-time hydration path for new chats without a pin. Adds regression coverage for the stop/start binding sequence. ### CI note All required checks pass. The external `Pixel / Review` status reports the same 3 pre-existing failures on this PR's merge base (`610f321d52c16d43e73c489aa672ef60a92c20ea`) and recent `main` commits. This backend-only change does not touch `site/` or Storybook snapshots. --- Generated by Coder Agents on behalf of @kylecarbs. |
||
|
|
2f50b2d740 |
fix(coderd): reject agent requests from suspended owners (#28513)
## What `ExtractWorkspaceAgentAndLatestBuild` computed the workspace owner's RBAC subject via `UserRBACSubject`, but discarded the returned user status. As a result, workspace agent requests were still authorized even when the owner's account was suspended. This change captures `userStatus` and rejects the request with `401 Unauthorized` when the owner is not `Active`, returning a clear message directing the user to contact an admin to reactivate the account. ## Changes - `coderd/httpmw/workspaceagent.go`: stop discarding the owner status from `UserRBACSubject`; return `401` when the owner is not active. - `coderd/httpmw/workspaceagent_test.go`: add an `InactiveUser` test that suspends the owner and asserts a `401` with the expected message. ## Testing - New `TestWorkspaceAgent/InactiveUser` unit test covers the suspended-owner path. --- > This PR was created by Coder Agents on behalf of @hwang251. |
||
|
|
c029869a04 |
fix(coderd): deflake TestHealthSettings dismissal subtests (#28501)
Fixes
https://linear.app/codercom/issue/PLAT-514/flake-testhealthsettingsdismisssection
Both TestHealthSettings dismissal subtests built their server with
coderdtest.New(t, nil), so GET /api/v2/debug/health ran the real
healthcheck, DERP probe included, only to assert that dismissed sections
come back as dismissed.
- Raising the timeout would not fix it. The healthcheck runs on a
context detached from the request (30s HealthcheckTimeout), and
derphealth's 10s per-node cap is skipped whenever a deadline already
exists, which is always true from that call site. Both subtests share
one 10s WaitShort across setup and four round trips, so the server may
spend 3x the test's entire lifetime on one request.
- Fix: inject a stub HealthcheckFunc in both subtests, as the six
TestDebugHealth subtests already do.
- The stub reports nothing dismissed, so the assertions still prove the
flags are applied per request in formatHealthcheck rather than read from
the cached report, which is what
|
||
|
|
3c3240836e |
refactor: consolidate viewport hooks and remove defineProperty matchMedia stub (#28460)
Follow-up to review feedback on #28387. That PR added three hooks for one feature (`useIsBelowLgViewport`, `useIsBelowMdViewport`, and the local single-use `useRightPanelNarrowSuppression`) and stubbed `window.matchMedia` in tests with `Object.defineProperty`. - Replace the two single-purpose viewport hooks with one generic `useMediaQuery(query)`. Callers pass the shared Tailwind-aligned query constants from `utils/mobile.ts`. The one new hook replaces the two deleted ones (net -1). - Inline `useRightPanelNarrowSuppression` into `AgentChatPage`, its only consumer, and drop its `renderHook` unit suite. The behavior stays covered by the narrow-viewport stories; the widening-restore case moved into the `NarrowingSuppressesExpandedPanel` play function. - Rework `testHelpers/matchMedia.ts` to install the stub with `spyOn` from `storybook/test` instead of `Object.defineProperty`. The helper is story-only now (stories run in real Chromium; jsdom has no `matchMedia`), so any future unit test needing a stub should use `vi.stubGlobal` directly. - Encode the feedback in the canonical FE contract so it gets caught during development: `.claude/docs/FRONTEND_PATTERNS.md` now bans new React hooks when an existing hook, a plain function, or component state suffices (FE3) and bans replacing browser globals with `Object.defineProperty` in tests or stories (FE9: `vi.stubGlobal` / `spyOn`), and notes that `renderHook` suites for stateful UI hooks belong in the consuming component's story (FE1). The `frontend-review` skill checklist flags all three. `site/AGENTS.md` is unchanged since it already defers to the patterns doc. Validation: `pnpm check`, `pnpm format:check`, `pnpm lint` (biome, types, knip, circular deps, compiler check), `AgentChatPage.test.ts` (70 passed), story runs for `AgentChatPage.stories.tsx` and `WorkspacePill.stories.tsx` (49 passed in Chromium). > Xum acted on Mike's behalf (@ibetitsmike). <!-- xum-attribution: model=claude-opus-4-6 thinking=high --> |
||
|
|
614b5e2d22 |
chore(site): remove outdated featured empty-state images (#28454)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. ## What The images in `site/static/featured` were outdated and no longer resembled the current UI, so this removes them along with the code that referenced them: - `workspaces.webp` — was the faded background image in the Workspaces empty state (`WorkspacesEmpty.tsx`). - `templates.webp` — was only referenced by a Storybook story (`TableEmpty.stories.tsx`). - `scheduling.webp` — had no references anywhere in the codebase. ### Cleanup alongside the removal - Dropped the `image={defaultImage}` prop (and the now-dead `defaultImage`) from all three `WorkspacesEmpty` states. - Removed the `className="pb-0"` / `paddingBottom: 0` overrides that only existed to seat the image flush against the bottom, restoring balanced padding. - Renamed the `TableEmpty` story `WithImageAndCta` → `WithCta` since it no longer has an image. ## Old | New | Surface | Old | New | |---|---|---| | Workspaces empty state | <img width="2306" height="1376" alt="main-tableempty" src="https://github.com/user-attachments/assets/1d3a5201-66ce-4f80-a2ea-5638221f95d0" /> | <img width="2306" height="1376" alt="pr-tableempty" src="https://github.com/user-attachments/assets/5a8fcc1d-b676-46e6-be54-12ff7f3489fd" /> | | Table empty state (templates) | <img width="2306" height="1376" alt="main-workspaces-empty" src="https://github.com/user-attachments/assets/7618ee1f-09d4-4398-8a1c-19362f63c80d" /> | <img width="2306" height="1376" alt="pr-workspaces-empty" src="https://github.com/user-attachments/assets/8a9c98a9-24b7-4c90-9959-f85dbc2daa9f" /> | | `scheduling.webp` | Unused | Deleted 🙂 | <details> <summary>Implementation notes / decision log</summary> - Verified references with a repo-wide search for `/featured/` and each `*.webp` filename. Only `WorkspacesEmpty.tsx` (real usage) and `TableEmpty.stories.tsx` (Storybook-only) referenced them; `scheduling.webp` was fully unused. - `EmptyState`'s `image` prop is optional, so removing usages required no component changes. - Delete-only (per request): no replacement images committed. The empty states now render as clean text/CTA layouts. - "New" screenshots were captured from Storybook (a temporary `WorkspacesEmpty` story was used only to render the states and was **not** committed). - Verified with `tsc --noEmit` (0 errors) and `biome check` (clean) on the changed files. </details> |
||
|
|
eab5e28695 |
fix: move organization model overrides from Models to Coder Agents settings (#28473)
## Summary Relocates the organization-scoped Coder Agents model override controls from the **Defaults & overrides** tab under **AI Settings > Models** into an **Organization settings** section on **AI Settings > Coder Agents**. Models remains the home for reusable model definitions, provider availability, default designation, and ACLs; Coder Agents now owns all agent-specific model/reasoning override controls. The backend organization scoping from #28442 is unchanged. ## Changes - **Coder Agents page** is split into two sections: - **Organization settings**: General subagent, Explore subagent, title generation, compaction, and Advisor (behind the `chat-advisor` experiment) model/reasoning overrides, with an organization picker when more than one accessible organization exists. - **Deployment settings**: personal override policy, virtual desktop provider (behind its experiment), and Advisor usage limits, visible only with `editDeploymentConfig`. - **Permissions**: the page and its sidebar entry are accessible with either `editDeploymentConfig` or organization chat model access. Organization mutations require `editChatModelConfigs` on the selected organization; otherwise controls render read-only. Organization discovery, selected-organization permissions, and deployment queries fail independently. - **Models page** drops the Defaults & overrides tab and the now-single-item tab UI. The old `DefaultsPage` components move to `CoderAgentsPage/OrganizationAgentSettings*`. - **Compatibility**: `/ai/settings/models/defaults` redirects to `/ai/settings/coder-agents`, preserving `org` and other query parameters. - **Docs**: intentionally not updated here; a follow-up PR will update `docs/ai-coder/agents/models.md` and `docs/ai-coder/agents/platform-controls/advisor.md`. ## Testing - Targeted unit tests (`ModelDefaultsRedirect`, `organizationModels`): 9 passing. - Affected Storybook interaction tests (Coder Agents page/view, organization agent settings, Models layout, AI settings sidebar): 41 passing. - `pnpm check`, full `pnpm lint` (Biome, tsc, circular deps, React compiler, knip), `pnpm format`. |
||
|
|
d12cc2da21 |
fix(site): measure virtualized log row heights (#28251)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Fixes coder/coder#25692. Closes coder/coder#25692. Supersedes coder/coder#28250. ## Problem `AgentLogs` virtualizes rows with a **fixed** `itemSize` (20px), so react-window sets the total scroll height to `itemCount * 20`. Log entries that contain newlines render taller than 20px, so the estimate falls short: tall rows overlap the rows below them and the true bottom sits past where `scrollToItem(..., "end")` can reach. The last lines become unreachable. ## Fix Switch to `VariableSizeList` and measure each row with a `ResizeObserver`, feeding the real height back via `resetAfterIndex`. The total scroll height now tracks the real content, so the bottom is always reachable. `AgentRow` and `TaskPage` only change the ref type; their existing autoscroll now lands correctly. ## Before / After Dogfooded on a local instance with a workspace whose agent logs include multi-line entries (241 entries, every 30th a 6-line block, plus a final bottom-marker line). Same workspace, same scroll interaction, `main` vs this branch: <!-- linear:table-colwidths:266,266,266 --> | | Before (`main`, `FixedSizeList`) | After (this PR, `VariableSizeList`) | | -- | -- | -- | | **A multi-line entry** | Painted into a 20px slot, so it collides with the rows above and below (text on top of text). | Rendered at full height in its own space, neighbours no longer overlap. | | |  |  | | **Scrolled to the bottom** | Total height is short, so normal scrolling stops early: the final marker line is clipped and unreachable. | Height tracks real content: the final marker line is fully visible and reachable. | | |  |  | ## Validation The new `AgentLogs.test.tsx` renders rows measured taller (40px) than the estimate and asserts the scroll area is sized from measured heights. Run against the `main` implementation it fails, against this branch it passes: ``` # main (FixedSizeList): scroll area = 5 rows * 20px - Expected 200 + Received 100 → FAIL # this branch (VariableSizeList): scroll area = 5 rows * measured 40px ✓ AgentLogs.test.tsx (1 test) → PASS ``` Also green: `tsc`, `biome check`, and the AgentRow (25) + TaskPage (46) + AgentLogs (3) Storybook interaction tests. <details><summary>Dogfood details and decision log</summary> **Dogfood setup** (SHA `1228dcfe8d`, `./scripts/develop.sh`): template `lognl2` (a `coder_agent` on a `null_resource` so the agent authenticates against a real build), workspace `lognl-demo2`. Posted 241 log entries via `PATCH /api/v2/workspaceagents/me/logs`, every 30th a 6-line `\n` entry plus a bottom marker; confirmed the 8 multi-line entries persisted with embedded newlines. On the fixed build, at maximum scroll the DevTools console reported `scrollTop 5204`, `scrollHeight 5460`, `clientHeight 256`, `remaining 0`, marker clipped `0px`; height is self-consistent (`innerHeight 5428` + 16px top + 16px bottom padding = `5460`). **Decision log** * coder/coder#28250 treated this as an autoscroll problem. Wrong: the height calculation is the bug, so no scroll target can reach a bottom placed out of range. Reverted. * Only embedded newlines make a row exceed 20px; long lines do not wrap (they overflow horizontally). A fixed constant can't cover a variable line count, so measuring is the robust fix. * `resetAfterIndex` can't run from a row's first-mount measurement effect because the list ref is assigned after child rows commit; the lowest changed index is recorded and flushed from a parent layout effect. </details> Evidence images are hosted on the `devex-407-evidence` branch of the author's fork (`jakehwll/coder`) to keep this repo's diff clean. |
||
|
|
18b37e64f5 |
chore(site/e2e): re-enable externalAuth suite (#26648)
> 🤖 This PR was written 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` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` ← this PR Removes the `test.describe.skip` introduced in #17235 so the externalAuth suite runs in CI again. Splitting the five commits keeps the bisection signal clean if CI behaviour changes after the un-skip. Local verification of the full stack: `pnpm playwright:test e2e/tests/externalAuth.spec.ts --repeat-each=20 --workers=1` → 61/61 passing in 4m48s. Large whitespace churn in the diff is biome reformatting the body to its natural indentation after the single-line `test.describe(...)` opener. The only semantic change is removing `.skip`. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/internal/issues/356 |
||
|
|
cfc66b5de3 |
fix: reset both providers in external auth hook (#26798)
> 🤖 This PR was written 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` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` ← this PR 5. #26648 `chore(site/e2e): re-enable externalAuth suite` Surfaced by stress-testing the leaf state of the stack locally with `--repeat-each=N`. The suite passes on iter 1, then deterministically fails the device test on every subsequent iteration: | iter | device | web | workspace | |---|---|---|---| | 1 | ✓ 2.8s | ✓ 2.5s | ✓ 6.2s | | 2 | **✘ 30.1s** | ✓ 2.6s | ✓ 6.2s | | 3 | **✘ 30.1s** | ✓ ~2.5s | ✓ ~6s | | … | … | … | … | `resetExternalAuthKey` in `site/e2e/hooks.ts` only deletes the link for `gitAuth.webProvider`, never `gitAuth.deviceProvider`. So: ```ts path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`, ``` Iter 1 of the device test creates a link for the `device` provider against the test user. The cleanup hook between tests resets `web` but not `device`. On iter 2 the page hits `/external-auth/device`, `externalAuthProviderQuery.data.authenticated` is already `true`, the page renders the authorized state immediately, and the test's `sentPending.wait()` hangs forever because the mock token endpoint is never polled. The whole test times out at 30s. Fix: reset every provider the suite exercises. Refactored the per-link delete into a Promise-returning helper so errors actually propagate; the previous `throw` inside the response and `error` callbacks crashed the worker rather than rejecting the call. Doesn't affect CI (single run per PR, fresh `--ephemeral` coderd), but it makes the suite robust to local stress testing and to anyone re-running it in the same coderd process. Filed before the un-skip lands so the un-skipped suite is repeatable from day one. Local verification: `pnpm playwright:test e2e/tests/externalAuth.spec.ts --repeat-each=20 --workers=1` → 61/61 passing in 4m48s. Same command before this fix → 1 pass, then deterministic device-test timeouts. Refs https://linear.app/codercom/issue/DEVEX-413 <details> <summary>Why a separate PR</summary> This is the fourth distinct root cause that piled up while the suite was skipped, and it's orthogonal to the other three. #26575 fixes the listener leak, #26793 fixes the 404 hook contract drift, #26795 fixes the dropped invalidation, and this PR fixes the per-test cleanup so the suite is actually repeatable. #26648 then just flips `.skip`. </details> |
||
|
|
b6d7653231 |
fix: apply MCP server selection when editing a chat message (#28471)
## Problem
Editing a user message in an Agent chat silently dropped the MCP server
selection. The composer renders the MCP picker in edit mode and toggles
update local client state, so the picker displayed the new selection,
but the edit request omitted `mcp_server_ids` at every layer (frontend
request builder, `codersdk.EditChatMessageRequest`, the `PATCH
/chats/{chat}/messages/{message}` handler, and `chatd.EditMessage`). The
chat's persisted selection never changed and the regenerated turn ran
without the newly enabled MCP tools. Two dogfood users hit this within
hours; there is no error anywhere and the UI shows the opposite of the
server state.
## Changes
- `codersdk`: add `MCPServerIDs *[]uuid.UUID` to
`EditChatMessageRequest`, mirroring `CreateChatMessageRequest` (nil
preserves the current selection).
- `chatd`: extract the send path's MCP update block into one shared
`applyRequestedMCPServerIDs` helper (explore-subagent snapshot
immutability guard plus Force On enforcement, Cure53 CDM-02-010) and
call it from both `SendMessage` and `EditMessage`, so enforcement cannot
drift between the two paths.
- `coderd`: extract the send handler's request validation (dedupe,
enabled-in-organization check, persisted-ID exemption) into
`normalizeRequestedChatMCPServerIDs` and wire it into the edit handler,
which now threads the selection into `EditMessageOptions`.
- Frontend: the edit request now includes `mcp_server_ids:
[...effectiveMCPServerIds]`, exactly like the send path, making the
picker's displayed state real.
- `make gen` artifacts (swagger, API docs, `typesGenerated.ts`).
## Tests
Each layer is covered and was proven with independent red toggles
(removing one layer's wiring fails only that layer's tests):
- chatd (`TestEditMessage_MCPServerIDs`): edit applies a provided
selection, nil preserves it, an emptied list cannot remove a `force_on`
server, and explore subagent chats keep the spawn-time snapshot.
- API (`TestPatchChatMessage/MCPServerIDsApplied`,
`MCPServerIDsInvalidRejected`): persistence via the endpoint, omission
preserves, unknown IDs get the same 400 as the send path.
- Storybook (`EditAppliesMCPServerSelection`): toggling a server on
during an edit puts it in the edit request payload.
Note: the `AgentChatPage.stories.tsx` story "Queued For Capacity After
Polling" fails locally on current main as well (verified against the
main baseline with this branch's changes reverted); it is unrelated to
this diff.
Remote dogfood UAT ran on the exact head and passed, including proof
that the regenerated turn actually gains the newly enabled MCP server's
tools.
> 🤖 Xum acted on Mike's (@ibetitsmike) behalf. • Model:
`anthropic:claude-fable-5`
<!-- xum-attribution: model=anthropic:claude-fable-5 -->
|
||
|
|
a48aedd924 |
fix(coderd/x): surface Gemini malformed-function-call stream deaths as retryable errors (#28470)
## Problem
When Gemini's OpenAI-compatible endpoint rejects a model-generated
function call server-side, it ends the SSE stream cleanly with the
nonstandard finish reason `function_call_filter:
MALFORMED_FUNCTION_CALL` after streaming only thought summaries; the
rejected call never reaches the wire. chatd treated this as a normal
completion: fantasy maps the unrecognized finish reason to `unknown`,
the reasoning block never closes (the only non-thought delta is the
`</thought>` marker, which the transport seam strips to an empty string,
and the openaicompat hook only ends reasoning on a non-empty content
delta), so the step accumulates no content and the generation loop
finishes the turn as complete. The user sees the model think for ~40
seconds and then nothing: no assistant message, no `last_error`, chat
status `waiting`. Observed twice in a row in production on
gemini-3.7-flash, with "Resume" reproducing it identically.
## Fix
Two independent layers:
- `coderd/x/googleopenai`: the stream rewrite now converts any chunk
whose `finish_reason` starts with `function_call_filter` into an
OpenAI-style SSE `{"error": ...}` event embedding the raw reason.
openai-go turns error-bearing events into stream errors, so the failure
rides the existing stream-error path instead of ending the stream
cleanly.
- `coderd/x/chatd/chaterror`: classifies that injected error as
retryable (kind `generic`, provider `google`) with a clear user-facing
message, so the existing generation retry machinery re-runs the step and
persists a `last_error` if retries exhaust.
- `coderd/x/chatd/chatloop`: provider-agnostic guard: a step that
produced no user-visible content and no tool calls under a finish reason
of `unknown`, `error`, `other`, or `tool-calls` (a tool-calls finish
that delivered zero calls) now returns a retryable error instead of
silently completing the turn. `stop` and `length` finishes keep their
existing semantics.
Tests cover the seam rewrite (live-capture SSE shape plus standard
finish reason passthrough), the new classification, and the chatloop
guard (error cases plus preserved stop, length, text, and tool-call
behavior). Each layer was red-green verified independently.
Remote dogfood UAT ran against this exact commit: normal reasoning and
tool-call chats on a real model complete cleanly with no spurious guard
errors and no retry loops. The Google-side failure itself is not
deterministically triggerable against live Gemini and is owned by the
unit tests.
> Xum acted on Mike's (@ibetitsmike) behalf.
<!-- xum-attribution: model=claude-opus, thinking=enabled -->
|
||
|
|
76962273d8 |
feat: add coder organizations edit for default member roles (#28227)
`default_org_member_roles` was only settable through Terraform or a raw
`PATCH /api/v2/organizations/{organization}`. This adds a CLI
equivalent.
```console
coder organizations edit --org acme --default-org-member-roles organization-workspace-access,organization-template-admin
coder organizations edit --org acme --default-org-member-roles "" # grant no roles
```
- The flag replaces the organization's current list rather than adding
to it. It accepts a comma-separated list, may be repeated, and
de-duplicates. An empty value removes every role.
- Removing a role prompts for confirmation, listing the roles being
removed, unless `-y` is passed. Adding roles applies without a prompt.
- The resulting set is printed, so no follow-up command is needed to
confirm what was applied.
- The organization is chosen with `-O/--org` or `$CODER_ORGANIZATION`,
and is implicit when the caller belongs to exactly one organization.
The org update endpoint is behind `FeatureMultipleOrganizations`, so the
command is enterprise/premium only. Non-built-in roles are rejected by
the server, and `dbauthz` already prevents a caller from setting a
default they could not assign to a member individually.
<details>
<summary>Implementation notes</summary>
## Shape
`codersdk.UpdateOrganizationRequest.DefaultOrgMemberRoles *[]string` and
the enterprise `patchOrganization` handler already existed; only the CLI
was missing, and nothing in `cli/` called `UpdateOrganization` at all.
No server or SDK changes are needed.
`organizations edit` follows the partial-update pattern of `coder groups
edit`: check `ParsedFlags().Changed(...)`, populate only the fields the
user set, and leave the rest nil so the handler preserves them. Passing
no flags is an error.
Two earlier shapes were tried and dropped:
- **A `default-org-member-roles` entry in the `organizations settings`
registry.** Every sibling entry unmarshals an SDK type that maps 1:1 to
a dedicated endpoint; this field has no such endpoint. Reusing
`UpdateOrganizationRequest` for the piped JSON meant a stray `name`
field could rename the org, and the documented `show > file`, edit, `set
< file` workflow failed on the default organization, whose name is
reserved.
- **A `--default-org-member-roles` flag on `organizations create`.**
`create` cannot set display name, description, or icon either, so a
roles-only flag there is asymmetric. It would also require
`CreateOrganizationRequest` to grow the field plus server-side
validation.
## Known sharp edge
`serpent.StringArray` resets its slice when any flag value is empty, so
`--default-org-member-roles a --default-org-member-roles ""` clears the
list rather than keeping `a`. This is pinned by a test and called out in
a code comment. The confirmation prompt catches it interactively; a
script passing `-y` would not. A custom flag value that ignores empty
occurrences was prototyped and reverted as more machinery than the
behavior warranted.
## Validation
Manually verified against a local dev deployment with a premium license:
setting, adding, clearing, prompt accepted and declined, duplicate and
whitespace handling, invalid role rejection, and a plain member being
denied. Confirmed with `/authcheck` that a member loses workspace-create
access when the roles are cleared and regains it when restored, and that
the change is recorded in the audit log.
## Follow-ups worth considering
- `--name`, `--display-name`, `--description`, and `--icon` on
`organizations edit`; all already exist on `UpdateOrganizationRequest`.
Rename deserves its own discussion.
- Setting the defaults to `organization-admin` makes every member an org
admin, including the ability to edit the org further, and those users
still render as plain members in member listings. Reachable today via
the API, UI, and Terraform; a warning on privileged roles would help.
- `dbauthz.InsertOrganization` has no `canAssignRoles` check, unlike
`UpdateOrganization`. Harmless while the only caller passes the
deployment default constant.
- `docs/admin/users/organizations.md` still points users at the
dashboard or a raw `PATCH`.
</details>
---
Created by Coder Agents on behalf of @jscottmiller.
|
||
|
|
6a58d50f1d |
feat(coderd): add queue-position-free provisioner job query (#28367)
Follow-up to #28302 ([GRU-82](https://linear.app/codercom/issue/GRU-82), [RFC](https://app.notion.com/p/coderhq/Lite-Workspace-API-Requests-3aad579be5928060b5e7cc65c539717f)). That PR added related-data selection but noted that selecting `latest_build.job` still ran the expensive `GetProvisionerJobsByIDsWithQueuePosition` query, which computes queue position and size with window functions over pending jobs and provisioner daemons and is consistently the top resource consumer in scale tests. This PR adds a cheaper `GetProvisionerJobsByIDs` query that fetches jobs by ID without the queue-position computation, and selects between the two based on whether `latest_build.job.queue_position` is requested. When the queue position is not selected, `QueuePosition` and `QueueSize` are left zero and the rest of the response is unchanged. Both paths go through a single `provisionerJobsByIDs` API method that takes the `jobRelated` selection, switches on it, and reshapes the cheaper query's rows into `GetProvisionerJobsByIDsWithQueuePositionRow` so downstream conversion is uniform. The new query has the same dbauthz properties as the queue-position variant (system-level pass-through pending the proper provisioner-job RBAC check tracked in #16160), with a matching authorization test. No behavior change for existing callers: they select `queue_position` (via `allLatestBuildRelated`), so they continue to use the queue-position query. --- *Generated by Coder Agents on behalf of @spikecurtis.* |
||
|
|
990a7c64f3 | fix(site): show "Never" for unused tokens (#28453) | ||
|
|
3bdd395faa |
docs(docs/admin/templates/extending-templates): clarify coder_env merge_strategy does not include host environment (#28466)
`merge_strategy` on `coder_env` only combines values across `coder_env` resources targeting the same name. It does not know about the value already set by the workspace's image or host, such as the base `PATH`. A user hit this in #27501: appending to `PATH` with three `coder_env` resources replaced the host's `PATH` entirely, since none of them referenced the existing value. Clarify this in the docs, and update the `PATH` example to show referencing `$PATH` in one of the resources so the host's original value is kept. Refs #27501 <details> <summary>Investigation notes</summary> - `coderd/provisionerdserver/provisionerdserver.go` builds the merged env starting from an empty map (`MergeExtraEnvs`), only combining `coder_env` resources with each other. - `agent/agent.go`'s `updateCommandEnv` applies the result with `os.ExpandEnv(v)`, which expands `$VARNAME` against the agent's real process environment. - Confirmed with a standalone repro that `value = "$PATH:/usr/local/cuda/bin"` preserves the host's real `PATH` and appends the extra directory, and that this composes correctly across multiple `coder_env` resources with `merge_strategy = "append"` as long as one of them references `$PATH`. - A companion PR updates the `terraform-provider-coder` schema description and example that feed the registry docs: https://github.com/coder/terraform-provider-coder (branch `bpmct/docs-env-merge-host`). </details> --- *Opened by Coder Agents on behalf of @bpmct.* --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
3c89b09734 | fix(site/src/pages/TemplateBuilder): validate customizations form before submit (#28081) | ||
|
|
9b5f47eeb7 |
fix(site/src): repair failing Storybook stories (#28462)
## Summary Repair the Storybook interaction and Pixel failures currently present on `main`. The failures came from organization-scoped model changes leaving stories with incomplete providers and query fixtures, product copy and route changes leaving stale assertions, and several interaction tests depending on implementation details or teardown timing. ## Broken stories and fixes ### Organization-scoped chat models PR #27960 introduced organization-scoped chat models and the following regressions: - `OrganizationModelsLayout / Switch Organization Preserves Auxiliary Parameters`, `Invalid Requested Organization Falls Back To Default`, `Invalid Requested Organization Denies Add`, `Duplicate Display Names Are Disambiguated`, and `No Readable Organization Is Not Found`: the stories only populated permission query keys for individual organizations, while the accessible-organization lookup requests authorization for all visible organization IDs together. The unmatched `/api/v2/authcheck` request returned a Storybook proxy 502, so the layout rendered an error instead of the intended picker, fallback, denied, disambiguation, or not-found state. Add fixtures for the combined organization-permission keys, preserve the intentionally denied permission map, and return an explicit empty authorization result for the no-readable-organization case. - `ModelFormProviderConfig / Provider Config Open AI`, `Provider Config Anthropic`, and `Provider Config Open AI Web Search`: `ModelForm` began consuming `OrganizationModelsContext`, but these stories were not wrapped in its provider and rendered the router error boundary. Add the same organization-model context decorator used by the sibling model form stories. - `AgentChatPage / Queued For Capacity After Polling`: the story retained a manually assembled chat-and-messages fixture after the page gained organization-model, provider, workspace, prompt, diff, chat-list, and authorization dependencies. Those missing queries prevented the polling request from being reached. Replace the partial fixture list with the shared `buildQueries()` setup. - `DashboardLayout / Custom Organization Role Can Open Models`, `DashboardLayout / ACL Readable Member Can Open Models`, and `NavbarView / For Member With Model Access`: these stories also landed in #27960 and inherited Pixel's tablet-and-desktop matrix, but their play functions exercise the desktop `Models` link. Pixel's 744px tablet viewport renders that link inside the closed mobile menu, so the desktop query always failed there. Restrict these authorization-to-navigation stories to the desktop matrix; mobile Models navigation remains covered by the dedicated `MobileMenu` story. ### Premium copy and navigation - `DeploymentSidebarView / Premium Tab Visible` and `Premium Tab Hidden`: PR #28226 renamed the production navigation item from `Premium` to `Trial Upgrade`, but added stories that still queried the old name. Update both the positive and negative assertions so the hidden-state story cannot pass while the real CTA is present. - `PremiumPageView / No License`: PR #28226 changed the production heading to `Start an unlimited 30-day Coder trial` while the story asserted the previous Premium wording. Update the accessible heading assertion to the rendered copy. - `AgentChatPageView / Queued For Capacity Community Admin`: PR #28437 intentionally moved the trial CTA from `https://coder.com/trial` to the internal `/deployment/premium` route, leaving the story's href assertion stale. Update the expected route while retaining the link-name and callout checks. ### MCP server refetch behavior - `AgentCreateForm / MCP Servers Error Shows Alert And Disables Send` and `MCP Servers Refetch Error Keeps Send Enabled`: the MCP coverage was introduced in #27942. PR #28442 later added a second unconditional MCP `ErrorAlert`, so a background refetch error appeared even when cached MCP data remained usable. The refetch story also called `refetchQueries()` without a key, which began refetching unrelated active model queries as the form's query surface expanded and produced unmatched API failures. Remove the duplicate unconditional alert, refetch only the organization's MCP query, and use semantic alert and heading assertions. Initial-load failures still disable Send, while background failures with cached data keep Send enabled without replacing the form with an error. ### Interaction and teardown stability - `IconField / Open Picker`: PR #27674 changed this story to wait for the `em-emoji-picker` custom element. That implementation-specific query races the lazy-loaded picker chunk and violates the component's observable contract. Keep the button state assertion and wait for the visible dialog instead. - `AgentChatPage / Slash Compact Command Submits` and `Slash Compact Yields To Personal Skill`: the command story added in #27081 waited on cmdk's `Commands` group heading, which is accessibility-hidden, while the skill variant queried raw implementation text. Menu placement and visibility are asynchronous, especially after the positioning changes in #28411. Wait for the visible selectable options by role before pressing Enter. - `AgentChatPageView / Terminal Focus On Tab Switch`: the focus coverage added in #24677 exposed an xterm teardown race rather than a product navigation regression. xterm queues its initial viewport synchronization, but Storybook could synchronously dispose the terminal first, leaving the queued callback to read a cleared renderer and report an unhandled error. Clear React state immediately, defer xterm disposal by one timer turn, query the labeled terminal textbox semantically, and remove the unnecessary empty WebSocket message fixture. |
||
|
|
042421694f |
fix: surface duplicate AI provider hostname as warning, not error (#28208)
Two live `ai_providers` rows can share the same `base_url` hostname.
This is an intended configuration (AIGOV-97): the direct routing path
(`/api/v2/ai-gateway/{name}/...`) routes by provider name and fully
supports it. Only the MITM proxy path (`aibridgeproxyd`) cannot
disambiguate, because its `nameByHost` map is 1:1.
`classifyProviderRow` marked the second provider as
`ProviderStatusError`, which surfaced as a misleading log warning and a
`status=error` Prometheus label. The provider is not actually broken; it
is fully routable via the direct path. The error was only visible in
server logs and Prometheus, not in the API or UI.
This change:
- Extracts `BaseURLHostname` into `coderd/aibridged` and eliminates
three duplicate implementations (proxy classifier, chatprovider, and the
API layer now uses it for status checks).
- Downgrades the proxy classifier from `ProviderStatusError` to a new
`ProviderStatusProxyExcluded` status. The provider is not broken; it is
just not reachable via the MITM proxy path.
- Adds `Status *AIProviderStatus` to `codersdk.AIProvider` with
`Warnings []string`, matching the `WorkspaceProxy` precedent.
- Populates warnings on create, update, GET, and list when a provider's
hostname collides with another enabled provider.
- Adds an integration test proving that a duplicate provider is routable
via the direct path but not via the proxy path.
Refs AIGOV-596
Generated by Coder Agents
https://github.com/user-attachments/assets/bf323837-5121-4d1b-ab19-593f1d32374e
|
||
|
|
defd5d48a1 |
docs: clarify that Coder server should run on a separate machine (#27468)
Closes [DOCS-600](https://linear.app/codercom/issue/DOCS-600/make-it-clear-in-the-quickstart-that-you-should-install-the-coder). The Quickstart guide has readers install and run `coder server` on the same machine they're working from, without calling out that this is only for a quick trial. Coder's actual value comes from remote development, so hosting the Coder server on a separate machine has a lot more power than running it locally. This adds two notes to `docs/get-started/index.md`: - In **Prerequisites**, explaining that this quickstart runs the server locally for convenience, but a real deployment should host it on a separate machine, and linking to the [Install](https://coder.com/docs/install) page. - In **Step 2: Install and start Coder**, a shorter reminder right before the install commands. ---- Created using Coder Agents 🤖 --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
df6a33ad37 |
docs: fix broken extending-templates examples (dynamic-parameters, docker-in-workspaces) (#28100)
## Summary Fixes broken examples on the `extending-templates` pages surfaced by the DOCS-637 runtime drift sweep, and completes the dangling `external-auth` reference. Verified against `main` and the CLI source (`coder templates init --id`, `coder templates push`). ## Changes | Page | Fix | |------|-----| | `dynamic-parameters.md` | The **Dynamic Validation** snippet referenced an undefined `show_cpu_cores` through a stray `count = data.coder_parameter.show_cpu_cores.value ? 1 : 0` line copied from the earlier Hide/Show Options example. Standalone copy failed `terraform validate`. Removed the `count` line (and its misleading "Only show this parameter" comment) so the snippet is self-contained. The valid occurrence in the Hide/Show example (where `show_cpu_cores` is defined) is untouched. | | `docker-in-workspaces.md` | The rootless Podman step used `echo "kubernetes-with-podman" \| coder templates init`, but `kubernetes-with-podman` isn't a built-in starter id, so the command scaffolds nothing. Step 4 now starts from the built-in `kubernetes` starter (`coder templates init --id kubernetes`) and shows the minimal Podman delta inline: the maintained `codercom/example-podman:ubuntu` base image, the `container.apparmor.security.beta.kubernetes.io/dev = unconfined` pod annotation, and a `github.com/fuse` device limit. The starter already runs as UID/GID 1000 with `run_as_non_root`, which rootless Podman needs. Also switches the deprecated `coder templates create` to `coder templates push`. Follow-up from review: swapped `ghcr.io/coder/podman` for the maintained `codercom/example-podman:ubuntu` image and linked its source, added a note that the AppArmor annotation is deprecated on Kubernetes 1.30+ (`securityContext.appArmorProfile`), and added an EKS Auto Mode caveat to the Bottlerocket section. | | `external-auth.md` | The **External Auth Providers** sentence dead-ended at "...in the Coder Control Plane. See" with no link. Completed it with a link to the admin External Authentication page (`../../external-auth/index.md`), the target chosen in review. | ## Dependency The `docker-in-workspaces.md` example now references `codercom/example-podman:ubuntu`, published by coder/images#339. That PR is approved but should merge (and publish the image to Docker Hub) before this PR merges, so the docs don't point at an image that doesn't exist yet. Linear: https://linear.app/codercom/issue/DOCS-645 > This PR was created with AI assistance (Coder Agents). --------- Co-authored-by: Ben Potter <me@bpmct.net> Co-authored-by: Ben Potter <ben@coder.com> |
||
|
|
54d4ae9aca | fix: detect Xum client in aibridge after Mux rename (#28448) | ||
|
|
c275327fb7 |
feat: audit operational agent settings (#28369)
Depends on #28394. Audit changes to seven operational agent settings through a typed `chat_operational_settings` resource. Each handler serializes baseline capture and mutation with a per-setting advisory lock, keeps both operations in one transaction, and records field-specific diffs. Effective no-op requests now skip both the database upsert and the success audit, including absent rows whose effective value is already the default. The change also retains the allowlisted raw `site_configs` query, updates generated audit and API surfaces, adds failure and concurrency coverage, and reuses the existing AuditPage resource-filter story pattern. Relates to CODAGT-720. |
||
|
|
2aba2685ad |
fix: require audit capture for chat settings (#28394)
Existing chat instruction setting handlers could complete a mutation without an audit event when advisory locking or old-state capture failed. Make system prompt and plan mode instruction updates fail closed: baseline capture and mutation stay in one transaction, audit infrastructure failures reject the request without changing the setting, and focused tests cover lock, read, transaction, and write failures. |
||
|
|
0ef331fcd0 |
feat!: scope chat model override settings to organizations (#28442)
Moves chat model override settings from deployment scope to organization scope, following the org-scoping of chat model configs in #28440. Depends on #28440 (based on `ethan/chat-models-availability-consolidation`). Remote dogfood UAT passed on this change, including multi-organization scenarios on a licensed deployment with real models. ## Problem Chat model configs are now owned by organizations, but the override settings that reference them stayed deployment-wide: admin subagent/title/compaction overrides in `site_configs`, personal overrides in `user_configs`, and the advisor model embedded in the advisor runtime JSON. A single deployment-level override can only point at one organization's model config, so overrides effectively worked for the default organization only, and title generation had to soften its hard-failure contract to tolerate unusable overrides. ## Fix Admin overrides (general, explore, title generation, compaction, advisor) and personal overrides (root, general, explore) become organization-scoped rows in two new typed tables, `chat_organization_model_overrides` and `chat_user_model_overrides`. Composite foreign keys on `(organization_id, model_config_id)` make cross-organization model references unrepresentable. The migration deletes the legacy serialized `site_configs`/`user_configs` override keys instead of migrating them; admins and users re-select models in the new organization-scoped settings. New endpoints live under `/api/experimental/organizations/{organization}/chats/model-overrides` (admin defaults) and `/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides` (personal overrides); the legacy deployment-level routes are removed. chatd resolves overrides via the chat's organization, restoring the hard-failure contract for configured-but-unusable title generation overrides. The deployment-level `AllowUsers` switch and advisor runtime limits stay deployment-scoped. The Coder Agents settings UI gains a per-organization "Defaults & overrides" page under AI Settings > Models, and the personal overrides page gains an organization selector for users in multiple organizations. Advisor telemetry now reports per-organization override rows. **BREAKING CHANGE**: Existing deployment-level overrides are dropped, not migrated. Every organization starts with no overrides after upgrade, and model selections must be re-applied in the new settings. The legacy deployment-level override endpoints are removed. Closes https://linear.app/coder/issue/CODAGT-872 > [!NOTE] > Xum (AI agent) authored this PR on behalf of @ibetitsmike. <!-- xum-attribution: model=claude-opus-4-6 thinking=high --> --------- Co-authored-by: Ethan Dickson <ethan@coder.com> |
||
|
|
3809314b31 |
refactor!: consolidate chat model availability (#28440)
## Summary Consolidate chat model management and runtime availability into a single organization model collection contract. The organization model response now includes provider availability, unavailable reasons, and unsupported-provider details, so callers no longer need a separate availability request. Depends on #28439. ## Problem The organization-scoped API exposes two overlapping representations of the same chat models: ```text GET /api/experimental/organizations/{organization}/chats/models GET /api/experimental/organizations/{organization}/chats/models/available ``` The collection route returns organization-owned model configurations and redacted provider descriptors. The availability route returns a second catalog-oriented response with provider availability and synthetic catalog entries. Callers must fetch, reconcile, and cache both responses even though they describe the same effective set of models. This creates duplicated server logic and separate SDK, OpenAPI, and frontend types. It can also produce inconsistent client state when one request succeeds while the other fails or when the two responses are refreshed at different times. ## Fix Return all model-management and runtime-availability information from the organization model collection route: ```text GET /api/experimental/organizations/{organization}/chats/models ``` `OrganizationChatModelsResponse` continues to return the caller-readable organization model configurations and redacted provider descriptors. Provider descriptors now also include: - `available` - `unavailable_reason` The response additionally includes `unsupported_providers`, allowing clients to explain configurations that the Agents harness cannot use. Frontend model settings, agent creation, existing chat recovery, and model override surfaces now consume this single collection query. This keeps model ownership, caller visibility, provider availability, and unsupported-provider guidance in one cache entry and one authorization path. The default-organization compatibility route remains available: ```text GET /api/experimental/chats/models ``` It now returns the same consolidated `OrganizationChatModelsResponse` contract instead of the former availability response. ## Breaking change This removes the separate organization model availability endpoint: ```text GET /api/experimental/organizations/{organization}/chats/models/available ``` Clients using that endpoint must migrate to: ```text GET /api/experimental/organizations/{organization}/chats/models ``` This PR also removes the corresponding public experimental client and schema surface: - `ExperimentalClient.ChatModelAvailability` - `ChatModelAvailabilityResponse` - `ChatModelProvider` - `ChatModelCatalogEntry` - The generated OpenAPI schema and frontend API/query types for the availability endpoint Callers must use `ExperimentalClient.ChatModels` and `OrganizationChatModelsResponse` instead. Availability is now reported on each `ChatModelProviderDescriptor`, and unsupported providers are returned in `OrganizationChatModelsResponse.UnsupportedProviders`. The default-organization compatibility route retains its URL, but its response schema changes from `ChatModelAvailabilityResponse` to `OrganizationChatModelsResponse`. Direct HTTP clients and generated clients that decode the previous response type must be updated even if they do not use the removed organization-scoped `/available` route. |
||
|
|
a53c6e6366 |
feat: audit chat model config changes (#28439)
Adds audit logging for chat model config creates, updates, deletes, ACL changes, and automatic default-model transitions. Audit records use the config display name or ID as the target and are emitted only after successful transactions. Depends on #27960. |
||
|
|
75ad45109d |
feat: use organization-scoped chat models (#27960)
Move chat model settings to `/ai/settings/organizations/:organization/models`, add an organization switcher, and redirect legacy settings paths. The pages use organization permissions for read, create, update, and delete controls. They use redacted provider descriptors. Agent creation loads models for the selected organization. Existing chats load models for the chat organization. Chat views warn about unavailable historical models, select a usable local recovery model, and disable generation when none exists. Settings that aggregate organizations retain successful results when one request fails. Depends on #27959 _This pull request description was generated by Coder Agents._ |
||
|
|
aefc761fcf |
feat!: enforce organization scope for chat models (#27959)
## Summary Move chat model management and runtime availability to organization-scoped API routes. The API now identifies the organization in every model-management operation, applies chat model RBAC and ACLs within that organization, and returns only models the caller can use there. Depends on #27958. ## Problem Chat model configurations now belong to organizations, but the existing experimental management and discovery APIs are deployment-shaped. Their routes do not identify an organization, SDK methods do not accept an organization ID, and item operations address a model configuration by ID alone. That contract cannot safely expose organization-owned model configurations. It also does not provide the organization-specific provider information, authorization behavior, ACL management, or recovery guidance needed by the settings and chat clients later in the stack. ## Fix Add organization-scoped chat model endpoints under: ```text /api/experimental/organizations/{organization}/chats/models ``` The new API provides: - Organization-scoped model listing and creation. - Model get, update, and delete operations scoped by both organization and model ID. - Model ACL read and update operations. - Organization-scoped runtime availability and provider status. - Redacted provider descriptors for model-management clients. - Chat model RBAC and row ACL enforcement for writes, runtime discovery, and Chatd model selection. - Recovery guidance when a chat's organization has no usable local model. Historical chats can retain a model reference owned by another organization, but runtime selection uses an authorized local default when one is available. Foreign, inaccessible, disabled, and otherwise unusable models are not exposed as usable selections for the organization. The existing collection and availability routes remain as default-organization compatibility endpoints in this layer: ```text GET /api/experimental/chats/model-configs POST /api/experimental/chats/model-configs GET /api/experimental/chats/models ``` These compatibility routes do not provide access to models in non-default organizations. The previous unscoped item update and delete routes are removed because item operations must include the owning organization. ## Breaking change This changes the experimental HTTP API and Go SDK contracts for chat model management. Clients must migrate model-management and availability requests to the organization-scoped routes and provide an organization identifier. In the Go SDK, the following methods now require an organization ID: - `ChatModelAvailability` - `ChatModels` - `CreateChatModel` - `UpdateChatModel` - `DeleteChatModel` `ChatModels` also changes its return value from `[]ChatModel` to `OrganizationChatModelsResponse`, which includes the organization models and the redacted provider descriptors required by management clients. New item and ACL methods likewise require both the organization ID and model ID. The default-organization compatibility routes reduce the immediate impact for collection and availability callers, but they do not preserve the previous item API or SDK method signatures. Generated clients and direct consumers of the experimental API must be updated before adopting this change. _This pull request description was generated by Coder Agents._ |
||
|
|
9bd8899359 |
refactor: clarify chat model resource names (#27958)
Rename the admin-managed SDK resource from `ChatModelConfig` to `ChatModel`. Rename catalog entries from `ChatModel` to `ChatModelCatalogEntry` and replace `ChatModelsResponse` with `ChatModelAvailabilityResponse`. API routes and JSON fields do not change. Experimental SDK and OpenAPI schema names do change. Depends on #27957 _This pull request description was generated by Coder Agents._ |
||
|
|
cbe24a3da5 |
feat: add chat model config RBAC resource (#27957)
Add the `chat_model_config` RBAC resource, actions, and API key scopes. This layer includes the scope migration and generated API types. Model list queries apply token scopes, SQL authorization filters, and row ACLs. Write and default-management handlers keep deployment configuration authorization until the next API layer replaces it. Depends on #27956 _This pull request description was generated by Coder Agents._ |
||
|
|
ac41b5b4f9 |
feat(coderd): include chat model organization IDs in telemetry (#27956)
Include the owning organization ID for each chat model configuration in telemetry snapshots. Depends on #27955 _This pull request description was generated by Coder Agents._ |
||
|
|
f32592ec6b |
feat!: scope chat model configs to organizations (#27955)
## Summary Scope chat model configurations and runtime model resolution to organizations. Each model configuration now belongs to exactly one organization, each organization can have its own default model, and Chatd only uses models that belong to the chat's organization. Depends on #27968. ## Problem Chat model configurations are currently deployment-wide even though chats belong to organizations. A default model, explicitly selected model, or configured model override can therefore reference the same model configuration from every organization. This prevents model catalogs and defaults from being isolated by organization and allows a chat to retain or select a model that its organization does not own. Existing model configurations also have no organization ownership or organization-scoped ACL data, and the database enforces a single live default across the entire deployment. ## Fix Add required organization ownership and ACL storage to chat model configurations. The migration assigns every existing configuration to the default organization, seeds organization-wide read access, and replaces the deployment-wide default constraint with a per-organization constraint. Scope Chatd lookup, admission, fallback, and queue-promotion behavior to the chat's organization: - New chats use the usable default model from their organization. - Explicit model selections from another organization are rejected. - Deployment or personal overrides that point to a foreign model are not used for the chat. - Historical cross-organization model references remain unchanged in the database, but runtime generation falls back to a usable local default. - Queue promotion resolves unavailable, disabled, missing, or foreign stored models to the usable local default and fails when no usable local default exists. The existing model configuration management and availability routes remain compatibility routes for the default organization in this layer. A later layer in the stack adds organization-scoped management routes and updates clients to use them. ## Breaking change This changes chat model behavior from deployment-wide to organization-scoped. Existing configurations are migrated only to the default organization; they are not copied into every organization. As a result: - A non-default organization no longer inherits the deployment's existing default model. - Creating a chat in an organization without a usable local default fails with `400: No default chat model config is configured.` - Explicitly selecting a configuration owned by another organization fails with `400: Model config does not belong to the specified organization.` - Existing chats whose stored model belongs to another organization can generate only if a usable local default is available; otherwise generation fails. Deployments using Agents in non-default organizations must configure a usable default chat model separately for each organization after the organization-scoped management layers in this stack are available. _This pull request description was generated by Coder Agents._ |
||
|
|
cd1362abfd |
fix(coderd): prevent stale chat model config writes (#27968)
Chat model updates could merge PATCH fields from a row read before the serialized transaction. Deletes could also succeed after another request deleted the row. Merge updates from the row read after the write lock. Make soft deletes report missing rows, and validate provider availability and model compatibility in the same transaction. This PR also removes `DeleteChatModelConfigsByAIProviderID`. The query has no production callers, including the AI provider deletion path, and only remained in generated database wrappers and mocks. Its removal is intentional dead-code cleanup and does not change provider deletion behavior. _This pull request description was generated by Coder Agents._ |
||
|
|
4ce8e8ed56 |
docs: address AI Gateway documentation review feedback (#28309)
Applies the review feedback from #27794. Prose only. --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
c9a1e2bcee |
fix(site): update Coder Agents license copy (#28432)
Updates the Coder Agents license card to consistently refer to concurrent agents, removes the usage management link, and links to the licensing and usage documentation. Updates Storybook coverage for the revised labels, tooltip, and link behavior. <details> <summary>Coder Agents disclosure</summary> This pull request was generated by Coder Agents on behalf of @jaaydenh. </details> |
||
|
|
5ca0143992 | refactor: upgrade golangci-lint to v2.0.2 (#28416) | ||
|
|
610f321d52 |
fix(site/src): disable archive actions for active chats (#28264)
Closes [CODAGT-632](https://linear.app/codercom/issue/CODAGT-632/gray-out-the-archive-chat-button-when-chat-is-running). The chatd state machine only permits archiving a chat family from idle execution states (`waiting`/`error`); archiving an active chat fails with HTTP 409. The agents UI still offered "Archive agent" and "Archive & delete workspace" for active chats, and `AgentsPageLayout` showed a confirm dialog claiming "Archiving it will interrupt the current run", which is no longer true: confirming just surfaced the 409 error toast. ## Changes - `ChatActionsMenuItems` gains a `chatFamilyAllowsArchive` helper mirroring the backend rule: archive is allowed only when the root chat and every embedded child are `waiting`/`error` (archive cascades atomically over the family, so an active child also rejects it). Unknown status stays fail-open with the server 409 as backstop. A new `isArchiveBlocked` prop disables both archive items; unarchive, pin, and rename are unaffected. - While blocked, the menu shows a visible hint ("Interrupt or wait for the agent to finish first.") and both disabled items reference it via `aria-describedby`. - Sidebar rows (`ChatTreeNode`, covering the dropdown and the right-click context menu) gate on the chat list record, which watch events already keep fresh. The chat top bar gates on the live stream status (`liveChatStatus`) plus the same list record's children, passed through the layout outlet context, so all three menus read one live source and re-evaluate without a reload. - Removed the stale "Archive agent?" interrupt-confirm dialog and its pending-state plumbing from `AgentsPageLayout`; archive now mutates directly. - "Archive & delete workspace" is now archive-first: the archive call is the atomic server-side eligibility check and completes before the destructive delete, so a chat that becomes active mid-flow can never lose its workspace. On delete failure the chat deliberately stays archived, with no compensating unarchive: the delete outcome can be ambiguous client-side (a late 5xx can arrive after the delete build committed), so restoring the chat could resurrect it while its workspace is being deleted. Instead the mutation refetches all chat collections on any failure and the toast directs the user to the archived filter, where Unarchive is one click. This is a deliberate departure from the self-restoring retry surface of #26900. The workspace-deletion confirm dialog is unchanged. ## Scope The client gate is best-effort UX; the server-side 409 is the correctness backstop. Rare staleness (a chat outside the loaded sidebar pages, events missed across a socket reconnect) leaves the actions enabled and the server rejects the request with the existing error toast. Cache-coherency hardening for those windows was deliberately removed to keep the change proportionate to the defect. ## Testing - Storybook play stories: disabled archive items with the accessible hint for an active chat and for an idle parent with a running child; enabled items for an idle chat; in the top bar menu and the sidebar dropdown/context menu (`ChatTopBar.stories.tsx`, `ChatsSidebar.stories.tsx`). - Unit tests: archive-first ordering, delete failures keep the chat archived and rethrow (`agentWorkspaceUtils.test.ts`). - `pnpm -C site check`, `pnpm -C site lint:types`, whole-file vitest runs for every touched test/story file. - Remote dogfood UAT on a deployment built from this branch: archive items disabled while running and in `requires_action`, re-enabled live on transition to `waiting`, archive works from `waiting` and `error` with no confirm dialog, unarchive unaffected. > Shux acted on Mike's behalf to create this pull request. |
||
|
|
7b72f5d86b |
fix(site/src/pages): keep TemplateCard official badge inline under preflight (#28433)
## Summary In the Template Builder base template select grid, each base template card's "official" checkmark badge was rendering below the card title (`h3`) instead of inline and vertically centered with it. ## Root cause Tailwind preflight was enabled in #27821 (removed `corePlugins: { preflight: false }`), which applies `svg { display: block }`. The badge relied on `align-middle`, which is a no-op on block-level elements, so the icon dropped onto its own line. The same badge in `ModuleCard` was patched in #27821 with `inline-block`, but the identical badge in `TemplateCard` was missed. ## Fix - `TemplateCard.tsx`: add `inline-block` to the badge so it flows inline within the title's text (trailing the last word and wrapping naturally), with `align-middle` handling vertical centering. This matches the existing `ModuleCard` pattern. - `Response.tsx`: update a stale comment that referenced preflight being disabled. ## Verification - `biome check` passes on both files. - Confirmed via a repo-wide sweep that no other inline icon layouts regressed from the preflight change (all other icon-next-to-text usages are protected by flex/inline-flex containers, `asChild` class merging, or are sole children). <img width="1083" height="626" alt="Screenshot 2026-08-21 at 12 02 47 PM" src="https://github.com/user-attachments/assets/2183dbf4-1f4f-4aae-92cf-21c11efebc5e" /> <details> <summary>Investigation notes</summary> Timeline: - Preflight was explicitly disabled via `corePlugins: { preflight: false }`. - The official badge (#26806, #27036, #27797) worked because the lucide SVG rendered inline while preflight was off. - #27821 ("refactor: remove MUI and Emotion") removed the `corePlugins.preflight: false` block, enabling preflight and its `svg { display: block }` rule. It patched `ModuleCard` with `inline-block` but missed `TemplateCard`. A read-only sweep across `site/src` found no additional broken inline-icon layouts. Candidates like `DERPRegionPage` (icon inside an `inline-flex` `Link`) and `TemplateCustomizationsStep` `BaseTemplateCard` (intended vertical layout) are safe. </details> --- _This PR was generated by Coder Agents on behalf of @jeremyruppel._ |
||
|
|
ee87ad6521 |
fix(site): point queued-capacity trial link to /deployment/premium (#28437)
Points the "Start an unlimited trial" link in the concurrency-limit queued callout (`QueuedForCapacityCallout`) to the internal `/deployment/premium` route instead of the external `https://coder.com/trial` URL, matching the convention used elsewhere in the app (e.g. `LicensesSettingsPageView`, `Paywall`). Uses `Link asChild` + `RouterLink` so navigation is client-side (SPA) instead of a full page reload. Also fixes all four links in the callout (`Start an unlimited trial`, both `learn more` links, and the `sales@coder.com` mailto) rendering at 12px (`size="sm"`) while the surrounding message text is 14px. Dropping the explicit `size="sm"` lets the links fall back to `Link`'s default size, matching `AlertDescription`'s `text-sm` (14px). <details> <summary>Decision log</summary> - Confirmed `/deployment/premium` is an existing internal route (`PREMIUM_PAGE_PATH` constant in `Paywall.tsx`), not an external link. - Existing pattern for internal links elsewhere in the codebase is `<Link asChild showExternalIcon={false}><RouterLink to="..." /></Link>`, so this change follows that convention rather than using a plain `<a href>`. - Only the admin-on-Community-license path (`canManageLicenses && !hasLicense`) is affected by the route change; the member and paid-license admin messages are unchanged. - Verified in a local `develop.sh` dev instance: HMR picked up the change cleanly, TypeScript reports 0 errors, and the links now render at 14px matching the message body. </details> --- PR generated with Coder Agents. |
||
|
|
cb52f5339d | feat: show a premium paywall on the external auth settings page (#28435) | ||
|
|
9ce42293b0 | fix(site/src): use the browser-only bullets from the copy doc (#28434) | ||
|
|
b8fcdbd32f | fix(site/src): sync paywall copy with the latest copy doc (#28436) | ||
|
|
a2afb458c5 |
feat: add premium paywall conversion telemetry (#28425)
**TLDR:** When someone using free Coder taps a "Start trial for free" button, we now write down which page they tapped it on, and whether they went on to actually start the trial. That tells us which locked features make people want Premium. ## Brief summary - Records two things: the button being clicked, and a trial actually being started. - Each click gets a random ID that travels with the person to the trial form, so we can tell who finished from who walked away. - Records a short label for the page (like `appearance` or `audit_log`), never the web address, so no customer names are collected. #28226 added the trial form and has since merged, so this now targets `main`. ## Description Today the "Start trial for free" button just moves you to the Premium page and nothing is recorded, so there is no way to know which feature convinced someone. This adds two events: | Event | Happens when | |---|---| | `cta_click` | Someone clicks "Start trial for free" | | `trial_signup` | A trial licence is actually issued | The click generates a random ID. It rides along to the trial form and gets saved on the signup, so a click and a signup can be matched up later. Clicks with no matching signup are the people who gave up. Trials started without a paywall (for example straight from the sidebar) are labelled `direct`, so "no label" never gets confused with "broken". A few small choices worth knowing: - The ID is kept in browser session storage, not in the web address, so the URL stays clean. It expires after 30 minutes. - The trial signup event is recorded by the server, not the browser, so nobody can fake a signup. - The browser cannot say which kind of event it is sending. It can only report a click; the server labels it. - Only people allowed to start a trial are counted. The server rejects the rest, which matters because a few paywalls show their button to everyone. - Nothing is recorded at all when telemetry is turned off. There is deliberately no "paywall was shown" event. Every admin page visit would report one, which is a lot of noise for little insight. Every paywall in the [Premium Copy Updates](https://www.notion.so/coderhq/Premium-Copy-Updates-3c1d579be59280a59557cabd54fd8a1f) table has a label. The rows that do not are badges and alerts with no button to click. ## Proof Ran a local Coder with no licence and clicked the paywall button on the Appearance page. What the browser sent, and the reply: ```json POST /api/v2/deployment/premium-funnel-events -> 204 No Content {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","variant":"premium"} ``` What the browser saved for the trial form, same ID: ```json {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","createdAt":1787268520857} ``` The click then landed on the Premium page with the trial form. Screenshots of the network panel, the saved value, and the Premium page are attached below. The form itself was not submitted, since that would contact the real licence server. ## Testing - Backend tests: a click is recorded with the server's own label, an unknown page label is refused, and a normal member is refused. - Storybook tests: button clicked and ID saved on all three paywall styles, plus the case where the viewer only sees guidance text and has nothing to click. - Unit tests for the saved ID: expiry, bad data, unknown page label, and the `direct` fallback. - `make gen` is clean, and lint, types, and formatting pass. Two stories fail on `main` today and fail the same way here: `ExternalAuthSettingsPageView > Page` and `PremiumPageView > No License`. Neither file is touched by this branch; both were verified failing on `main` with this branch checked out elsewhere. ## Follow-up coder/coder-telemetry-server#45 saves these events for reporting. It merges after this one. <details> <summary>Plan and decisions</summary> **Goal:** find out which Premium paywall drives trial signups, and tell abandons apart from completions. **Options considered and dropped:** 1. Put `?from=appearance` in the URL. Rejected: messy for users. 2. Use router state. Rejected: lost if the page is refreshed. 3. Let the browser report the signup. Rejected: it could be faked or lost during the redirect. 4. Work out the page from the URL. Rejected: URLs contain organization and template names. 5. Report every paywall impression. Rejected: one event per admin page visit is noise, and the click is the signal. 6. Separate event types and tables. Rejected: makes the matching query harder for no gain. **Event shape:** ```go type PremiumFunnelEvent struct { ID uuid.UUID // event ID; for a click this is also the matching ID EventType string // cta_click | trial_signup, set by the server Source string // appearance, audit_log, ..., direct Variant string // which paywall design was shown AttributionID uuid.UUID // the click this signup came from UserID uuid.UUID CreatedAt time.Time } ``` **Where the code lives:** nothing under `components/` imports from `modules/`, so the paywall components stay presentational and only gain an `onCTAClick` prop. New wrappers in `modules/paywall/` own the reporting, one per paywall style, each taking a fixed `source`. **Self-review:** the `frontend-review` skill caught that reading permissions inside the wrapper forced an auth context into presentational page views, which broke their stories. Every call site already passes `canViewPremium={permissions.viewAllLicenses}` and the button only renders when that is true, so the wrapper no longer reads permissions and the server stays the gate. It also flagged one unattributed paywall on the Security page, now wired up as `browser_only`. </details> > Generated with [Coder Agents](https://coder.com/agents) on behalf of @david-fraley |
||
|
|
f44a3b59d7 |
feat: Premium Page CTA updates and form handling (#28226)
* Premium page has form for signing up for a trial * Input validation and normalization for trial form fields * api endpoint definition to make this processes seamless * license page updates to access `?success=true` queryparam and confetti <img width="1512" height="833" alt="Screenshot 2026-08-17 at 10 08 41 PM" src="https://github.com/user-attachments/assets/1766e724-c63a-4da9-b2bd-a98e6b66cc9b" /> <img width="1510" height="874" alt="Aug-17-2026 22-12-38" src="https://github.com/user-attachments/assets/3a2a0f35-1176-4290-aa0d-b3f7d66c15d6" /> ```mermaid sequenceDiagram autonumber actor Owner participant UI as UI<br/>TrialRequestForm + PremiumPage participant API as API<br/>api.ts + coderd middleware participant Coderd as Coderd<br/>postTrialLicense + trialer participant Licensor as Licensor<br/>v2-licensor + Postgres + pubsub + cache Note over UI: mutation.status = "idle" Owner->>UI: fills 10 fields, checks acknowledgement UI->>UI: Yup validate, strip "acknowledged", submit UI->>API: POST /api/v2/licenses/trial<br/>cookie + X-CSRF-TOKEN Note over UI: mutation.status = "pending" API->>API: verify CSRF, apiRateLimiter,<br/>apiKeyMiddleware sets actor API->>Coderd: dispatch Coderd->>Coderd: audit.InitRequest, authorize license:create,<br/>httpapi.Read validates, HasLicense is false Coderd->>Licensor: POST trial request<br/>deployment_id unspoofable, 10s deadline Licensor-->>Coderd: 200, raw signed JWT Coderd->>Coderd: ParseClaimsIgnoreNbf, uuid.Parse Coderd->>Licensor: InsertLicense, updateEntitlements,<br/>publish PubsubEventLicense, commit audit log Licensor-->>Coderd: database.License Coderd-->>API: 201 codersdk.License API-->>UI: 201 codersdk.License Note over UI: mutation.status = "success" UI->>Licensor: invalidate entitlements + licenses keys UI->>Owner: navigate /deployment/premium?success=true Note over Owner: confetti fires on the success param ``` --------- Co-authored-by: Matt Vollmer <matthewjvollmer@outlook.com> |
||
|
|
58c1248cf3 |
fix(site/src): repair failing storybook interaction tests and pixel captures (#28417)
The Storybook suites fail on main in two independent ways: three interaction tests in the locally-run Vitest suite (`pnpm vitest run --project=storybook`, which does not run in CI), and three story captures in CI's pixel snapshot job, whose failures do not fail the GitHub check (the job PATCHes `status=failed` to pixel.coder.com and still exits 0). Vitest interaction failures: * `ModelForm > Cost Estimate Shows Loading While Debounce Pending` has been broken since it landed in #28298. In add mode the model identifier field is the Known Model autocomplete, which only commits typed text to `form.values.model` on option selection or dropdown close, so typing free text left the form's model empty and the pricing section never entered its loading state. The story now selects the catalog option. This also fixes the same story's capture failure in the pixel job. * The `SecretsPageView` import error stories raced the dialog enter animation: `findByText` resolves on DOM presence, and under parallel-suite CPU load `toBeVisible()` can run while the Radix dialog content is still at `opacity: 0` from its `fade-in-0` animation. These now poll with `waitFor(...toBeVisible())`, matching the file's other import stories. Pixel capture failures: * `NavbarView > ForMCPCreateOnlyAdmin` / `ForMCPDeleteOnlyAdmin` inherit the meta's tablet+desktop matrix but click the "Admin settings" button that only exists in the desktop navbar, so their tablet captures always fail. They now pin `pixelWithDesktop` like every other admin story in the file. * `AgentsPageLayout > OrganizationScopedMCPServers` fails intermittently because `CompactOrgSelector` option names are timing-dependent: when an org icon fails to load (as in the pixel static server), the avatar fallback initials render and join the option's accessible name (`"MY My Organization 2"`), breaking exact-name queries and polluting screen-reader output. The decorative avatar is now `aria-hidden`, which was verified red-green by forcing unloadable icons locally. > [!NOTE] > Shux (AI agent) authored this PR on behalf of @ibetitsmike. <!-- shux-attribution: model=claude-opus-4-6 thinking=high --> |
||
|
|
05fcd06979 | fix(site): auto-select MCP server after successful OAuth (#28155) | ||
|
|
903d7b756d |
feat: add workspace agent session count protocol (#28125)
Phase 2 of the Scalable Approach for Adding New IDE Session Types RFC, tracked in #27410. Adds a session_counts map to Stats on the Agent API, so an agent can report a count per app name instead of the four fixed fields, which are now marked deprecated. Bumps the API to v2.11. - coderd normalizes names on ingest, caps distinct entries, and sums the overflow under unknown, so an arbitrary client name cannot blow up cardinality. - Agents predating v2.11 set the fixed fields instead, never both, and the ingest path falls back to them. Nothing sends session_counts yet. The agent starts populating it in #28338. |
||
|
|
fd09112e28 |
fix(site): right-align DateTimeRangePicker dropdown to match other dropdowns (#28428)
Follow-up to #28392. The `DateTimeRangePicker` popover opened with `align="start"` (left-aligned to the trigger), while the other dropdowns on the page use `align="end"`. This changes the `PopoverContent` alignment to `align="end"` for consistency. One-line change; no behavior or layout changes inside the dropdown itself. --- Generated by Coder Agents on behalf of @tracyjohnsonux. |
||
|
|
c767cc5198 |
refactor: normalize workspace agent session metadata (#28124)
Phase 1 of the Scalable Approach for Adding New IDE Session Types RFC, tracked in #27410. Gives agent session accounting one canonical vocabulary for app names, so later work can report a count per app without the agent and the server disagreeing about what an app is called. - Adds codersdk/appname.go: NormalizeAppName for storage and lookup, and AppFamilyName for the places that need a bounded value, such as a Prometheus label or the Connection_Type enum. - agentssh.Server keeps session counters in a map keyed by normalized name instead of three fixed atomics, dropping entries at zero. - Drops MagicSessionType. Sessions carry plain app names, and unrecognized ones flow through as themselves rather than collapsing to unknown. Wire behavior is unchanged: the agent still reports the four session_count_* fields over Agent API 2.10. |
||
|
|
3ffb266af6 |
fix(site): align settings header style and paywall spacing (#28418)
Settings pages rendered the premium upsell banner at different vertical
offsets and the IdP sync pages used a lighter heading style than the
rest of the settings UI.
Three causes:
1. The IdP Sync (`/organizations/:org/idp-sync`) and Organization IdP
Sync (`/deployment/idp-org-sync`) pages hand-rolled their headers with a
bare `<h1 className="text-3xl m-0">` (missing `font-semibold`) and a
`flex flex-col gap-12` wrapper (48px above the banner).
2. Workspace Proxies and Appearance wrapped `SettingsHeader` in flex
columns with their own `gap` (`gap-4`, `gap-8`), which stacked on top of
the header's built-in `pb-6`, pushing the banner to 40px and 56px
offsets respectively while Groups sits at 24px.
3. `SettingsHeaderDescription` used `leading-normal` (21px lines) while
the inline `Link` component ("View docs") renders a 24px-tall
`inline-flex leading-6` box, inflating the line box on pages whose
description contains a docs link and shifting content below by ~3px.
Changes:
- Replace the custom header markup on both IdP sync pages with the
shared `SettingsHeader`, `SettingsHeaderTitle`, and
`SettingsHeaderDescription` components; the Export Policy button moves
into the `actions` slot.
- Hoist `SettingsHeader` out of the gap wrappers on Workspace Proxies
and Appearance so the header's `pb-6` is the single source of
header-to-content spacing; the gap now applies only within the entitled
content.
- Set `SettingsHeaderDescription` to `leading-6` so description line
height matches the inline `Link`, whether or not a docs link is present.
All affected pages now start content, including the premium paywall,
24px below the header description.
_Created by Coder Agents on behalf of @tracyjohnsonux._
|