mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
75fd7bc09ae71d7b66ba916dedfe170602c32aba
15498
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0c3c65d85b |
fix: stabilize latest workspace app status ordering (DEVEX-381) (#27041)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Closes [DEVEX-381](https://linear.app/codercom/issue/DEVEX-381/flake-test-tasksendwaitsforworkingappstate). Follow-up to #25648 and #25858, which addressed a different symptom of the same test. ## Symptom ``` task_send_test.go:348: context expired while waiting for trap: context deadline exceeded --- FAIL: Test_TaskSend/WaitsForWorkingAppState (26.02s) ``` Windows-only, on `test-go-pg (windows-2022)`. Reported four times since #25648 landed (2026-06-02, 2026-06-10, 2026-07-01). ## Root cause The test: 1. `setupCLITaskTest` inserts `workspace_app_status(state=idle)` at the end of setup. 2. `WaitsForWorkingAppState` then inserts `workspace_app_status(state=working)` before starting the CLI. 3. Both are persisted via `dbtime.Now()`, which rounds to microseconds. Windows `time.Now()` resolution is coarser than that (often ~1 ms or worse), so back-to-back calls frequently round to the same microsecond. 4. `GetLatestWorkspaceAppStatusesByWorkspaceIDs` has no tiebreaker: ```sql ORDER BY workspace_id, created_at DESC ``` Its sibling `GetLatestWorkspaceAppStatusByAppID` already uses `ORDER BY created_at DESC, id DESC` for exactly this reason. When the two rows collide, Postgres picks either. 5. On the failing runs, the query returned the `idle` row. `waitForTaskIdle` saw idle on the first poll, returned nil, `TaskSend` proceeded, and the CLI completed successfully in ~5 s. 6. But the test was blocked at `resetTrap.MustWait(ctx)` waiting for a **second** `ticker.Reset` that never happened. `WaitLong = 25s` elapsed, line 348 failed. CI log confirms the sequence: only one `Ticker.Reset(5s)` is caught, then `Ticker.Stop([]) call, matched 0 traps` (from `defer ticker.Stop()`), then the trap wait times out. This is the same class of flake Spike documented in #15923 and #21332 ("Windows in particular doesn't have high-resolution timers"), just hidden behind a SQL `ORDER BY`. ## Fix Two changes: 1. **`coderd/database/queries/workspaceapps.sql`**: add an `id DESC` tiebreaker to `GetLatestWorkspaceAppStatusesByWorkspaceIDs`, matching `GetLatestWorkspaceAppStatusByAppID`. Makes the query deterministic when `created_at` collides. 2. **`cli/task_test.go` / `cli/task_send_test.go`**: add a `withoutInitialAppStatus()` option to `setupCLITaskTest` and use it from `WaitsForWorkingAppState`. The test now inserts a single `working` row, so the collision cannot happen in the first place. Belt-and-braces with change 1. Comments in both places reference DEVEX-381 and #21332 so the next agent doesn't have to re-derive this. ## Verification - `go test ./cli -run 'Test_TaskSend' -count=1`: all 12 subtests pass, `WaitsForWorkingAppState` completes in ~5.6 s (was ~16 s previously due to a longer poll loop). - Stress: 20 sequential runs of `WaitsForWorkingAppState` on Linux, race-enabled binary, all pass in ~5.5 s each. - `go test ./coderd -run 'AppStatus|Task' -count=1` passes. - `go vet ./coderd/database/... ./cli/...` clean. - `make lint/emdash` clean. - `gofmt` clean. Not reproducible on Linux (real time between the two patches is orders of magnitude larger than microsecond); the Windows path is fixed by making the ordering deterministic and by not creating the collision in the first place. <details> <summary>Implementation plan & decision log</summary> ### Investigation 1. Pulled the failing job log for run `28483879823/job/84428355669`. 2. Traced the mock-clock trap sequence: one `NewTicker` and exactly one `Ticker.Reset(5s)` were caught, then `Ticker.Stop([]) call, matched 0 traps` fires (the `defer ticker.Stop()` on `waitForTaskIdle` return). This proves `waitForTaskIdle` returned after a single poll, not that the trap machinery hung. 3. The command exited with `<nil>` (`clitest.go:299: command "coder task send" exited with error: <nil>`) and a `POST /send` completed in 5.4 s. So the CLI succeeded; the test's own trap wait is what timed out. 4. The only `waitForTaskIdle` return-nil paths are `Active + CurrentState.State in {Idle, Complete, Failed}` and `Active + CurrentState == nil past 30s grace`. First observation of nil cannot be past 30s. So `TaskByID` must have returned `State == Idle`. 5. Traced `TaskByID` → `taskGet` → `workspaceData` → `GetLatestWorkspaceAppStatusesByWorkspaceIDs`. Found the missing tiebreaker; the sibling query one line above (`GetLatestWorkspaceAppStatusByAppID`) already had it. 6. Confirmed the two `PATCH /app-status` calls in the Windows log happened at `00:26:13.077` and `00:26:13.093`, well within Windows timer resolution. 7. Confirmed `dbtime.Now()` rounds to microseconds; Windows `time.Now()` doesn't have that precision, so `Round(time.Microsecond)` on two calls close together frequently produces equal values. ### Prior art from Spike - #15923: loosened `HeartbeatPeriod * 9/10` to `3/4` for Windows. - #21332: switched `assert.After` to `assert.NotBefore` because timestamps can equal on Windows. Both explicitly cite "Windows doesn't always have high-resolution timers available." ### Considered alternatives - **Only fix the test.** Works today but leaves the SQL query non-deterministic; another test that relies on `GetLatestWorkspaceAppStatusesByWorkspaceIDs` could hit the same collision. - **Only fix the SQL query.** Would give a stable answer but not necessarily the *right* one. If both patches share a `created_at`, `id DESC` picks whichever UUID sorted higher, still random with respect to insertion order. - **Make `dbtime.Now()` monotonic per process.** Cleanest at the source, but affects every timestamp in the database and has broader implications than a targeted flake fix. Going with both the query fix (defense in depth, matches existing pattern) and the test fix (eliminates the collision at the source) is the smallest change that closes the flake and hardens the query. ### Rejected commit-message scopes Changes touch both `cli/` and `coderd/database/`, so per AGENTS.md the scope is omitted for the cross-cutting commit and PR title. </details> |
||
|
|
0862c82e18 |
chore: bump the coder-modules group across 3 directories with 1 update (#27207)
Bumps the coder-modules group with 1 update in the /dogfood/coder directory: coder/code-server/coder. Bumps the coder-modules group with 1 update in the /dogfood/coder-envbuilder directory: coder/code-server/coder. Bumps the coder-modules group with 1 update in the /dogfood/vscode-coder directory: coder/code-server/coder. Updates `coder/code-server/coder` from 1.5.0 to 1.5.1 Updates `coder/code-server/coder` from 1.5.0 to 1.5.1 Updates `coder/code-server/coder` from 1.5.0 to 1.5.1 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
63ec93a7ce |
feat: add AWS Bedrock mantle endpoint to AI Gateway (#26745)
Implements https://linear.app/codercom/issue/AIGOV-213/add-bedrock-provider # AWS Bedrock mantle support in AI Gateway ## Summary Add support for the AWS Bedrock **mantle** endpoint (`bedrock-mantle.{region}.api.aws/anthropic/v1/messages`) to AI Gateway. Mantle serves Claude through the native Anthropic Messages API. We model it as a `protocol` field on the existing Bedrock provider settings (`invoke-model` default, or `mantle`) rather than as a new provider type, and we treat mantle as a pure passthrough: SigV4-sign and forward, no body translation. ## Background Claude on AWS Bedrock is reachable through two endpoints, each speaking exactly one wire protocol: 1. **InvokeModel** (existing): `bedrock-runtime.{region}.amazonaws.com`. Model id in the URL path, request translated into Bedrock's InvokeModel format, responses returned as a binary AWS eventstream. This is what AI Gateway already supported for Bedrock. 2. **Mantle** (this doc): `bedrock-mantle.{region}.api.aws/anthropic/v1/messages`. Native Anthropic Messages API: model in the body, plain SSE streaming. ## Why a `protocol` field, not a new provider type The alternative is to model mantle as its own `ai_provider_type` (`bedrock-mantle`) alongside `bedrock`. I chose the `protocol` field instead for two reasons: 1. Mantle reads more like a protocol of Bedrock than a separate provider. It is the same AWS account, credentials, region, and IAM, reached over a different wire protocol and host. One Bedrock provider with two protocols (`invoke-model` default and `mantle`) models that more organically than two provider types. 2. It avoids a database migration. The `protocol` field lives in the settings JSON blob (empty resolves to `invoke-model`, so existing providers are unaffected), whereas a new type means an enum value and the `ALTER TYPE ... ADD VALUE` migration that goes with it. ## Why passthrough, not translation The client already emits Bedrock-legal requests in mantle mode: ```sh export CLAUDE_CODE_USE_MANTLE=1 export CLAUDE_CODE_SKIP_MANTLE_AUTH=1 export ANTHROPIC_BEDROCK_MANTLE_BASE_URL=https://<coder>/api/v2/aibridge/<provider-name> ``` So the gateway just forwards the body and SigV4-signs it (service `bedrock-mantle`), and skips all the InvokeModel body-translation (model remap, thinking conversion, beta-flag allowlist, field stripping). This keeps the mantle path thin and avoids a second copy of translation logic to maintain. ## Consequences - Protocol-dependent fields: `model` / `small_fast_model` are used by InvokeModel but ignored by mantle (the client sends the model), and `base_url` is required for mantle but optional for InvokeModel. Validation is protocol-aware. - No central model control on mantle: because it is a passthrough, the operator cannot pin the model. - `region` and the `base_url` host must name the same region (the SigV4 scope must match the endpoint); a mismatch surfaces as `Credential should be scoped to a valid region`. ## Draft UI <img width="1100" height="579" alt="image" src="https://github.com/user-attachments/assets/37bab46d-8958-4a96-9f47-1fef3493e1b6" /> ## Follow-up PRs: - https://github.com/coder/coder/pull/27156 |
||
|
|
043d367cfe |
fix(site): show AI settings and AI Sessions in mobile admin menu (#27191)
## Problem The desktop `DeploymentDropdown` surfaces **AI** and **AI sessions** items under Admin settings (behind `canViewAISettings` / `canViewAIBridge`), but the mobile `MobileMenu` was never updated to match — its `AdminSettingsSub` only knows about Deployment, Organizations, Audit logs, Connection logs, and Healthcheck. This is a leftover from #25582 ("promote AI settings to a top-level section"), which threaded `canViewAISettings` through `Navbar` / `NavbarView` / `DeploymentDropdown` but did not touch `MobileMenu.tsx`. `canViewAIBridge` has the same oversight. ## Fix - Extend `MobileMenuPermissions` with `canViewAIBridge` and `canViewAISettings`. - Render **AI** (→ `/ai/settings`) and **AI Sessions** (→ `/ai-gateway/sessions`) in the mobile Admin settings collapsible, in the same order as the desktop dropdown. - Thread the two flags through `NavbarView` into `MobileMenu`. - Cover the new args in `MobileMenu.stories.tsx` (Admin story now shows the AI items; Auditor / OrgAdmin / Member keep them hidden). No backend, permission, or routing changes. > 🤖 This PR was drafted by Coder Agents on behalf of @tracyjohnsonux and needs a human review. |
||
|
|
5334cc80b6 |
fix(site): pass all properties of a multi-select dynamic parameter option to MultiSelectCombobox defaultOptions (#27145)
fixes DEVEX-463 Previously, a dynamic parameter of `form_type = "multi-select"` with default options selected would only pass in these 3 properties for each option fed into `MultiSelectCombobox`'s `defaultOptions`: - `value` - `label` - `disable` `defaultOptions` are used as the initial value of `MultiSelectCombobox`'s `selected` array of `Option`s. If the `icon` property is missing from a selected option, then no icon is shown: https://github.com/coder/coder/blob/e59a67d63fd522d1e95b42e57d4fbfe7d2bc5fef/site/src/components/MultiSelectCombobox/MultiSelectCombobox.tsx#L501-L502 Now `icon` and `description` are included among those properties passed to default selected options, when a default option's value can be found among the parameter's available options in the Terraform template. This fixes the bug where icons wouldn't display for default selected options. If no corresponding option is found, then we fall back to the old partial option object (`{ value, label, disable }`). <img width="1840" height="1191" alt="Screenshot 2026-07-09 at 10 08 54 PM" src="https://github.com/user-attachments/assets/91be4aaf-9016-46d6-871b-344c8f2504cb" /> |
||
|
|
15fd7b746a |
fix: use fake timers in edit workspace test (#27141)
Fixes an issue where the debounce delay plus edit delay exceeds the wait timeout. Also remove the mocked rich parameters call; it is not used. |
||
|
|
6f6d7539c8 |
feat: remove unused chat statuses pending, paused, and completed (#27064)
The chatd state machine only recognizes `waiting`, `running`, `error`, `requires_action`, and `interrupting`. Remove the unused `pending`, `paused`, and `completed` values from the database enum, backend, SDK, frontend, generated queries, and API docs. Migration `000543_chat_status_remove_unused` remaps existing `pending` rows to `running`, remaps `paused` and `completed` rows to `waiting`, drops the obsolete `idx_chats_pending` index, and recreates `chats_expanded` around the enum swap. It also removes the dead `AcquireChats` query and all remaining query literals for the deleted statuses. **NOTE**: The enum swap can break chat queries from older replicas during a mixed-version rollout because they still reference `'pending'::chat_status`. Chats are experimental, so this PR accepts that limited rollout window instead of adding a two-release expand and contract sequence. > This PR was authored by Mux (AI agent) on Mike's behalf. |
||
|
|
e0b57f3154 |
chore: remove mui from <IconsPage /> (#27201)
This pull-request removes the MUI specific components from `<IconPage />`. |
||
|
|
aee528e044 |
fix: remove @emotion/react from <LastSeen /> (#27199)
This removes the manual `@emotion/react` we had from the theme being
injected in `<LastSeen />`. This now is relying on Tailwind 🙂
|
||
|
|
ad29777cb2 | feat: NATS mTLS pubsub implementation (#26902) | ||
|
|
010d96c3cd |
chore: remove useTheme from <OAuth2AppsSettingsPageView /> (#27198)
This wasn't used, removing 🙂
|
||
|
|
6580cdcf7f |
refactor: use AI budget period from deployment config (#27117)
## Description
Read the AI budget period from the deployment config on both the RPC server and the `/users/{user}/ai/spend` endpoint, instead of hardcoding `month`. Drops the `period_start` RPC parameter that was incorrectly introduced in #26915: the period should have been derived from the deployment config from the start.
## Changes
- Add `codersdk.NewAIBudgetPeriodFromString`, mirroring `NewAIBudgetPolicyFromString`.
- `aibridgedserver.Server` takes a `quartz.Clock`, reads `BudgetPeriod` from the deployment config at construction, and computes the period window inside `IsBudgetExceeded`.
- Remove `period_start` from `IsBudgetExceededRequest` and stop sending it from the daemon.
- The `userAISpendStatus` endpoint reads the period from `AIBridgeConfig.BudgetPeriod` instead of hardcoding month.
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
|
||
|
|
199b5936c1 |
fix(docs): replace invalid </br> tags and format swallowed placeholder URL (#27174)
## Summary Fixes two classes of invalid/broken HTML in hand-written docs. Both are visible problems in today's rendered docs, independent of any docs-engine work. 1. **`</br>` is not a real HTML tag.** `br` is a void element with no closing form; browsers error-correct `</br>`, but it is invalid HTML. Replaced all 15 usages with `<br />` across: - `docs/admin/templates/extending-templates/dynamic-parameters.md` - `docs/admin/users/idp-sync.md` - `docs/tutorials/best-practices/organizations.md` 2. **Browser-swallowed placeholder URL.** In `docs/ai-coder/github-to-tasks.md`, `https://<your-coder-url>/settings/external-auth` was unformatted, so HTML renderers parse `<your-coder-url>` as an unknown tag and drop it. The live docs currently render the broken text `re-authenticate at https:///settings/external-auth`. Wrapped in backticks, matching every other instance in the same file. Table realignment noise in the diff is from `fmt/markdown` (`<br />` is one character wider than `</br>`). A repo-wide grep confirms no remaining `</br>` and no other unformatted `https://<placeholder>` URLs in prose (other hits are inside code fences or already backticked). The equivalent placeholder issues in **generated** reference docs (CLI help strings, swagger annotations) are intentionally out of scope and tracked separately in [DOCS-551](https://linear.app/codercom/issue/DOCS-551/backtick-placeholder-syntax-in-generated-reference-docs-cli-help). Tracking issue: [DOCS-550](https://linear.app/codercom/issue/DOCS-550/fix-invalid-br-tags-and-browser-swallowed-placeholder-url-in-hand) --- Created by Coder Agents on behalf of @nickvigilante. |
||
|
|
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. |