mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
d5e5b10ff19d2b951e984e25ea5140bb3b489fa7
15285
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d5e5b10ff1 |
feat(site): polish AI budget members table (#26805)
Finishing touches for the AI cost control group members table.
- Spend stays primary-colored until near the budget limit, via a new
AIBudgetAmount component
- "AI budget period" label shows the current spend window in local
time, next to the members tab
- Budget tooltip notes the reset date and the group's default limit
- "Budget type" renamed to "Budget group", with a badge for the
governing group or override
- Distinguishes $0 budget ("None") from no budget ("Unlimited")
- Unattributed spend from another group shows a note instead of a dash
- "Manage AI budget" disabled only when another named group governs
- Replaces UserAISpend with generated UserAISpendStatus, fixing
limit_source
Closes #26401
|
||
|
|
8fee087952 |
feat(offlinedocs): prefer front-matter title, fall back to manifest (#27167)
## Summary
Part of the docs front-matter title migration (Linear DOCS-482).
`offlinedocs` now prefers a front-matter `title` for a page and falls
back to the
manifest nav label when the page has no front matter. This keeps the
offline docs
renderer aligned with the hosted docs renderer as docs pages migrate to
front-matter
titles.
No `docs/**` page has front matter today, so every page renders exactly
as before.
The existing `& + h1` dedup is kept, so exactly one H1 renders.
## Changes
- `offlinedocs/pages/[[...slug]].tsx`
- `getStaticProps` reads `attributes` from `front-matter` and passes a
resolved
`title` (front matter, else manifest label) through props.
- The `<title>` and the injected page `<Heading>` render the resolved
title.
## Verification
Built offlinedocs (`pnpm build`): 455/455 static pages generated.
Temporarily added a
front-matter title to one page to confirm precedence, then reverted:
| State | `<title>` | page `<h1>` |
|---|---|---|
| Before (no front matter) | `Administration` | `Administration` |
| After (`title: "FM Proof: Administration Console"`) | `FM Proof:
Administration Console` | `FM Proof: Administration Console` |
The body `# Administration` stayed hidden by the `& + h1` dedup, so
exactly one H1
rendered in both cases. `pnpm lint` (`tsc --noEmit`) and `prettier
--check` both pass.
## AI disclosure
This PR was generated by Coder Agents and opened on behalf of
@nickvigilante, who is
accountable for its contents. Manual verification evidence is included
above per the
[AI contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING).
|
||
|
|
580ba19a0c |
fix(site/e2e): fix login helper race condition causing navigation flake (#27107)
Fixes flake reported in [DEVEX-538](https://linear.app/codercom/issue/DEVEX-538/flake-create-user-with-password). ## Problem The `login()` e2e helper had a race condition causing intermittent navigation failures: ``` page.goto: Navigation to "/deployment/users" is interrupted by another navigation to "/" ``` After clicking Sign In, `LoginPage.tsx` does a hard navigation via `location.href = sanitizeRedirect(redirectTo)`. With no `?redirect=` param, `retrieveRedirect` defaults to `"/"`, so login navigates to `/`. The browser loads `/`, fires the `load` event, then React boots and the router does a client-side redirect from `/` to `/workspaces` (via `<Navigate to="/workspaces" replace />`). The old helper waited with `expectUrl(page).toHavePathName("/workspaces")`, which polls `page.url()` and resolves the moment the pathname matches. It has no awareness of page load state. So it resolved after the client-side redirect changed the URL, but before the `/workspaces` page components had mounted. When a test immediately called `page.goto()` afterward, pending React rendering could trigger a competing navigation. ## Fix Replace the URL polling with two Playwright-idiomatic waits: 1. `page.waitForURL(/\/workspaces/)` hooks into the browser's navigation lifecycle: it waits for the URL to match AND for the page to reach a load state (`"load"` by default), unlike `expectUrl` which is purely a string poll. 2. `await expect(page).toHaveTitle(/Workspaces/)` waits for the page title, which is set by the `WorkspacesPage` component. This proves React booted, auth resolved, and the page fully rendered, closing the window where pending React work could interfere with the next navigation. Also adds `{ waitUntil: "domcontentloaded" }` to `page.goto("/login")` for consistency with every other navigation helper in the file. > 🤖 Generated by Coder Agents on behalf of @jeremyruppel |
||
|
|
e11147ec66 |
fix: add VPN wake rebind hook (#26739)
Add a CoderVPN WakeRequest RPC so Coder Desktop can trigger the existing link-change recovery path (Rebind + ReSTUN) immediately on OS wake, instead of waiting for magicsock's idle re-STUN timer. Wake events are debounced to at most one rebind per 5s to avoid duplicate resets of peer path trust. Closes #26736 |
||
|
|
1497ba14fe |
refactor: type computer use provider as an enum (#27086)
The deployment-wide computer use provider was passed around as a bare `string` on the `codersdk` wire structs, in `chattool`, and in the generated TypeScript, and its valid values (`anthropic`, `openai`) were never exposed as a `codersdk` enum. That's out of step with our other chat settings (`ChatDebugRunKind`, `ChatUsageLimitPeriod`), which already define enums with `Valid()` and an `All<Name>s` slice, and it left the allowed values duplicated as literals with no typed contract for clients. This adds `codersdk.ChatComputerUseProvider` as the single source of truth and routes the API boundary, `chattool`, `chatd`, and the generated TypeScript through it. The DB layer and chattool's internal model-provider routing stay `string` on purpose, since they handle untrusted or fantasy-model values that just happen to share the names. |
||
|
|
27ed052d86 | fix(site): remove invalid Autocomplete hooks (#27177) | ||
|
|
ea4554025e |
fix(coderd): stop manual title generation from writing to chat_messages (#27087)
Coder Agents chats could get stuck showing "Thinking" forever when a title regenerate/propose request ran while a generation was in flight. Manual title generation recorded token cost by inserting a hidden assistant message into `chat_messages` and immediately soft-deleting it. Triggers on that table sync `chats.history_version` to `snapshot_version`, so this out-of-band write broke the `history_version` fence of an in-flight generation task, killing it without a replacement and leaving the chat stuck in `running`. Remove the accounting path entirely; AI Gateway already records title-call usage in `aibridge_interceptions`/`aibridge_token_usages`. The manual title endpoints no longer write to `chat_messages` at all, and new regression tests assert `history_version` stays untouched. Note this intentionally drops title-generation cost from chatd's chat-level cost surfaces; it still counts against the user's AI budget via AI Gateway. Closes CODAGT-595 |
||
|
|
0f11673a74 |
fix(site/src/pages/AIBridgePage/SessionThreadsPage/SessionTimeline): hide "Session completed" until every thread has loaded (#26955)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. The "Session completed" marker at the bottom of an AI Gateway session timeline was rendered unconditionally, so on long sessions it appeared below still-loading threads while the user scrolled. That is misleading: users read it as the end of the session even when more threads are about to stream in. Only render the session end marker (rows 7 and 8 of the grid: the connecting vertical line, the success dot, and the "Session completed" text) once every thread has loaded, that is, once both `hasNextPage` and `isFetchingNextPage` are false. The dashed timeline box still closes cleanly at the bottom, and the infinite-scroll spinner keeps rendering inside row 5 while more pages fetch. | Old | New | | --- | --- | | <img width="1099" height="338" alt="preview-old-behaviour" src="https://github.com/user-attachments/assets/f86c9ce1-f4ca-4088-a1ed-9cdcf8fb940c" /> | <img width="1099" height="323" alt="preview-new-heaviour" src="https://github.com/user-attachments/assets/b4a1e703-10d2-4378-9f25-48cac46249a1" /> | ## Verification Rendered each SessionTimeline story via a headless Chromium and asserted whether "Session completed" is present: | Story | `hasNextPage` | `isFetchingNextPage` | "Session completed" | | --- | --- | --- | --- | | OneThread | false | false | visible | | MultipleThreads | false | false | visible | | FetchingNextPage | true | true | hidden | | HasMoreThreadsToLoad (new) | true | false | hidden | All checks pass locally: - `pnpm format` (no changes) - `pnpm lint:check` - `pnpm lint:types` - `make pre-commit` via githooks <details> <summary>Implementation notes</summary> - `site/src/pages/AIBridgePage/SessionThreadsPage/SessionTimeline/SessionTimeline.tsx`: wrap the row 7 spacer and row 8 status dot/text in `!hasNextPage && !isFetchingNextPage`. - `SessionTimeline.stories.tsx`: add `HasMoreThreadsToLoad` to cover the between-fetches state. - No prop signature or public API change; `SessionTimelineSkeleton.tsx` is untouched because the skeleton is only shown before any threads have loaded. </details> |
||
|
|
9af3d31036 | fix: relabel advisor max uses setting from per run to per turn (#27046) | ||
|
|
3d8ffd34b3 | fix(coderd/x/chatd): retry quickgen without temperature when model rejects it (#27120) | ||
|
|
e59a67d63f | fix(site): use RBAC-filtered organizations for embedded metadata (#27110) | ||
|
|
d66e4d794f | feat: add configurable reasoning effort to Coder agents (#26974) | ||
|
|
5fed583a46 |
fix(coderd): enforce required external auth on task create (#26718)
Tasks created through the API now enforce required external auth: `tasksCreate` rejects an owner who is missing a required (non-optional) provider with a 403 before generating a task name or inserting any rows, matching the gate `createWorkspace` already applies to workspaces. Adds `TestCreateTaskExternalAuth` covering the required and optional-provider cases. Fixes PLAT-298. _Coder Agents generated._ |
||
|
|
37558fcdc9 |
fix(coderd/externalauth): preserve scopes on entra v1 token refresh (#24851)
Without this, Entra silently narrows scopes to the default set. |
||
|
|
f84801eecf |
chore: bump coder/fantasy for gpt-5.6 Responses routing (#27132)
gpt-5.6 models were unusable with agents: fantasy's Responses allowlist did not include the new family, so `IsResponsesModel` returned false and chatd fell back to the Chat Completions path (no reasoning params, no encrypted reasoning continuity). ## Changes - Bump the `charm.land/fantasy` replace pin to coder/fantasy `6da0c3b10237` (coder_2_33), pulling in: - coder/fantasy#46: route `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` through the OpenAI Responses API as reasoning models. - coder/fantasy#41: surface Anthropic refusal stop_reason as content-filter (already on coder_2_33, rides along with the bump). - Update the fork changelog comment in go.mod. ## Verification - Probed all three gpt-5.6 models through the ai-gateway: `/v1/responses` with `reasoning.effort`, `include: ["reasoning.encrypted_content"]`, and `store: false` completes for each. - `go build ./coderd/...` and `go test ./coderd/x/chatd/chatopenai/ ./coderd/x/chatd/chatprovider/` pass against the new pin. > This PR was authored by Mux on Mike's behalf. |
||
|
|
3e608e791d | feat(site): add claude-fable-5 and claude-mythos-5 known model defaults (#27131) | ||
|
|
8a095d3b38 |
feat(site/src/pages/TemplateBuilder): deselect modules using button in main content area instead of sidebar (#27113)
closes DEVEX-588 Prototyped in #27077, broken off into a separate PR to make this work easier to track ## changes - Reveals the previously hidden trash can icon within `ModuleConfiguration` (main content area) - Removes the "x" icons from `ModuleSelection` (sidebar) ## context @tracyjohnsonux and I decided [in Slack](https://codercom.slack.com/archives/C0AUKB54P0E/p1783456607073329?thread_ts=1783450189.570979&cid=C0AUKB54P0E) that it would be a better UX to move the deletion action from the "x" icons in the sidebar to the trash can icons in the main content area. This change has the benefits of 1. making it harder to delete modules accidentally 2. removing the responsibility of deletion from the items in `ModuleSelection` - interacting with these items will serve only to navigate to configuring that module (DEVEX-587, to be done in a separate PR) <img width="1840" height="1191" alt="image" src="https://github.com/user-attachments/assets/4571ea2f-75cf-4cd7-b626-1826eea83bdf" /> |
||
|
|
20ee0068e6 |
docs(docs/install/releases): update release calendar for v2.35.1 (#27128)
Update release calendar with the latest branch releases: - v2.34.1 → v2.34.5 (Stable/ESR) - v2.33.7 → v2.33.11 (Security Support) - v2.32.6 → v2.32.10 (Not Supported) - v2.29.16 → v2.29.19 (Extended Support Release) - 2.35 added as Mainline at v2.35.1 Channel rotation for the 2.35 mainline release: - 2.32: Security Support → Not Supported - 2.33: Stable → Security Support - 2.34: Mainline (ESR) → Stable (ESR) - 2.35: Not Released → Mainline Also updates the ESR version link to point to v2.34.5. |
||
|
|
81bb8a49c4 |
test(cli): tolerate stray requests in fake agent API (#27127)
Test_TaskSend flaked (coder/internal#1547, coder/internal#1609) when a stray POST /chat/completions hit the fake agent API and the catch-all handler called t.Fatalf. No code under test posts that path to the sidebar app URL; the request most likely came from another test's lingering client after its server's ephemeral port was reused. Fatalf was also called off the test goroutine, which the testing package forbids. Unknown paths now get a 404 and a log line with request details for attribution. Unstubbed known agentapi endpoints still fail the test, via t.Errorf, so a coderd regression is still caught. |
||
|
|
3e85cfb2c5 |
fix: during workspace bulk start/stop, skip workspaces already in target state (#27108)
Previously, bulk start required every selected workspace to be stopped, and bulk stop required every selected workspace to be running. Mixed selections disabled both buttons entirely. - Change the disabled checks on bulk start/stop from `every()` to `some()` so the buttons are enabled when at least one workspace is eligible. - Filter workspaces by status in the mutation functions so only eligible workspaces are sent to the API, matching the pattern used by other batch mutations (update, favorite, unfavorite). - Update docs to reflect the new behavior. > [!NOTE] > Generated by Coder Agents. [View session](https://coder.com/). <details> <summary>Implementation plan</summary> ## Problem When an admin selects multiple workspaces and opens the "Bulk actions" dropdown, the **Start** menu item is disabled unless *every* selected workspace has `latest_build.status === "stopped"`. If even one workspace is already running (or in any other non-stopped state), the Start button is grayed out and unusable. Same issue applies to **Stop**. ## Changes ### 1. Relax disabled condition (`WorkspacesPageView.tsx`) Changed `every()` to `some()` for both Start and Stop dropdown items. The buttons are now enabled when at least one selected workspace is in the target state. ### 2. Filter in mutations (`batchActions.ts`) Added `.filter()` before `.map()` in both `startAllMutation` and `stopAllMutation` so only eligible workspaces hit the API. This matches the existing pattern in `updateAllMutation`, `favoriteAllMutation`, and `unfavoriteAllMutation`. ### 3. Update documentation (`docs/user-guides/workspace-management.md`) Replaced "can only be applied to a set of workspaces which are all in the same state" with "apply to eligible workspaces in the selection, skipping workspaces that are already in the target state." ## Testing Four new Storybook stories: | Story | What it tests | |-------|---------------| | `StartIgnoresAlreadyRunningWorkspaces` | Mixed selection; only stopped workspaces get `startWorkspace` calls | | `StopIgnoresAlreadyStoppedWorkspaces` | Mixed selection; only running workspaces get `stopWorkspace` calls | | `StartDisabledWhenNoWorkspacesAreStartable` | All running; Start button is disabled | | `StopDisabledWhenNoWorkspacesAreStoppable` | All stopped; Stop button is disabled | </details> |
||
|
|
66b42650ae |
fix(cli): respect empty --ssh-host-prefix/--hostname-suffix flags (#27084)
## Problem `coder config-ssh --ssh-host-prefix=""` (or the matching env var, `CODER_CONFIGSSH_SSH_HOST_PREFIX=`) was silently ignored, and the deprecated `Host coder.*` block was written to the SSH config anyway. The merge logic that decides whether to fall back to the server's default prefix checked `user.userHostPrefix == ""`, which is true both when the flag was never passed and when it was explicitly set to empty, so there was no way to distinguish the two. The same issue applied to `--hostname-suffix`. ## How this affects users Anyone who wants to opt out of the legacy prefix-based SSH aliases (`ssh coder.myworkspace`) in favor of the newer suffix-based ones (`ssh myworkspace.coder`) had no way to do so, the `Host coder.*` wildcard block kept reappearing on every `config-ssh` run regardless of the flag. Because that wildcard matches any hostname starting with `coder.`, not just Coder workspaces, it can silently intercept SSH connections to unrelated hosts that happen to share that prefix. It got worse on top of that: even after passing `--ssh-host-prefix=""`, running `config-ssh --use-previous-options` in a later session, a normal way to refresh local config without retyping every flag, would silently bring the block back, because the empty choice was never persisted to the file in the first place. ## Solution Track whether each option (`--ssh-host-prefix`, `--hostname-suffix`) was explicitly set by the user, as opposed to left at its zero value, and only fall back to the server default (or skip persisting the option) when it was genuinely never set. ## How it works Two new fields on `sshConfigOptions`, `userHostPrefixExplicit` and `hostnameSuffixExplicit`, carry this information: - **Live invocation**: they're set from `userSetOption(inv, ...)`, which inspects serpent's `Option.ValueSource` for the flag, right after `header`/`headerCommand` are set in the `Handler`, before any `--use-previous-options`/prompt logic can replace the struct wholesale from a prior run's saved options. - **Persistence**: `sshConfigWriteSectionHeader` now writes the `# :ssh-host-prefix=` comment line even when the value is empty, as long as it was explicit, and `sshConfigParseLastOptions` sets the field back to `true` whenever it parses that line on a later run, regardless of value. `mergeSSHOptions`'s fallback condition changed from `user.userHostPrefix == ""` to `user.userHostPrefix == "" && !user.userHostPrefixExplicit` (and the mirror for suffix). `equal()` and `asList()` were extended to include the two new fields so the `--dry-run` diff and "options differ, use new ones?" prompt stay accurate. ## Why implemented this way - Reuses `userSetOption` (`cli/util.go`), an existing helper already used for this exact "distinguish zero value from unset" problem elsewhere in the CLI (`cli/templateedit.go`), instead of inventing new machinery. - Storing the "explicit" bit as a plain field on `sshConfigOptions`, rather than as extra parameters to `mergeSSHOptions`, keeps that function dependency-free (still plain data in, plain data out, no `serpent.Invocation` coupling), while letting the same bit flow naturally through the SSH config's persisted-options comment, solving the live-flag case and the `--use-previous-options` persistence case with one mechanism instead of two. - A sentinel-value approach was considered and rejected: a self-tracking custom `serpent.Value` doesn't work because serpent applies a flag's default through the same `Value.Set()` call used for real input, so it can't tell the two apart; a plain sentinel string would work but leak into several other code paths (equality checks, diff/prompt text, the persisted comment) that would all need to filter it out. Closes https://github.com/coder/internal/issues/1208 ## Manual verification Every step below was run against a local dev server (`./scripts/develop.sh` + `./scripts/coder-dev.sh`), pointed at a throwaway `--ssh-config-file`, never a real `~/.ssh/config`. ### 1. Baseline: unchanged behavior with no flags ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" cat "$TEST_SSH_CONFIG" ``` Both `Host coder.*` and `Host *.coder` are written, unchanged from before this fix (both server defaults are non-empty out of the box). <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.coder # ------------START-CODER----------- # This section is managed by coder. DO NOT EDIT. # # You should not hand-edit this section unless you are removing it, all # changes will be lost when running "coder config-ssh". # Host coder.* ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR ProxyCommand .../coder-slim ... ssh --stdio --ssh-host-prefix coder. %h Host *.coder ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR Match host *.coder !exec ".../coder-slim connect exists %h" ProxyCommand .../coder-slim ... ssh --stdio --hostname-suffix coder %h # ------------END-CODER------------ ``` </details> ### 2. Explicit empty `--ssh-host-prefix` omits the legacy block (the core fix) ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --ssh-host-prefix "" cat "$TEST_SSH_CONFIG" ``` `Host coder.*` is gone, only `Host *.coder` remains. The choice is now also persisted (`# :ssh-host-prefix=`). <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.coder # ------------START-CODER----------- # This section is managed by coder. DO NOT EDIT. # # You should not hand-edit this section unless you are removing it, all # changes will be lost when running "coder config-ssh". # # Last config-ssh options: # :ssh-host-prefix= # Host *.coder ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR Match host *.coder !exec ".../coder-slim connect exists %h" ProxyCommand .../coder-slim ... ssh --stdio --hostname-suffix coder %h # ------------END-CODER------------ ``` </details> ### 3. Same, via the environment variable instead of the flag ```sh CODER_CONFIGSSH_SSH_HOST_PREFIX="" ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" grep -c "Host coder" "$TEST_SSH_CONFIG" ``` Confirms the fix isn't flag-only, `userSetOption` checks `ValueSource`, set the same way for `ValueSourceFlag` and `ValueSourceEnv`. <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.coder 0 ``` </details> ### 4. Explicit empty prefix combined with an explicit suffix ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --ssh-host-prefix "" --hostname-suffix mytest cat "$TEST_SSH_CONFIG" ``` Only `Host *.mytest` is written. Both options are correctly recorded in the persisted comment. <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.mytest # ------------START-CODER----------- # This section is managed by coder. DO NOT EDIT. # # You should not hand-edit this section unless you are removing it, all # changes will be lost when running "coder config-ssh". # # Last config-ssh options: # :ssh-host-prefix= # :hostname-suffix=mytest # Host *.mytest ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR Match host *.mytest !exec ".../coder-slim connect exists %h" ProxyCommand .../coder-slim ... ssh --stdio --hostname-suffix mytest %h # ------------END-CODER------------ ``` </details> ### 5. The explicitly-empty choice survives `--use-previous-options` with no flag repeated This is the persistence half of the fix: confirms the "omit this block" choice, once persisted, doesn't get lost on a later run that reuses previous options without repeating `--ssh-host-prefix`. Before this fix, this exact sequence would bring `Host coder.*` back. ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --ssh-host-prefix "" ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --use-previous-options cat "$TEST_SSH_CONFIG" ``` <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.coder No changes to make. # ------------START-CODER----------- # This section is managed by coder. DO NOT EDIT. # # You should not hand-edit this section unless you are removing it, all # changes will be lost when running "coder config-ssh". # # Last config-ssh options: # :ssh-host-prefix= # Host *.coder ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR Match host *.coder !exec ".../coder-slim connect exists %h" ProxyCommand .../coder-slim ... ssh --stdio --hostname-suffix coder %h # ------------END-CODER------------ ``` </details> The second command printed `No changes to make.`, and critically, `Host coder.*` did **not** reappear even though that run passed no `--ssh-host-prefix` flag at all, only `--use-previous-options`. ### 6. `--use-previous-options` still wins over this run's explicit empty flag (unaffected by this fix) Confirms this fix didn't change the pre-existing, intentional precedence of `--use-previous-options`: a previously-saved *non-empty* value still wins over an explicit empty flag passed on a later run. ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --ssh-host-prefix "custom-test." ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --use-previous-options --ssh-host-prefix "" cat "$TEST_SSH_CONFIG" ``` <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.coder No changes to make. # ------------START-CODER----------- # This section is managed by coder. DO NOT EDIT. # # You should not hand-edit this section unless you are removing it, all # changes will be lost when running "coder config-ssh". # # Last config-ssh options: # :ssh-host-prefix=custom-test. # Host custom-test.* ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR ProxyCommand .../coder-slim ... ssh --stdio --ssh-host-prefix custom-test. %h Host *.coder ConnectTimeout=0 StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel ERROR Match host *.coder !exec ".../coder-slim connect exists %h" ProxyCommand .../coder-slim ... ssh --stdio --hostname-suffix coder %h # ------------END-CODER------------ ``` </details> `Host custom-test.*` is preserved verbatim, `--use-previous-options` correctly overrides the explicit empty flag when the saved value is non-empty, the mirror image of step 5's explicit-empty saved value. ### 7. End-to-end sanity check with a real workspace ```sh ./scripts/coder-dev.sh config-ssh --yes --ssh-config-file "$TEST_SSH_CONFIG" --ssh-host-prefix "" --hostname-suffix mytest ssh -F "$TEST_SSH_CONFIG" -o ConnectTimeout=15 myworkspace.mytest echo ok ``` <details> <summary>Output</summary> ```text Updated "/tmp/tmp.9Y7VIeuQoY" You should now be able to ssh into your workspace. For example, try running: $ ssh myworkspace.mytest ok ``` </details> `ok` came back from a real, running workspace, confirming the ProxyCommand and Match/exec wiring generated by the suffix-only config actually establishes a working SSH session end-to-end, not just a text-generation check. |
||
|
|
1eea4a7e5b |
fix(site): keep activity bump editable when allow_user_autostop is on (#27083)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. The UI guard added in #22112 disabled the `Activity bump` field and cleared its saved value whenever the template's `Default autostop` was 0. It did not check the "Allow users to customize autostop duration for workspaces" (`allow_user_autostop`) setting, so templates that relied on user-defined autostop timers had their `activity_bump_ms` silently cleared when saving in the Coder UI. Enable the field, preserve the value on submit, and update the helper text when either `default_ttl_ms > 0` or `allow_user_autostop` is true. Closes [DEVEX-438](https://linear.app/codercom/issue/DEVEX-438/allow-user-autostop-default-autostop-disabled-causes-activity-bump-to). > **Note:** This needs to be backported to 2.34 (ESR). <details> <summary>Implementation notes</summary> ### Problem [#22112](https://github.com/coder/coder/pull/22112) introduced a UI guard that: 1. Disables the `Activity bump (hours)` field when `default_ttl_ms === 0`. 2. Sends `activity_bump_ms: undefined` on submit under the same condition, which the backend treats as "do not update", but combined with the disabled state users cannot re-enter a value once cleared and the previously stored value effectively becomes orphaned. The guard ignored `allow_user_autostop`. When that setting is enabled, workspaces still have a scheduled stop (whatever the user configures on their workspace), so `activity_bump_ms` is still meaningful. ### Fix Broaden the guard to consider both signals. The field is only disabled and the value only discarded when **both** `default_ttl_ms === 0` **and** `allow_user_autostop === false`. Changes: - `TemplateScheduleForm.tsx` - `disabled` prop now checks `!default_ttl_ms && !allow_user_autostop`. - Submit path preserves `activity_bump_ms` when either signal is truthy. - Passes `allowUserAutostop` through to the helper text. - `TTLHelperText.tsx` - `ActivityBumpHelperText` accepts `allowUserAutostop` and only shows the "no scheduled stop" hint when neither signal is set. Updated copy mentions both signals. - Tests and stories - Existing tests explicitly uncheck `allow_user_autostop` before asserting the guard fires (since `MockTemplate.allow_user_autostop` defaults to `true`). - Added coverage: guard stays off when only `allow_user_autostop` is enabled; toggling `allow_user_autostop` re-enables the field without touching `default_ttl_ms`. - Added a story that verifies `activity_bump_ms` is preserved on submit when `allow_user_autostop` is enabled and `default_ttl_ms` is 0. </details> |
||
|
|
bab8ce9d41 |
feat: setup logging, tracing and metrics in standalone AI Gateway (#27068)
Adds logging, tracing and metrics setup to standalone AI Gateway. Existing options are re-used when possible. |
||
|
|
ef0b5585d5 |
feat: record and expose terminal upstream interception errors (#26961)
Categorises the terminal error of a failed interception and persists it on the interception record, then surfaces it on the AI Gateway API. - Categorise into an enum (`bad_request`, `unauthorized`, `rate_limited`, `overloaded`, `server_error`, `unknown`), unwrapping the ResponseError envelope, the upstream Anthropic/OpenAI SDK errors, and key-pool exhaustion so blocking and streaming paths agree. - Thread the type and raw message through the recorder dRPC into the `aibridge_interceptions` row (optional proto fields; NULL on success). - Expose the error on the AI Gateway thread API from the root interception. *This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.* |
||
|
|
63497ee9d8 |
feat(coderd/database): add error columns to aibridge interception records (#26960)
Adds a nullable `aibridge_interception_error_type` enum and an `error_message` column to `aibridge_interceptions`, so a failed interception's terminal upstream error can be persisted. Schema only: the write path and API exposure land in the stacked backend PR. *This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.* |
||
|
|
cd1a676232 |
chore(coderd): deflake chat http tests (#27121)
Closes https://github.com/coder/internal/issues/1615. The affected test was starting coderd with a live chatd worker, but assumed that the chat would not be processed by a worker. The fix was to start coderd without a chatd worker. I noticed that some other tests in the file could suffer from the same flake root cause, so I fixed them too. |
||
|
|
a6afcd046f | fix: prevent deadlock between async error handling and failed subscribes (#27109) | ||
|
|
52775ef172 |
test(coderd/externalauth): fix RevokeTokenRFC_Timeout flake (#27082)
Under CI load the request's 10ms revoke timeout could expire before the request reached the FakeIDP revoke handler. The handler never ran, so the test's wait for it to finish blocked until the 25s test context expired instead of passing quickly. Raise `RevokeTimeout` to 100ms so the request has ~10x more headroom to reach the handler under load. After RevokeToken returns, check a `handlerStarted` signal before asserting: this anchors the `DeadlineExceeded` assertion to a request that was actually in flight, and turns any residual scheduling race into a fast, labeled failure instead of a hang. Unblock the handler on the early-exit path with a `t.Cleanup`. It must be registered after the FakeIDP setup so LIFO runs it before the server's `Close()`; otherwise a handler that dispatched late would block `Close()` and hang teardown until the test timeout. Drop the previous `time.Sleep` watchdog and the handler-done channel, since the FakeIDP server's `Close()` already joins the in-flight handler. Refs: https://linear.app/codercom/issue/PLAT-317 |
||
|
|
b5a445d3c2 | fix(site): stop popover heading from overlapping provisioner tag fields (#27101) | ||
|
|
2ad5af5b54 |
fix(coderd): use pasted-text attachments as chat title input (#27067)
Closes https://linear.app/codercom/issue/CODAGT-268 ## Problem The chat UI collapses large pastes (>=10 lines or >=1000 chars) into a synthetic `pasted-text-*.txt` attachment. A chat created with only such an attachment had no title input anywhere: the create path derived `titleSource` only from text and file-reference parts (so the chat was named "New Chat"), async auto-titling extracted text the same way and silently skipped generation, and the manual propose/regenerate paths returned an empty title for the same reason. The regular prompt path already inlines these files for the model; only the title paths were blind. ## Fix Add a single title-input derivation in `chatprompt` and use it everywhere: - `chatprompt.TitleText` joins text and file-reference parts (unchanged formatting), and falls back to synthetic pasted-text attachment content (truncated to a 16 KiB title budget) when they yield nothing. - `chatprompt.SyntheticPasteFileIDs` identifies paste attachments; `chatprompt.FallbackTitle` consolidates the previously duplicated `chatTitleFromMessage` / `fallbackChatTitle`. - Chat creation captures paste blob references while validating file parts (the file row was already loaded there) and derives `titleSource` via `TitleText`. Only the create path derives titles; message send and edit reuse the same validation without copying any blob data. - `GenerateChatTitleAsync` and the manual propose/regenerate paths resolve paste content via `titlePasteText`, which only queries when a visible user message has no other title text, so chats with typed text never incur a file fetch. - Title-path paste fetches are bounded: a new `GetChatFileDataPrefixesByIDs` query returns only a `substr` prefix (`chatprompt.TitlePasteBytePrefix`, 64 KiB = 4 bytes x the 16 Ki-rune title budget) so full blobs (up to 10 MiB each) never leave the database for titling, and `chatprompt.TitlePasteText` applies the same bound to the create path which already holds the loaded row. Deliberate side effect: because generation-time extraction now matches create-time `titleSource` exactly, file-reference-only chats also become eligible for AI titles. They were previously skipped by the same derivation mismatch. Non-goals: no frontend changes (attachment chip UX stays as is), and non-synthetic user-uploaded `.txt` files still yield "New Chat". ## Testing - Unit tests for `TitleText`, `TitlePasteText`, `SyntheticPasteFileIDs`, `FallbackTitle`, `titleInput`, `titlePasteText`, and paste-aware `extractManualTitleTurns`. - Real-database test for `GetChatFileDataPrefixesByIDs` (prefix shorter and longer than stored data) plus dbauthz coverage for the new query. - Integration tests: paste-only create gets a fallback title from the paste content, async title generation fires with the paste content as input, and `RegenerateChatTitle` works on a paste-only chat. > This PR was written by [Mux](https://mux.coder.com) on Mike's behalf. |
||
|
|
d16f254714 |
fix(site/src/pages/AgentsPage): remove archive actions for child chats (#27063)
Child chats (sub-agent chats) no longer offer archive-state actions in their menus. Archive state is root-only on the backend and cascades to children (`coderd/exp_chats.go` rejects `archived` changes when `parent_chat_id` is set), so a child's "Archive agent", "Archive & delete workspace", and "Unarchive agent" items always failed with a 400. All chat action menus (chat header kebab, sidebar row dropdown, sidebar right-click context menu) render the shared `ChatActionsMenuItems`, which already hides Pin/Unpin for child chats; this extends the same gating to the archive and unarchive items. Since an archived child chat then has no menu actions at all, the menu triggers are hidden for archived child chats (`chatHasMenuActions`): the header kebab and the sidebar row's dropdown trigger are not rendered, and the row's right-click context menu is disabled. Archived root chats keep their "Unarchive agent" action. Stories: renamed the ChatTopBar child-chat story to `ChildChatHidesPinAndArchiveActions` and extended it to assert both archive items are hidden, plus new stories for the archived-child cases (`ArchivedChildChatHasNoActionsMenu`, `ArchivedChildChatRowHasNoActionsMenu`) and a sidebar child-menu story (`ChildChatMenuHidesArchiveActions`). Closes CODAGT-631. > This PR was created by Mux, an AI agent working on behalf of Mike. |
||
|
|
990f0a5529 |
chore(coderd/database): remove unused UpdateChatMessageByID query (#27099)
Removes the `UpdateChatMessageByID` query. Its only non-generated reference was its own dbauthz coverage test, so it is dead code. > Generated by Coder Agents on behalf of @johnstcn. |
||
|
|
344a97751e |
docs: add a write-docs authoring skill (#26767)
Adds .claude/skills/write-docs/SKILL.md, the authoring counterpart to the doc-check skill, and cross-links it from AGENTS.md. Links the canonical content guidelines, prose style guide, and PR description style guide instead of restating them. Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
83cb587c3f |
docs(docs/.style/style-guide): add directional-language and contractions rules (#26729)
Adds two new accessibility-and-voice rules to the style guide.
**Directional language**
(`docs/.style/style-guide/accessibility-and-inclusion.md`).
Screen-reader users navigate documents linearly and cannot follow
spatial references like "see below" or "the menu on the left". The rule
prescribes anchor links, section headings, document order ("the previous
section", "the following section"), and named UI elements instead. A
replacement table covers the common cases.
**Contractions are the default**
(`docs/.style/style-guide/voice-and-tone.md`). Prefer contractions in
body prose for the same reason the docs use second person and present
tense. Three exceptions: auxiliary contractions (`you'd`, `there's`,
`it's`, `we'd`, `they're`) need an explicit complement and cannot end a
sentence; contractions join exactly two words (no `you'd've` or
`wouldn't've`); spell out for emphasis and high-stakes operations like
deletion or data loss (`do not`, `cannot`, `will not`).
The PR also sweeps the existing style-guide subpages so the existing
prose comply with both rules.
Resolves
[DOCS-462](https://linear.app/codercom/issue/DOCS-462/add-screen-reader-aware-directional-language-rule-and-sweep-existing).
<details>
<summary>Directional-language sweep targets</summary>
| File | Change |
| --- | --- |
| `docs/.style/style-guide/README.md` | "pages below" becomes "linked
pages" |
| `docs/.style/style-guide/accessibility-and-inclusion.md` | "top of the
page" becomes "beginning of the page". Captions "follow" instead of "go
below". Latin abbreviation table cells drop "as described below". Sample
captions name widgets instead of panel positions. |
| `docs/.style/style-guide/audience-and-scope.md` | Don't example
rewritten without "below". "Above the first paragraph" becomes "before
the first paragraph". "At the top of the page" becomes "at the beginning
of the page". |
| `docs/.style/style-guide/capitalization-and-punctuation.md` |
"Exceptions above" becomes "exceptions listed earlier". |
| `docs/.style/style-guide/formatting.md` | Captions "follow" instead of
"go below". Sample captions renamed by widget. Don't example "as shown
above" becomes "as shown in the screenshot". |
| `docs/.style/style-guide/numbers-units-and-dates.md` | "10th and up"
becomes "10th and higher". |
Idiomatic stack metaphors like "built on top of Terraform" and phrasal
verbs like "set up", "back up", "log in", and "shut down" are explicitly
carved out as not directional and stay as-is.
</details>
<details>
<summary>Contractions rule scope</summary>
The rule lands as `## Contractions are the default` in
`voice-and-tone.md`, placed between `Present tense by default` and
`Trailing prepositions are a judgment call` because all three rules sit
in the natural-phrasing cluster.
The sweep applies the rule across all eight style-guide subpages: 76
lines updated where the spelled-out form (`does not`, `is not`,
`cannot`, `you have`, `there is`, `that is`) reads more naturally as a
contraction.
Skipped:
- Don't blocks inside the contractions rule that intentionally
demonstrate the wrong form.
- Do blocks inside the emphasis sub-rule that intentionally model `do
not`, `cannot`, and `will not` for high-stakes operations.
- The Churchill joke inside the trailing-prepositions Don't blocks.
- The "that is" dictionary definition of `i.e.` in the Latin
abbreviations table.
- `may not` (no contraction in modern English).
- `that has` relative clauses where `'s` could read as possessive.
</details>
<details>
<summary>Lints</summary>
- `make lint/markdown`: 0 errors across 495 files.
- `make lint/prose`: only the pre-existing intentional `[Demo]`
annotations in `docs/.style/_vale-annotation-demo.md` fire.
</details>
---
*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
|
||
|
|
f7632451f4 |
feat(docs): add Coder.BrandNames Vale rule, enforce HashiCorp casing (#25501)
Lands the first concrete rule under the `Coder` style: `Coder.BrandNames`, a bundled `substitution` rule that enforces canonical brand casing in prose. HashiCorp is the first entry; [DOCS-188](https://linear.app/codercom/issue/DOCS-188) extends it with GitHub, OpenTofu, Kubernetes, Terraform, JetBrains, and VS Code. ## What changes Four commits, ordered so each is independently valid: 1. **`docs: fix HashiCorp casing in prose and sidebar`** ([06d769dad1](https://github.com/coder/coder/pull/25501/commits/06d769dad179cf85c535b058df3b6bafdc1f9565)). 5 Markdown files plus 2 `docs/manifest.json` entries. Drives the corpus violation count to zero. 2. **`feat(docs/.style/styles/Coder): add Coder.BrandNames Vale rule`** ([e00fc780a7](https://github.com/coder/coder/pull/25501/commits/e00fc780a7a20dcf82105d997af5cfcddd4b1855)). New `BrandNames.yml` with the HashiCorp swap at `level: error`, plus a new `### Brand names` subsection in `docs/.style/style-guide.md`. 3. **`docs(.style/styles/Coder/README.md): scrub planned-rules notes obsoleted by Coder.BrandNames`** ([af8833b9f5](https://github.com/coder/coder/pull/25501/commits/af8833b9f58dd617732ae533bbf53eb4fc2e816a)). Removes the README's "intentionally empty for now" lead-in and the obsolete HashiCorp casing bullet from the planned-coverage list. 4. **`docs: apply semantic line breaks and fix Vale findings on PR-touched files`** ([e9f11df188](https://github.com/coder/coder/pull/25501/commits/e9f11df1886fdf0d5efc5e8a2cab95fecbd898f9)). Pre-review pass on every Markdown file this PR modifies. Full sembr and Vale-warning cleanup on the style-guide infrastructure (`style-guide.md`, `Coder/README.md`); sembr applied to the HashiCorp swap paragraph only on the five product docs, per scoping discussion with @nickvigilante. ## Severity rationale `error` from day one. HashiCorp's brand owner publishes a canonical casing; any other casing in prose is wrong, not a judgment call. Matches the `error = low FPs x high gravity` framework. False-positive rate is effectively zero because Vale's `substitution` rule skips inline code, fenced code blocks, and URLs by default, so `hashicorp/kubernetes` (Terraform provider source) and `developer.hashicorp.com` stay untouched. ## Verification - `make lint/markdown`: 0 errors across 487 files. - `make lint/prose`: 1 error, 1 warning, 1 suggestion in 468 files. All three findings are the intentional `Coder.DemoError`, `Coder.DemoWarning`, and `Coder.DemoSuggestion` annotations on `docs/.style/style-guide/demo/demo.md` (added on main as part of the [DOCS-425](https://linear.app/codercom/issue/DOCS-425) inline-annotation demo), not real findings. `Coder.BrandNames` fires zero times against the cleaned-up corpus. - `make pre-commit-light`: passed (7s). - Self-test: ran the rule against an unmodified `docs/` and confirmed it flags the 7 prose instances the cleanup commit fixes, then re-ran against the post-cleanup state and confirmed zero alerts. ## Known future conflict When [#26632](https://github.com/coder/coder/pull/26632) ([DOCS-434](https://linear.app/codercom/issue/DOCS-434)) merges, the monolithic `docs/.style/style-guide.md` is split into the `docs/.style/style-guide/` multi-page structure. The `### Brand names` subsection added in commit 2 will need to land in `docs/.style/style-guide/word-choice.md` (which already references the rule), and the `link:` in `docs/.style/styles/Coder/BrandNames.yml` will need to update from `style-guide.md#brand-names` to `style-guide/word-choice.md#brand-names`. Resolution path documented in an inline comment on this PR. <details> <summary>Implementation plan and decision log</summary> ### Why bundle into Coder.BrandNames rather than one file per brand Vale's convention (mirrored by `Google.WordList` with ~70 swaps in a single file) is to bundle `substitution` rules when they share severity, message template, and link. All brand-name rules share that shape: `error`, `Use '%s' instead of '%s'`, link to the style guide section. Bundling reduces "add a brand" to a one-line YAML diff and keeps `CODEOWNERS` and blame coherent. Per-rule performance is irrelevant at this scale; Vale's per-rule overhead is sub-millisecond and dwarfed by Markdown parsing. ### Why the cleanup lands first Commits are ordered cleanup-then-rule so each commit is a known-good state: - After commit 1: corpus is HashiCorp-clean, but no rule exists yet. - After commit 2: rule exists and lints a clean corpus. Reversing the order would land the rule at commit 1 (firing 7 errors on uncleaned content) and resolve them at commit 2. Under `--no-exit` the CI job still passes, but the inline annotations on commit 1 would be misleading. ### Why HashiCorp first instead of all brands at once Proof-of-concept value. HashiCorp is the smallest cleanup (7 prose lines plus 2 sidebar lines = 9 lines), zero FPs, zero ambiguity. Once the loop (rule plus cleanup plus style-guide section) is proven, [DOCS-188](https://linear.app/codercom/issue/DOCS-188) appends the other brands as additional commits to the same bundle. ### Brand-token sensitivity The `swap:` table only matches: - `Hashicorp` (capital H, lowercase rest), the actual wrong form in the corpus. - `HASHICORP` (all caps), defensive; doesn't appear in current corpus but cheap to include. `hashicorp` (all lowercase) is **not** in the swap table. The lowercase form appears 49 times in URLs (`developer.hashicorp.com`, `registry.terraform.io/providers/hashicorp/...`, `github.com/hashicorp/...`) and 6 times as Terraform provider sources (`source = "hashicorp/kubernetes"`), all of which are correct lowercase by convention. Vale's substitution rule scope ensures URLs and code blocks are skipped, but skipping the rule entirely for `hashicorp` (lowercase) is the explicit decision; if a prose typo of lowercase "hashicorp" ever shows up, we'd catch it through `Vale.Spelling` ([DOCS-187](https://linear.app/codercom/issue/DOCS-187)) instead. ### Self-reference in the style guide The `### Brand names` section's example table needed `Hashicorp` and `HashiCorp` as literal demonstration tokens. Wrapping them in backticks (`` `Hashicorp` ``, `` `HashiCorp` ``) keeps Vale from flagging the wrong-case example as a real violation. This is correct typography too: demonstration tokens get code formatting. ### Manifest.json Vale doesn't lint JSON, so the two `docs/manifest.json` entries are fixed by direct edit rather than tool enforcement. The sidebar `path` (`./admin/integrations/vault.md`) is unchanged; the title change does not affect the page URL on coder.com. No redirect needed in `coder/coder.com:redirects.json`. ### Pre-mortem - **Generated docs noise**: `Coder.BrandNames` does not fire on auto-generated `docs/reference/` content because no codersdk identifier matches the swap pattern. Zero risk. - **Future-additions friction**: adding GitHub to the swap table is one YAML line and a cleanup commit. The bundling shape pays off here. - **Disable footgun**: if a contributor needs to write the wrong casing on purpose (quoting an external bug report verbatim, for example), they can wrap the literal in backticks (already correct typography) or use the per-line Vale skip comment. </details> Closes [DOCS-34](https://linear.app/codercom/issue/DOCS-34). --- *Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on Nick's behalf.* |
||
|
|
1cc230b43a |
refactor: extract docgen env prep into a shared package (#26827)
## What `clidocgen` and the new `configdocgen` (coder/coder#26824) both carried a byte-identical `prepareEnv()` that unsets `CODER_*` and pins `CLIDOCGEN_*` / `TMPDIR` so generated docs don't embed the generating host's home directory. This extracts it to `scripts/docgenenv.Prepare()` and migrates `clidocgen`. ## Why Duplication flagged during review of #26824. `configdocgen` adopts the shared helper in that PR, removing its copy. ## Risk Behavior-preserving: regenerating the CLI reference (`make docs/reference/cli/index.md`) yields no diff, and `make pre-commit` passes (`lint/go`, `lint/ts`, `build`). A focused unit test pins the `Prepare()` contract, and `_test.go` files are excluded from `CLIDOCGEN_INPUTS` so test edits don't mark the generated docs stale. <details> <summary>CI status — blocked by an unrelated <code>main</code> breakage (#24993)</summary> All red checks on this PR are inherited from `main`, not caused by these changes. This PR touches only `Makefile` and `scripts/{clidocgen,docgenenv}`; it does not touch Helm. `main` went red at `d0f68cb9b0` ("feat: add listenerset", #24993, merged ~18:26 UTC). The committed `helm/coder/tests/testdata/listenerset*.golden` files don't match what `helm template` renders, so: - **`gen`** regenerates those goldens, and the unstaged-files check fails. - **`test-go-pg` (ubuntu-latest, pg-17) and `test-go-race-pg`** fail only on `TestRenderChart/{coder,default}/listenerset[_redirect]` (golden mismatch; the test prints "Run with -update to update golden files"). The same `test-go-pg` job passes on macOS and Windows, where the Helm render test is skipped, and `scripts/docgenenv` reports `ok` on the failing runners. Base commit `14a61041d9` was green; `main` is red from `d0f68cb9b0` onward. These checks clear once `main` is fixed and this branch is updated. `fmt`, `lint`, `Storybook`, `check-build`, and `test-e2e` are green. </details> --- 🤖 Opened by Coder Agents on behalf of @nickvigilante. --------- Co-authored-by: Cian Johnston <cian@coder.com> |
||
|
|
83acdaebd1 |
docs: add DOCKER_HOST guidance for non-default Docker socket paths (#26807)
## What Add `DOCKER_HOST` guidance for non-default Docker socket paths to two pages: - `docs/install/docker.md`: expands the **Cannot connect to the Docker daemon** troubleshooting section with the `DOCKER_HOST` fix and how to persist it to your shell startup file. - `docs/admin/templates/troubleshooting.md`: adds a concise **Cannot connect to the Docker daemon** entry that cross-references the install guide for the full steps. ## Why `install/docker.md` previously documented only the default socket path (`/var/run/docker.sock`). When Docker runs through a tool that uses a per-user socket, such as rootless Docker on Linux, or Colima, Podman, or Rancher Desktop on macOS, the daemon exposes its socket at a non-default path, so the Coder server cannot connect until `DOCKER_HOST` is set. The guidance frames Colima as one example, notes that default socket paths vary by tool, and persists the setting in a shell-agnostic way. Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
8cf59d700f |
chore: allowlist Go-standard marshal spelling in typos config (#26891)
typos-cli 1.47.x started flagging `unmarshaling` and `marshaling` as
misspellings of the British-English double-l forms.
The Go standard library uses the single-l American English spelling
throughout `encoding/json`, `encoding/xml`, etc., so these are correct
and intentional.
Adds both words to the `extend-words` allowlist so the linter accepts
the canonical Go spelling regardless of which typos version is in use.
---
🤖 Built with AI assistance.
|
||
|
|
0c51e4e346 |
ci(.github): allowlist npmjs.com in linkspector link check (#26996)
## What `npmjs.com` package pages return **HTTP 403** to automated link checkers and datacenter IPs (including GitHub Actions runners), regardless of user agent. This makes the scheduled `weekly-docs` Linkspector check fail on a valid link and fire a false-positive Slack alert every Monday. On PRs it can also red-X any change that touches the affected file. This adds `npmjs.com` to `ignorePatterns` in `.github/.linkspector.yml`, consistent with how the repo already allowlists other sites that block runner IPs (for example `merriam-webster.com`, `code.visualstudio.com`, `dotnet.microsoft.com`). ## Affected link Both occurrences are in `docs/about/contributing/frontend.md` (lines 43 and 293) and point to the same page, `https://www.npmjs.com/package/@coder/pixel-storybook`. These are the only `npmjs` links under `docs/`. Failing run: https://github.com/coder/coder/actions/runs/28785524667 ## Verification The link is valid; the failure is IP/environment-specific bot-blocking, not a dead link. ```text # Linkspector on the GitHub runner (from the failing run): Cannot reach https://www.npmjs.com/package/@coder/pixel-storybook Status: 403 (x2) # curl from a datacenter IP (HEAD, GET, and browser User-Agent all 403): HEAD (default UA): 403 GET (default UA): 403 GET (Chrome UA): 403 # npm registry API (authoritative existence check): GET registry.npmjs.org/@coder%2Fpixel-storybook 200 ``` Running Linkspector locally against `frontend.md` (browser path, non-runner IP) reports the link as valid, confirming the 403 is specific to blocked runner IPs. The edited config parses and runs cleanly under Linkspector, with `npmjs.com` present in `ignorePatterns` (25 patterns total). ## AI disclosure This change was generated by **Coder Agents** (an AI assistant) and reviewed by @nickvigilante before submission, per [`AI_CONTRIBUTING.md`](https://github.com/coder/coder/blob/main/docs/about/contributing/AI_CONTRIBUTING.md). <details> <summary>Investigation & decision log</summary> 1. **Reproduced the report.** The scheduled `weekly-docs` run failed at the "Check Markdown links" step. `gh run view --log-failed` showed exactly two Linkspector errors, both `Cannot reach https://www.npmjs.com/package/@coder/pixel-storybook Status: 403`, in `docs/about/contributing/frontend.md` (lines 43, 293). 2. **Confirmed it is a false positive.** `curl` from a datacenter IP returns 403 for HEAD, GET, and a real Chrome User-Agent, while the npm registry API returns 200 for the package. So the package page exists and works in normal browsers; npmjs.com just blocks automated/runner traffic. 3. **Scoped the change.** `git grep npmjs docs/` returns only those two links, both on `www.npmjs.com`. A domain-level `npmjs.com` pattern is a substring match that covers both and future npm links. 4. **Chose the established fix.** `.github/.linkspector.yml` already allowlists ~10 sites that 403 runner IPs. Added `npmjs.com` alongside them with an explanatory comment, matching the existing `merriam-webster.com` style. 5. **Validated.** YAML parses; Linkspector accepts the updated config; `npmjs.com` is present in `ignorePatterns`. </details> |
||
|
|
8853f5535a |
fix(.github/workflows): raise docs indexer POST timeout to 300s (#27095)
## Problem The `algolia-and-isr` job's "POST to coder.com docs indexer" step aborts at `curl --max-time 120`. A whole-branch docs reindex fetches and extracts a few hundred pages server-side and runs longer than two minutes, so curl gives up before the handler responds: ``` curl: (28) Operation timed out after 120000 milliseconds ``` The step never receives the handler's result even though the server is still processing, so a legitimate reindex is reported as a failure. ## Fix Raise `--max-time` on that POST from `120` to `300`, matching the indexer's server-side function budget so curl waits for the response instead of aborting mid-reindex. - Only the Algolia indexer POST is changed. - The `vercel-rebuild` deploy-hook curl is left at `120` (it returns immediately). - No behavior change beyond the timeout. Validated with `actionlint`. <details> <summary>Rationale & decision log</summary> - The indexer handler performs an **atomic whole-branch reindex**: fetch the manifest, fetch + extract every navigable page, then replace the index slice. On a large ref that is a few hundred pages at concurrency 8, which comfortably exceeds the old 120s curl budget. - `300s` aligns curl with the handler's own server-side function ceiling, so the workflow observes the real response (or a real error) instead of a false client-side timeout. - The deploy-hook POST in `vercel-rebuild` only fires a webhook and returns immediately, so its timeout is intentionally left unchanged. - If 300s later proves tight, the next levers are raising server-side extract concurrency (bounded by upstream raw-content rate limits) or moving the whole-branch reindex to an async job. Out of scope here. </details> --- > Opened as a **draft** by Coder Agents on behalf of @nickvigilante. |
||
|
|
c8f12dbf52 | fix(dogfood/coder): stop setting FIPS crypto-policies in workspace images (#27094) | ||
|
|
f2e8d72100 |
chore: apply openai-go bugfix to fix openrouter response parsing (#27092)
Applies https://github.com/coder/openai-go/pull/3 Closes https://github.com/coder/coder/issues/26469 `kylecarbs/openai-go` was renamed to `coder/openai-go` I've created a [branch](https://github.com/coder/openai-go/tree/coder/pinned) to track the changes we've made. We're far behind `main` now, so we should make an effort to update this as some point. I've manually tested using OpenRouter + GLM 5.2 as the bug report states and it works fine. <img width="824" height="321" alt="image" src="https://github.com/user-attachments/assets/804c527a-3a59-43bd-91c8-3b9bfb48df81" /> <img width="927" height="149" alt="image" src="https://github.com/user-attachments/assets/35829bc2-5597-430c-8ede-bb2ebabc73a5" /> <details> ``` : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING : OPENROUTER PROCESSING data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":"Yep","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":", I","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":"'m here","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":". What","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":" do you","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} : OPENROUTER PROCESSING data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":" need?","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]} data: {"id":"gen-1783515596-pm90JeoPBXFHqVq3dDlL","object":"chat.completion.chunk","created":1783515596,"model":"z-ai/glm-5.2-20260616","provider":"Novita","service_tier":null,"choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}],"usage":{"prompt_tokens":4855,"completion_tokens":13,"total_tokens":4868,"cost":0.00479794,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00479794,"upstream_inference_prompt_cost":0.0047579,"upstream_inference_completions_cost":0.00004004},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}} data: [DONE] ``` </details> Signed-off-by: Danny Kopping <danny@coder.com> |
||
|
|
affb359d13 |
feat: synchronise provider changes with WatchAIProviders (#27091)
## Why PR #26797 was accidentally merged into the stale `graphite-base/26797` branch instead of `main` (Graphite picked the wrong base), so its changes never landed on `main`. This PR re-lands that work as a clean cherry-pick onto the current `main`. ## What Adds a `WatchAIProviders` streaming RPC to the `ProviderConfigurator` service so a running standalone AI Gateway refetches its provider set when the provider configuration changes. The server subscribes to `AIProvidersChangedChannel` (published by the provider CRUD endpoints) and forwards each event as a payload-free signal, plus one signal on subscribe; the gateway calls `GetAIProviders` on each signal to rebuild its pool. The aibridged API is bumped to v1.2. Env-seeded providers don't need a signal: seeding finishes before coderd serves the gateway connection, so the gateway's initial fetch already reflects the seeded set. ## For reviewers The change is split into two commits to make review easy: 1. **`feat: synchronise provider changes with WatchAIProviders`** is a faithful cherry-pick of #26797, identical to the originally reviewed PR. It is committed without pre-commit hooks because it does not build against current `main` on its own. 2. **`fix: resolve cherry-pick conflicts against main`** contains only the deltas needed to re-land on current `main`, and passes the full pre-commit suite: - `coderd/aibridged/proto/aibridged.pb.go` regenerated via the proto make target (the cherry-picked copy was generated against the older proto). - `enterprise/cli/aigatewaystart.go` import block unioned; `main` added `os` and `strings` while the PR added `sync`. - Three `aibridgedserver.NewServer` test call sites that landed on `main` after the original branch diverged now pass the new `pubsub` argument. Refs https://linear.app/codercom/issue/AIGOV-465 *This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.* |
||
|
|
48f07e6e13 |
feat: add user AI spend endpoint (#26978)
## Description
Adds the `GET /api/v2/users/{user}/ai/spend` endpoint returning the
user's current AI spend, effective budget, and period bounds.
## Changes
- Add `userAISpendStatus` handler under the same feature/experiment gate
as `/api/v2/users/{user}/ai/budget`.
- Add `codersdk.UserAIBudgetSummary` (embedded into `UserAISpendStatus`)
and a `UserAISpendStatus` client method.
- Move `LimitSource` from `coderd/aibridge/budget` to `codersdk` so the
type is shared across endpoints.
Closes https://linear.app/codercom/issue/AIGOV-472
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
|
||
|
|
60d0859bde |
feat: add /healtz and /readyz endpoints to standalone Gateway (#26988)
Adds `/healthz` and `/readyz` endpoints to standalone AI Gateway. * `/healthz`: returns 200 once the AI Gateways HTTP server is listening. * `/readyz`: returns 200 when the DRPC connection to `coderd` is established. Cleanup: Removed `initConnection*` fields from `aibridged.Server` as they where not used anywhere. |
||
|
|
ccba3969ab |
feat: add ai-gateway start command (#26605)
> AI Tools were used to produce this PR This PR adds `coder ai-gateway start` command that runs the AI Gateway as an independent process. - Standalone process doesn't have access to DB. Uses DRPC services under `/api/v2/ai-gateway/serve`for auth, recording and provider initialization. - It only handles LLM traffic, other endpoints (eg. `/sessions`) are only available though `coderd`. - The standalone gateway reuses applicable flags from AI Gateway deployment options. Provider-seeding and coderd-only options are excluded. - Only added to fat build, the slim build stub rejects the command. Some wiring used by this new command is added. **`NewWebsocketDialer`** - implements the standalone gateway's connection to coderd's `/api/v2/ai-gateway/serve` endpoint. It upgrades to a WebSocket, multiplexes with yamux, and wires all DRPC services. **`AIGatewayDataPlaneMiddleware`** - extracts the per-request middleware chain (concurrency limiting, rate limiting, BYOK gating) into a shared function used by both the embedded route and the standalone gateway. **`RootCmd.ResolveClientConnection`** - resolve the deployment URL and builds an HTTP transport without requiring a session token. Used in `ai-gateway start`command as it authenticates using different credential type. --------- Co-authored-by: Danny Kopping <danny@coder.com> |
||
|
|
195dffc651 |
test: migrate WorkspacesPage tests to Storybook play stories (#26958)
Migrates the WorkspacesPage tests from vitest to Storybook play-function stories. The old `WorkspacesPage.test.tsx` rendered the page through `renderWithAuth` and MSW, which is slow and contributes to `test-js` timeout flakes. The new stories seed the react-query cache directly and assert the same behavior in `play` functions, so they run as Storybook interaction tests instead of in the vitest `unit` project. Coverage is preserved across all flows from the old test: rendering the empty and filled pages, deleting only the selected workspaces, the three batch-update cases (skipping up-to-date workspaces, updating a running workspace after acknowledging the restart risk, and ignoring dormant workspaces), stopping only the selected running workspaces, starting only the selected stopped workspaces, filtering workspace apps by health and visibility, and hiding the start button for an outdated stopped always-update workspace. The entire `WorkspacesPage.test.tsx` file is removed since it contained no pure-logic tests to keep. Closes CODAGT-686 |
||
|
|
9a1aab7986 |
test: migrate template settings tests to Storybook play stories (#26957)
Migrates the TemplateSettingsPage tests from vitest to Storybook play-function stories. The old `TemplateSettingsPage.test.tsx` rendered the full settings layout through `renderWithTemplateSettingsLayout` and MSW, which is slow and contributes to `test-js` timeout flakes. The new stories seed the react-query cache directly and assert the same behavior in `play` functions, so they run as Storybook interaction tests instead of in the vitest `unit` project. Coverage is preserved across four flows: a successful metadata update, the validation error surfaced in the form when the name is already taken, deprecating a template when the access_control entitlement is present, and leaving the deprecation message empty when it is not. The pure-logic description validation tests stay in `TemplateSettingsPage.test.tsx`. Relates to CODAGT-686 |
||
|
|
bc0f7cf0ae |
test: migrate schedule page tests to Storybook play stories (#26848)
Migrates the WorkspaceSchedulePage tests from vitest to Storybook play-function stories. The old `WorkspaceSchedulePage.test.tsx` rendered the full settings layout through `renderWithWorkspaceSettingsLayout` and MSW, which is slow and contributes to `test-js` timeout flakes. The new stories seed the react-query cache directly and assert the same behavior in `play` functions, so they run as Storybook interaction tests instead of in the vitest `unit` project. Coverage is preserved across four flows: enabling autostop seeds the template's default TTL, changing autostop on a running workspace shows the restart dialog after a successful save, a stopped workspace skips that dialog, and changing only autostart skips it as well. The pure-logic schedule and TTL conversion tests stay in `WorkspaceSchedulePage.test.tsx`. Relates to CODAGT-686 |
||
|
|
94605fa193 |
refactor(site): set square elements' dimensions with size- classes (#27071)
Finds all class combinations like `w-N h-N` and replaces them with `size-N`. - find: `/w-(\d+) h-\1(?=\s|")/g`, or `/h-(\d+) w-\1(?=\s|")/g` - replace with: `size-$1` The positive lookahead `/(?=\s|")/` matches whitespace or a double quote character after the number, to prevent false matches where the numbers aren't the same but share the same left digits: https://github.com/coder/coder/blob/b3766d62be9487732c7e657cf737e739cc5413da/site/src/pages/WorkspacesPage/WorkspacesTable.tsx#L124 Unfortunately we don't have a way to automatically enforce this, since there's no Biome equivalent for `eslint-plugin-tailwindcss`'s [`enforces-shorthand` rule](https://github.com/francoismassart/eslint-plugin-tailwindcss/blob/HEAD/docs/rules/enforces-shorthand.md) |