Commit Graph
15285 Commits
Author SHA1 Message Date
Ehab Younes 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
2026-07-13 17:33:40 +03:00
Nick Vigilante 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).
2026-07-13 10:30:10 -04:00
Jeremy Ruppel 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
2026-07-13 09:47:01 -04:00
Ehab Younes 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
2026-07-13 15:14:00 +03:00
Ethan 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.
2026-07-13 18:37:48 +10:00
Danielle Maywood 27ed052d86 fix(site): remove invalid Autocomplete hooks (#27177) 2026-07-13 09:06:36 +01:00
Ethan 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
2026-07-13 16:50:19 +10:00
Jake Howell 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>
2026-07-13 10:38:55 +10:00
Thomas Kosiewski 9af3d31036 fix: relabel advisor max uses setting from per run to per turn (#27046) 2026-07-10 09:36:12 +02:00
Thomas Kosiewski 3d8ffd34b3 fix(coderd/x/chatd): retry quickgen without temperature when model rejects it (#27120) 2026-07-10 09:13:42 +02:00
McKayla はな e59a67d63f fix(site): use RBAC-filtered organizations for embedded metadata (#27110) 2026-07-09 17:56:09 -06:00
Danielle Maywood d66e4d794f feat: add configurable reasoning effort to Coder agents (#26974) 2026-07-09 23:35:12 +01:00
dylanhuff-at-coder 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._
2026-07-09 13:59:49 -07:00
Dallin Stevens 37558fcdc9 fix(coderd/externalauth): preserve scopes on entra v1 token refresh (#24851)
Without this, Entra silently narrows scopes to the default set.
2026-07-09 11:49:19 -08:00
Michael Suchacz 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.
2026-07-09 18:25:34 +00:00
Thomas Kosiewski 3e608e791d feat(site): add claude-fable-5 and claude-mythos-5 known model defaults (#27131) 2026-07-09 20:02:59 +02:00
Andrew Aquino 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"
/>
2026-07-09 10:48:14 -07:00
Denis Afonso 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.
2026-07-09 18:47:50 +01:00
Mathias Fredriksson 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.
2026-07-09 18:30:30 +03:00
Andrew Aquino 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>
2026-07-09 08:13:38 -07:00
Bobby Ho 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.
2026-07-09 07:40:42 -07:00
Jake Howell 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>
2026-07-10 00:38:10 +10:00
Paweł Banaszewski 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.
2026-07-09 14:02:18 +00:00
Danny Kopping 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.*
2026-07-09 15:36:56 +02:00
Danny Kopping 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.*
2026-07-09 15:06:44 +02:00
Hugo Dutka 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.
2026-07-09 11:20:42 +00:00
Jon Ayers a6afcd046f fix: prevent deadlock between async error handling and failed subscribes (#27109) 2026-07-08 17:28:08 -05:00
George K 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
2026-07-08 13:01:29 -07:00
McKayla はな b5a445d3c2 fix(site): stop popover heading from overlapping provisioner tag fields (#27101) 2026-07-08 13:59:54 -06:00
Michael Suchacz 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.
2026-07-08 21:37:30 +02:00
Michael Suchacz 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.
2026-07-08 21:31:34 +02:00
Cian Johnston 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.
2026-07-08 19:02:19 +01:00
Nick Vigilante 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.
2026-07-08 12:08:40 -04:00
Nick Vigilante 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.*
2026-07-08 12:05:24 -04:00
Nick Vigilante 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.*
2026-07-08 11:43:29 -04:00
Nick VigilanteandCian Johnston 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>
2026-07-08 15:39:45 +00:00
Nick Vigilante 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.
2026-07-08 11:33:02 -04:00
Nick Vigilante 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.
2026-07-08 11:20:51 -04:00
Nick Vigilante 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 &amp; 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>
2026-07-08 11:11:00 -04:00
Nick Vigilante 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 &amp; 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.
2026-07-08 14:42:20 +00:00
Cian Johnston c8f12dbf52 fix(dogfood/coder): stop setting FIPS crypto-policies in workspace images (#27094) 2026-07-08 15:05:47 +01:00
Danny Kopping 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>
2026-07-08 13:36:42 +00:00
Danny Kopping 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.*
2026-07-08 15:32:17 +02:00
Susana Ferreira 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
2026-07-08 12:46:47 +01:00
Paweł Banaszewski 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.
2026-07-08 09:28:02 +00:00
Paweł BanaszewskiandDanny Kopping 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>
2026-07-08 11:12:53 +02:00
Ethan 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
2026-07-08 12:12:34 +10:00
Ethan 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
2026-07-08 12:12:29 +10:00
Ethan 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
2026-07-08 12:12:26 +10:00
Andrew Aquino 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)
2026-07-07 15:50:58 -07:00