mirror of
https://github.com/coder/coder.git
synced 2026-11-01 04:47:17 +08:00
d014d4c372fb7dfdb3b0ea00a9935fec9b9528f9
298
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
18b37e64f5 |
chore(site/e2e): re-enable externalAuth suite (#26648)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` ← this PR Removes the `test.describe.skip` introduced in #17235 so the externalAuth suite runs in CI again. Splitting the five commits keeps the bisection signal clean if CI behaviour changes after the un-skip. Local verification of the full stack: `pnpm playwright:test e2e/tests/externalAuth.spec.ts --repeat-each=20 --workers=1` → 61/61 passing in 4m48s. Large whitespace churn in the diff is biome reformatting the body to its natural indentation after the single-line `test.describe(...)` opener. The only semantic change is removing `.skip`. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/internal/issues/356 |
||
|
|
cfc66b5de3 |
fix: reset both providers in external auth hook (#26798)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` ← this PR 5. #26648 `chore(site/e2e): re-enable externalAuth suite` Surfaced by stress-testing the leaf state of the stack locally with `--repeat-each=N`. The suite passes on iter 1, then deterministically fails the device test on every subsequent iteration: | iter | device | web | workspace | |---|---|---|---| | 1 | ✓ 2.8s | ✓ 2.5s | ✓ 6.2s | | 2 | **✘ 30.1s** | ✓ 2.6s | ✓ 6.2s | | 3 | **✘ 30.1s** | ✓ ~2.5s | ✓ ~6s | | … | … | … | … | `resetExternalAuthKey` in `site/e2e/hooks.ts` only deletes the link for `gitAuth.webProvider`, never `gitAuth.deviceProvider`. So: ```ts path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`, ``` Iter 1 of the device test creates a link for the `device` provider against the test user. The cleanup hook between tests resets `web` but not `device`. On iter 2 the page hits `/external-auth/device`, `externalAuthProviderQuery.data.authenticated` is already `true`, the page renders the authorized state immediately, and the test's `sentPending.wait()` hangs forever because the mock token endpoint is never polled. The whole test times out at 30s. Fix: reset every provider the suite exercises. Refactored the per-link delete into a Promise-returning helper so errors actually propagate; the previous `throw` inside the response and `error` callbacks crashed the worker rather than rejecting the call. Doesn't affect CI (single run per PR, fresh `--ephemeral` coderd), but it makes the suite robust to local stress testing and to anyone re-running it in the same coderd process. Filed before the un-skip lands so the un-skipped suite is repeatable from day one. Local verification: `pnpm playwright:test e2e/tests/externalAuth.spec.ts --repeat-each=20 --workers=1` → 61/61 passing in 4m48s. Same command before this fix → 1 pass, then deterministic device-test timeouts. Refs https://linear.app/codercom/issue/DEVEX-413 <details> <summary>Why a separate PR</summary> This is the fourth distinct root cause that piled up while the suite was skipped, and it's orthogonal to the other three. #26575 fixes the listener leak, #26793 fixes the 404 hook contract drift, #26795 fixes the dropped invalidation, and this PR fixes the per-test cleanup so the suite is actually repeatable. #26648 then just flips `.skip`. </details> |
||
|
|
46ec620767 |
fix(site): deflake adjust user theme preference (#28219)
## Summary Deflakes the `adjust user theme preference` Playwright test (`site/e2e/tests/users/userSettings.spec.ts`), tracked in DEVEX-415. The test selected the Light theme and then hard-navigated with `page.goto` before the optimistic appearance update was persisted. The navigation could cancel the in-flight `PUT /api/v2/users/me/appearance`, so the reloaded document embedded the stale `dark` preference and the final assertion flaked. `toPass` retries could not help because retrying the reload only re-reads the still-stale persisted state. ## Fix Wait for the appearance form's save spinner to clear before the hard reload, mirroring how other settings tests wait for a visible save confirmation. Asserting the optimistic light class first guarantees the spinner is already showing if a save started; a repeat run that is already light never shows it, so the test is idempotent and there are no direct API calls. To give the test a UI signal, `Spinner` gets an opt-in `label` prop that exposes it as a `role="status"` live region with an `aria-label` (decorative otherwise). `Loader` moves its label onto its own status container so it keeps a single status region. ## Changes - `site/src/components/Spinner/Spinner.tsx`: opt-in `label` prop. - `site/src/components/Loader/Loader.tsx`: label on its own status region. - `site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx`: label both appearance save spinners. - `site/e2e/tests/users/userSettings.spec.ts`: wait for the save spinner. ## Validation - `biome check` and `tsc -p .` pass. - Loader + AppearancePage unit tests and the affected storybook tests pass. - The e2e test passed 20/20 under `pnpm playwright:test -g "adjust user theme preference" --repeat-each 20`. <details> <summary>Implementation plan & decision log</summary> # DEVEX-415: Fix flake in "adjust user theme preference" e2e test ## Problem Playwright test `site/e2e/tests/users/userSettings.spec.ts` → `adjust user theme preference` flakes. After selecting the Light theme and hard-navigating to `/`, the reloaded page sometimes stays `dark`, failing the final assertion. CI Flake Bot has recorded repeated recurrences on `main` (latest 2026-08-17, runs `32004239187`, `31745261984`) even after PR #25183 added `toPass` retries. ## Root cause (confirmed by reading the code) The appearance update is optimistic and its persistence is not awaited before navigation: - `updateAppearanceSettings` (`site/src/api/queries/users.ts`) has an `onMutate` that optimistically writes the new theme into the React Query cache. The `<html>` class flips to `light` immediately, before the `PUT /api/v2/users/me/appearance` completes. - `useQueuedAppearanceSubmit` (`site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.tsx`) serializes submits: if a request is in flight, the next is queued and only fires after the first settles. - A fresh `member` user has **empty** appearance settings. `migrateLegacyPreference` (`site/src/theme/themeMode.ts`) maps empty settings to `{ mode: "single", theme: "dark" }` (`DEFAULT_THEME = "dark"`). So the "Theme mode" dropdown already starts on **Single theme** and `<html>` starts `dark`. Selecting "Single theme" in the test is therefore a **no-op** (`onChangeMode` early-returns when `mode === draft.mode`) and fires **no** PUT. The only appearance PUT in the test is the one from clicking "Light default" (`onSelectSingle("light")` → `theme_preference: "light"`). - The test's first `expectLightThemeClasses(page)` passes purely from the optimistic cache. It then calls `page.goto("/")` almost immediately (~20 ms after the click). If PUT #2 (the light one) has not persisted server-side, the new document loads the still-persisted `dark` preference from embedded metadata, and every retry of the post-navigation assertion sees `dark` for the full 10 s window. `toPass` cannot help post-navigation because it only re-reads stale, already persisted state; it cannot make an unfinished/queued PUT complete. ## Implemented fix: wait for the saving spinner (UI signal) before navigation > Iteration history: (1) An earlier attempt waited on the appearance `PUT` via > a `waitForApiCall` helper, but that couples the test to a state transition > and is non-idempotent (a repeat run that is already light fires no PUT, so > the wait times out). CI retries reuse the same ephemeral server, so this is a > real hazard. (2) A second attempt confirmed persistence with > `page.request.get(...)`, but that calls the API directly and stops being a > site test. Both were rejected. The repo's non-flaky settings tests wait for a **visible save confirmation** (e.g. "settings updated successfully" toasts) before trusting the result. The appearance theme form has no toast; its only save-in-progress feedback is the `<Spinner>`. So the fix mirrors that pattern using the spinner: 1. Make the theme section's spinner identifiable via a new opt-in `label` prop on `Spinner` (sets `role="status"` + `aria-label` only when provided; decorative otherwise). `Loader` moves its label onto its own `status` container so it keeps a single status region and its `getByLabelText` queries keep working. 2. In the test, after clicking "Light default", assert the optimistic light class, then wait for that spinner to be hidden before `page.goto("/")`. Why this is correct and idempotent: - React Query sets `isPending` before `onMutate` applies the optimistic cache update, so by the time the optimistic light class is visible the spinner is already showing if a save started. Waiting for it to clear guarantees the PUT settled (and was not canceled by navigation) before the reload. - A repeat run that is already light: clicking "Light default" is a no-op radio change, no PUT fires, the spinner never shows, and `toBeHidden` passes immediately. The reload still shows light. - No direct API calls: the test only observes site UI. ### Implemented changes `Spinner` gains an opt-in `label` prop (`site/src/components/Spinner/Spinner.tsx`): ```tsx role={label ? "status" : undefined} aria-label={label} ``` `Loader` carries the label on its own status container (`site/src/components/Loader/Loader.tsx`), and the appearance save spinners use the new prop (`AppearanceForm.tsx`): ```tsx <Spinner loading={isUpdating} size="sm" label="Saving theme preference" /> <Spinner loading={isUpdating} size="sm" label="Saving terminal font" /> ``` `site/e2e/tests/users/userSettings.spec.ts`: ```ts await expect( page.getByRole("combobox", { name: /theme mode/i }), ).toContainText("Single theme"); // precondition: single mode const singleThemeGroup = page.getByRole("group", { name: "Theme" }); await expect(singleThemeGroup).toBeVisible(); await singleThemeGroup.getByText("Light default", { exact: true }).click(); await expectLightThemeClasses(page); // optimistic DOM => spinner showing if saving await expect( page.getByRole("status", { name: "Saving theme preference" }), ).toBeHidden(); // save settled before the hard reload await page.goto("/", { waitUntil: "domcontentloaded" }); await expectLightThemeClasses(page); ``` Validation: `biome check`, `tsc -p .` pass; Loader + AppearancePage unit tests and the affected storybook tests pass; the e2e test passed 20/20 under `pnpm playwright:test -g "adjust user theme preference" --repeat-each 20` (idempotent). ## Alternatives considered - **Wait on the appearance `PUT` via a `waitForApiCall` helper**: rejected. It couples the test to a state transition and is non-idempotent, a repeat run that is already light fires no PUT so the wait times out (14/15 repeats failed). CI retries reuse the same ephemeral server, so this is a real hazard, not just a local-repeat artifact. - **Confirm persistence with `page.request.get(...)`**: rejected. It calls the API directly and stops being a site test. - **Default `role="status"` on the shared `Spinner`**: rejected. `Loader` wraps `Spinner` in its own `status` div, so a default would nest two status regions and break `Loader.test.tsx`. The opt-in `label` prop avoids this. - **Add a networkidle wait or sleep before navigation**: rejected. Violates the repo guidance against `time.Sleep`-style timing hacks and is inherently racy. - **Fix product behavior instead of the test**: out of scope. Related bug DEVEX-94 ("Light Theme setting not respected until Appearance page opened") tracks product-side persistence/embedding behavior; this task is scoped to stabilizing the e2e test. ## Files touched - `site/src/components/Spinner/Spinner.tsx` (new opt-in `label` prop). - `site/src/components/Loader/Loader.tsx` (label on its own status region). - `site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx` (use the `label` prop on both appearance save spinners). - `site/e2e/tests/users/userSettings.spec.ts` (wait for the spinner). </details> --- This PR was created by Coder Agents on behalf of @jeremyruppel. --------- Co-authored-by: Samuel Volin <sam.volin@coder.com> |
||
|
|
e1fa247e59 | feat: redirect to the template builder after first time setup (#27670) | ||
|
|
c424a76a12 | feat: wire chat search box to full-text search (#27973) | ||
|
|
4e512f786f |
fix: prefetch outdated Coder CLI in e2e setup instead of in the test (#27629)
## Summary `e2e/tests/outdatedCLI.spec.ts` has a 30 second budget in which it must create a template and workspace, start an agent, download an 84 MiB release binary from GitHub, and then exercise the actual thing under test: whether a `v2.8.0` client can still SSH into a workspace served by HEAD. In the run that filed this ticket, `install.sh` spent **20.04 seconds** of that budget on an HTTP request the test does not need, leaving 5.4 seconds for the download. The SSH flow never executed. Worth being precise about the shape, because it changes the fix. The stall is not in the code under test and it is not an SSH problem. `install.sh` resolves the latest stable release version **unconditionally**, even when `--version 2.8.0` is passed explicitly, and on the pinned path that value feeds nothing but a cosmetic post-install advisory string. Two thirds of the test's budget went to producing one sentence of console output that the test discards. Refs: https://github.com/coder/internal/issues/1571 ## Problem ### What the test is for This is a backward-compatibility test, and `v2.8.0` is the compatibility floor it enforces rather than a "supported version" in the release-channel sense. The pin traces to one code comment, `we no longer support versions prior to Tailnet v2 API support`, citing 059e533544; that commit first shipped in v2.8.0, so the pin sits exactly on the boundary it names. Worth stating plainly: this is the oldest client expected to still interoperate, not a version that receives patches. Release support is mainline / stable / n-2 / ESR, all far newer. The test's value is that it runs the *real* historical binary, compiled in Feb 2024, against a current server: `codersdk` REST compatibility, tailnet coordination v2, DERP negotiation, `coder ssh --stdio` as an SSH transport, and the agent accepting a session. Nobody gets to assert what that old client sends over the wire, which is exactly why the binary has to be downloaded rather than faked. The structural defect is that the download shares a timeout with the assertion: ```text ┌─────────────────────────────────────────────────────────────────┐ │ ONE 30-second Playwright test budget │ ├──────────────────────────────┬──────────────────────────────────┤ │ What we want to measure │ Incidental setup │ │ (deterministic, local) │ (network, non-deterministic) │ │ │ │ │ • template + workspace │ • HTTP HEAD to github.com │ │ • agent connect │ • 84 MiB download from │ │ • coder ssh --stdio │ GitHub release CDN │ │ • SSH handshake + exec │ • tar extraction │ │ • workspace stop │ │ └──────────────────────────────┴──────────────────────────────────┘ ~7-14 s, stable 0 s (cached) .. ∞ (unbounded) ``` A test that asserts protocol compatibility should not be able to fail because `github.com` was slow. ### The evidence CI runs Playwright with `DEBUG: pw:api`, and `downloadCoderVersion` passes `TRACE=1` to `install.sh`, which makes it `set -x`. The job log therefore stamps every phase. Reconstructed from [job 80049661296](https://github.com/coder/coder/actions/runs/27124540074/job/80049661296), `t=` relative to test start: ```text t=+0.000 08:17:44.671 browserContext.newPage <- test starts t=+0.882 08:17:45.553 login complete t=+4.228 08:17:48.899 workspace create submitted t=+4.519 08:17:49.190 agent-status-ready visible <- startAgent returns t=+4.526 08:17:49.197 install.sh: parse_arg --version 2.8.0 ... <- downloadCoderVersion t=+4.531 08:17:49.202 curl -sSLI https://github.com/coder/coder/releases/latest : : 20.042 SECONDS OF NOTHING : (agent logs keepalives; the page sits idle) : t=+24.573 08:18:09.244 response= 200 .../releases/tag/v2.33.6 <- probe returns t=+24.575 08:18:09.246 STABLE_VERSION=2.33.6 <- feeds a log line t=+24.582 08:18:09.253 curl -#fL -o .../coder_2.8.0_linux_amd64.tar.gz.incomplete : 5.4 s of an 84 MiB download t=+30.000 08:18:14.671 Playwright kills the test ``` Three observations rule out the originally suspected cause (slow SSH readiness or general runner slowness): - **The SSH flow never started.** `sshIntoWorkspace` is called after `downloadCoderVersion` returns, and it never returned. There is no `coder ssh --stdio` process in the log. - **The agent was healthy.** `agent-status-ready` resolved in 88 ms, and through the entire 20 second stall the agent logs a live DERP connection, successful STUN, and a completed wireguard handshake. - **The runner was fast, not slow.** Login plus template plus workspace plus agent took 4.5 seconds. ### Where the 20 seconds goes ```text install.sh main() ... L431 STABLE_VERSION=$(echo_latest_stable_version) <- ALWAYS runs | +-- echo_latest_stable_version() (install.sh:94) curl -sSLI https://github.com/coder/coder/releases/latest # no --connect-timeout # no --max-time # non-200 => exit 1 (hard failure) L454-461 the only consumers when --version is pinned: if VERSION == STABLE_VERSION: STABLE=1 L148 advisory="To install our stable release (v${STABLE_VERSION}), ..." L159 "Coder ${channel}release v${VERSION} installed. ${advisory}" ``` That is the whole dependency chain. `-sSLI` also follows redirects and `/releases/latest` *is* a redirect, so this is at minimum two round-trips to `github.com` with no timeout ceiling on either. ### Why 30 seconds and not 60 `test.setTimeout(60_000)` used to be on this test. #16236 removed it, and that removal was deliberate: it was itself a flake fix (coder/internal#204, #279) whose thesis was that `go run` compiling inside a resource-constrained test run was the problem. Having pre-built the binary, it consistently stripped the allowances that existed to absorb compile time: | File | Change in #16236 | Was that allowance really compile time? | |---|---|---| | `app.spec.ts` | `setTimeout(75_000)` removed, click timeout `60_000` -> `10_000` | Yes | | `webTerminal.spec.ts` | `setTimeout(75_000)` removed | Yes | | `helpers.ts` | agent-ready wait `45_000` -> `15_000` | Yes | | `outdatedCLI.spec.ts` | `setTimeout(60_000)` removed | **No: also an 84 MiB download** | | `outdatedAgent.spec.ts` | timeout untouched, 60 s survives | n/a | The reasoning was sound and the sweep internally consistent. It had one blind spot: for `app.spec.ts` and `webTerminal.spec.ts` that budget genuinely was the compiler's, but here it covered compile time **plus** a release download, and only the compile half went away. With 60 seconds, the failing run above would have finished in roughly 31 to 43 seconds and passed. ### Budget arithmetic At `t=+24.58` the test still had to do: | Remaining work | Realistic cost | |---|---:| | Download 84 MiB tarball | 2 - 8 s | | `tar` extract | 0.3 - 1 s | | `coder ssh --stdio` cold start | 0.5 - 2 s | | Tailnet dial + SSH handshake | 1 - 3 s | | `stopWorkspace` | 2 - 4 s | | **Needed** | **~6 - 18 s** | | **Available** | **5.42 s** | ## Fix Move the download into the existing `testsSetup` Playwright project, where it gets a 300 second budget and where a failure is attributed to the download rather than to SSH. ```mermaid flowchart TB subgraph BEFORE["BEFORE: one budget, two concerns"] direction TB T1["tests project, timeout 30s"] T1A["outdatedCLI.spec.ts<br/>login / template / workspace / agent<br/><b>downloadCoderVersion <- NETWORK</b><br/>sshIntoWorkspace / exec / stopWorkspace"] T1 --> T1A end subgraph AFTER["AFTER: network work has its own clock"] direction TB S2["testsSetup project, timeout 300s"] S2A["downloadCoderVersions.spec.ts<br/>stable-version probe + 84 MiB + retries<br/>all live HERE"] T2["tests project, timeout 60s"] T2A["outdatedCLI.spec.ts<br/>downloadCoderVersion = cache hit, ~300ms<br/>SSH path gets the whole budget"] S2 --> S2A S2A -- "dependencies" --> T2 T2 --> T2A end BEFORE ~~~ AFTER style T1A fill:#ffe5e5,stroke:#cc0000,stroke-width:2px style S2A fill:#e5ffe5,stroke:#007700,stroke-width:2px style T2A fill:#e5ffe5,stroke:#007700,stroke-width:2px ``` ### Why it works `downloadCoderVersion` was already idempotent and cache-checking: it spawns `<binaryPath> version` first and returns early on exit 0. So the test keeps its existing call and that call simply becomes a no-op costing a few hundred milliseconds. **No test logic changes.** ```mermaid sequenceDiagram autonumber participant S as testsSetup:<br/>downloadCoderVersions participant IS as install.sh participant GH as github.com participant T as tests:<br/>outdatedCLI participant CD as coderd + agent Note over S: budget 300s S->>IS: downloadCoderVersion(v2.8.0) IS->>GH: stable-version probe (unbounded) IS->>GH: fetch 84 MiB asset GH-->>IS: /tmp/coder-e2e-cache/bin/coder-e2e-2.8.0 IS-->>S: binaryPath Note over T: budget 60s, local only T->>T: downloadCoderVersion(v2.8.0) Note right of T: spawn "<bin> version" -> exit 0<br/>returns early, ~300ms, no network T->>CD: coder ssh --stdio, handshake, exec "exit 0" CD-->>T: exit code 0 ``` ### Why the prefetch is non-fatal The obvious implementation raises on failure. That would be wrong here, and I verified why rather than assuming: `tests` declares `dependencies: ["testsSetup"]`, and a failing setup project stops dependent tests from **running at all**. Adding a deliberately-throwing setup spec produced: ```text ✓ 1 [testsSetup] › addUsersAndLicense.spec.ts › setup deployment (11.7s) ✓ 2 [testsSetup] › downloadCoderVersions.spec.ts › download outdated CLI (353ms) ✘ 3 [testsSetup] › zzTempFail.spec.ts › temporary blast radius probe (0ms) 1 failed 1 did not run <- outdatedCLI never ran 2 passed ``` So raising would convert a one-test flake into a whole-suite outage on any GitHub hiccup. Instead the prefetch logs a warning and returns, and the test's own `downloadCoderVersion` call fetches inline as it does today. The failure path is therefore no worse than the status quo, and the success path removes the network from the test entirely. Of the three policies available (fail hard, fall back inline, or skip the test), this is the only one that cannot regress anything: it never blocks the suite, and it never silently drops coverage the way an auto-skip would. ### Restoring the 60 second budget This is the second half of the change, and it exists for the fallback path above. It cannot reintroduce what #16236 fixed: the timeout value has no causal relationship to how the binary is produced, `coderBinary` stays pre-built, `go run` stays gone, and only `outdatedCLI.spec.ts` is touched. It does give back a bounded sliver of the CI-latency goal, and the bound is small. A passing run is unaffected. The cost lands only when this one test hangs, and then it is +30 s once: `--workers 1` so there is no fan-out, `CODER_E2E_TEST_RETRIES` is unset in CI so `retries` is 0 and nothing multiplies it, and the job budget is `timeout-minutes: 20`. ## Measurements Four scenarios, locally on darwin/arm64 against a freshly built `site/e2e/bin/coder`: | Scenario | setup spec | `outdatedCLI` | `install.sh` inside the test? | Result | |---|---:|---:|---|---| | Cold, empty cache | 5.7 s | 10.0 s | **no**, ran in setup | ✓ passed | | Warm cache | 340 ms | 11.9 s | **no**, 0 invocations | ✓ passed | | Prefetch fails, cache empty | 1 ms | 15.4 s | yes, inline fallback | ✓ passed | | Setup spec throws | n/a | did not run | n/a | blast radius above | The cold run is the load-bearing one: `install.sh` is invoked from the setup spec and the test runs local-only in 10.0 s, so the 84 MiB download and the 20 s probe are no longer on the assertion's clock. For context on what "local only" costs, eight consecutive `main` runs where the CI cache already made `install.sh` a no-op: | Job | duration | |---|---:| | 90382498172 | 11.7 s | | 90352398170 | 12.1 s | | 90335547858 | 8.1 s | | 90317232684 | 13.7 s | | 90297156949 | 8.4 s | | 90280065546 | 6.9 s | | 90265047082 | 6.9 s | | 90250803989 | 6.6 s | 6.6 to 13.7 seconds. This change makes that the only path rather than the lucky one. Also checked: the test name is byte identical (`ssh with client v2.8.0`) so flake tracking keeps matching it, `outdatedAgent` remains skipped, and `webTerminal`, `auditLogs`, and `updateTemplate` still pass, so the added setup dependency disturbs nothing. The full 60-test suite was not run locally because the premium tests need `CODER_E2E_LICENSE`. ## Also in this change The pinned versions move to `site/e2e/constants.ts` as `oldestSupportedCLIVersion` and `oldestSupportedAgentVersion`, so the setup spec and the tests share one source of truth, and the comments explaining *why* those particular versions travel with them. The CI cache key follows them there: it previously hashed the two spec files, and now hashes `constants.ts`, so it still invalidates exactly when a pinned version changes. ## Not addressed here The 20 second probe is relocated, not removed. `install.sh` still resolves the latest stable version on every pinned install, with no `--connect-timeout` or `--max-time`, and still treats a non-200 as fatal, so a GitHub hiccup can fail an install whose target tarball is already cached locally. That is a user-facing bug in its own right and wants its own PR, since fixing it means deciding what a pinned install should print when we no longer look up what "stable" currently is. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
00d134ebfd | chore: remove classic parameter UI (#25014) | ||
|
|
5699f1cdfb |
fix: retry and cache e2e Coder release downloads to reduce test-e2e ssh flake (#27470)
closes DEVEX-651 ## Summary Fixes coder/internal#218 (`flake: e2e-test / test ssh`). Despite the title, the `ssh with client v2.8.0` / `ssh with agent v2.12.1` cases (`site/e2e/tests/outdatedCLI.spec.ts`, `outdatedAgent.spec.ts`) are not failing because of a bug in SSH. They fail during **setup**, in `downloadCoderVersion()`, which runs `install.sh` to fetch an old Coder release from GitHub. Transient GitHub errors (HTTP 403/503, surfacing as nonzero `curl` exit codes such as 22 or 1) make `install.sh` fail and take the whole ssh test down with it. This is an external-download flake, confirmed by the recurring `install.sh failed with code {22,1}` evidence in the issue thread and Ethan's note ("Networking issues again"). ## Changes 1. **Retry-with-backoff** (`site/e2e/helpers.ts`): `downloadCoderVersion()` now retries `install.sh` up to 5 times with exponential backoff and jitter (~1s, 2s, 4s, 8s). A single transient download failure no longer fails the test. `install.sh` already reuses completed binaries and resumes partial downloads (`curl -C -`), so retries are cheap. 2. **Cross-run cache** (`.github/workflows/ci.yaml`): the `test-e2e` job now persists `/tmp/coder-e2e-cache` with `actions/cache`, so most runs skip the GitHub download entirely. The key is derived from the spec files that pin the downloaded versions, so it invalidates when those versions change. Saves are restricted to `main` (`restore` runs everywhere), matching the existing cache-poisoning convention used for the Vale and golangci-lint caches. Before this change, neither retry, mirror, nor cross-run caching protected this path; the only caching was within a single run. ## Testing - `biome check e2e/helpers.ts` passes. - `tsc --noEmit` introduces no new errors. - CI `test-e2e` exercises the changed path. <details> <summary>Investigation notes</summary> - The failure always originates in `downloadCoderVersion` -> `install.sh` -> `fetch()` (`curl -#fL ... https://github.com/coder/coder/releases/download/vX.Y.Z/...`). - `curl` exit 22 = server returned an HTTP error (403 seen in logs); exit 1 = other transient failure. GitHub also returned 503s across the workflow in some occurrences. - `/tmp/coder-e2e-cache` was not persisted by any `actions/cache` step in `ci.yaml`, so every fresh job re-downloaded from GitHub and was exposed to the flake. - Retry addresses transient failures; the cache removes the dependency on GitHub for most runs. Combined, they target the root cause at two layers. </details> --- This PR was generated by Coder Agents on behalf of @aqandrew. |
||
|
|
b8727d9c23 | fix: don't show admin settings dropdown to everyone (#27481) | ||
|
|
0caaac5d27 |
fix: prebuild coderd before Playwright startup for e2e tests (#27136)
The test-e2e job could flake while Playwright waited for its webServer to become ready. CI already built site/e2e/bin/coder, but that binary was built before pnpm build and without the embed tag, so Playwright ignored it and started coderd with go run -tags embed instead. That put Go compilation, including frontend asset embedding, inside Playwright's webServer timeout. Local runs had a similar ordering trap. pnpm playwright:test invoked Playwright directly, and Playwright starts webServer before globalSetup, so the existing preflight build could not guarantee a binary existed before webServer startup. Build the E2E coder binary with the embed tag and make it depend on site/out/index.html. In CI, build the frontend first, then build the E2E binary, then run Playwright. For local runs, have pnpm playwright:test and pnpm playwright:test-ui run make site/e2e/bin/coder before invoking Playwright. Playwright now starts the prebuilt site/e2e/bin/coder binary and waits on the public /healthz endpoint instead of an authenticated API endpoint. This keeps compilation outside the webServer readiness timeout and makes the readiness probe quieter and more direct. I moved to /healtz because the existing endpoint was returning 401, which still means “server is up,” but it is a strange readiness signal. /healthz is explicitly public and returns 200 OK once the HTTP server is accepting requests. Caveat: running pnpm exec playwright test --config=e2e/playwright.config.ts directly bypasses the package script and therefore bypasses the prebuild. Use pnpm playwright:test or make test-e2e for the supported local flow. Because the site package scripts now call make, add make to knip's ignored system binaries. Tests: - pnpm lint:types - pnpm exec playwright test --config=e2e/playwright.config.ts --list - PATH="/opt/homebrew/opt/make/libexec/gnubin:/opt/homebrew/opt/gnu-getopt/bin:$PATH" make gen/mark-fresh - PATH="/opt/homebrew/opt/make/libexec/gnubin:/opt/homebrew/opt/gnu-getopt/bin:$PATH" pnpm playwright:test --list <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. --> |
||
|
|
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 |
||
|
|
39da38b189 |
fix(site/e2e): accept 404 from external auth reset hook (#26793)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` ← this PR 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` `deleteExternalAuthByID` used to be inverted: `sql.ErrNoRows` (link doesn't exist for this user/provider) fell through to the `500` path, while non-`ErrNoRows` DB errors went to `httpapi.ResourceNotFound`. #19775 (Sep 2025) refactored it to return `404` for not-found and `500` for real DB errors, which is the contract you'd expect. The relevant lines from #19775 in `coderd/externalauth.go`: ```diff - err := api.Database.DeleteExternalAuthLink(ctx, ...) + link, err := api.Database.GetExternalAuthLink(ctx, ...) if err != nil { - if !errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(w) return } httpapi.Write(ctx, w, http.StatusInternalServerError, ...) return } ``` `resetExternalAuthKey` in `site/e2e/hooks.ts` still treats `500` as the not-found code, so the first `beforeEach` in the externalAuth suite throws. The suite was skipped at the time #19775 landed (#17235), so nobody noticed the contract drift until #26648 tried to re-enable it. This just flips the accepted status codes to `200 || 404` and rewrites the stale comment. The 401/403/500 paths still surface as failures, which is what we want. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/coder/pull/19775 <details> <summary>Why a separate PR</summary> Keeps the bisection signal clean: #26575 proves the EADDRINUSE flake is fixed, this PR fixes the hook contract drift surfaced by re-enabling the suite, and #26648 just flips `.skip`. Squashing into #26648 would conflate two unrelated fixes. The CI run on #26648 already confirms the flake fix is doing its job: `successful external auth from workspace` passes (5.6s) and the `beforeAll`/`afterAll` mock servers come up and tear down cleanly with no EADDRINUSE. The only failures are this 404 hook drift. </details> |
||
|
|
b341ce63fb |
fix(site/e2e): close mock external-auth servers in teardown (#26575)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` ← this PR 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` The externalAuth e2e suite has been skipped since #17235 because `createServer` in `site/e2e/helpers.ts` started an express server but never gave callers a way to close it. On retries or repeated runs, the listener from the previous invocation was still bound to the hardcoded port and the next `beforeAll` failed with `EADDRINUSE`, eventually timing out in `waitForPort`. `createServer` now returns a `{ app, close }` pair. The web flow closes in `afterAll`; the device flow uses `try/finally`. `closeAllConnections()` is called before `close()` so teardown stays bounded if keep-alive connections linger. The suite remains `test.describe.skip` here; #26648 flips the skip off once the rest of the stack is in. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/internal/issues/356 <details> <summary>Decision log</summary> Discussed the full options list with @jakehwll before drafting. Picked option A (minimal teardown) because: - Two prior PRs (#15537, #16528) attacked symptoms (port probing, longer timeout) without addressing the leaked listener. - Kayla's diagnosis on coder/internal#356 pointed at exactly this case: nothing else in CI is grabbing the port, the listener from the previous run is still bound. - A is mechanical and orthogonal: it adds a real teardown without changing port allocation, fixture wiring, or what gets mocked. If the flake persists after A, we know to escalate to a worker-scoped fixture or dynamically allocated ports. Returning `close` rather than the raw `http.Server` encapsulates the `closeAllConnections` + `close` choreography so callers don't repeat it. `closeAllConnections` is optional-chained because it landed in Node 18.2; coder/coder runs newer, but the chain costs nothing. The device test uses `try/finally` rather than a shared `afterEach` to keep per-test state local. The web flow's `afterAll` mirrors its `beforeAll`. </details> |
||
|
|
7887cff9d0 |
feat: add user secrets management page (#25371)
Adds the account settings UI for managing user secrets, including the table, add/edit/delete dialog, Storybook coverage, and route/sidebar entry. Also updates the shared `FeatureStageBadge` beta variant with dedicated beta styling, sizing, and label casing for the Secrets page. Stacked on #25370. _This PR was generated by Coder Agents._ |
||
|
|
ddc0e99c69 |
chore: remove coder_secret Terraform integration (#25512)
Removes the coder_secret Terraform integration: the data.coder_secret consumption path through provisionerdserver → provisioner.proto → provisioner/terraform, the dynamic-parameter secret-requirement validation, and the workspace-update / resolve-autostart surfaces that depended on it. This is being done due to a product/feature direction change (see PLAT-243). User-secret CRUD (DB, REST, CLI, UI, telemetry, audit) and the agent-manifest secret-injection path are untouched. The provisionerd API is bumped from v1.17 to v1.18 rather than rolled back: v1.17 shipped in v2.33.x, so user_secrets field numbers are reserved and the changelog documents both versions. Generated with assistance from Coder Agents. |
||
|
|
2c18e07e39 |
feat: add theme mode dropdown (#25183)
## Summary - Wire the Appearance settings page to the new theme mode dropdown and sync or single theme selectors. - Update Appearance page tests and stories for theme mode behavior. - Update the user settings e2e test to exercise single theme selection. ## Dependencies - Depends on #25076, #25180, #25181, and #25182. - This PR targets helper branch `pr25077/05-theme-mode-dropdown-base`, which contains dependency commits only, so this PR diff stays focused on final dropdown wiring. Rebase and retarget after the dependency PRs merge. ## Validation - `pnpm -C site exec vitest run --project=unit src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx src/theme/themeMode.test.ts src/api/queries/users.test.ts` - `pnpm -C site lint:types` - `pnpm -C site storybook:ci` - `pnpm -C site build` - `pnpm -C site playwright:test -- e2e/tests/users/userSettings.spec.ts` - Pre-commit hook passed on the branch commit. |
||
|
|
2871a02352 |
fix: use actual ai_task instances for HasAITasks (#25197)
Previously, `hasAITaskResources()` scanned the Terraform graph for `coder_ai_task` node labels. The graph includes resource definitions regardless of `count`, so templates with `count = 0` were incorrectly marked as `HasAITasks = true`, causing them to appear on the `/tasks` page when no AI task resources would be created. Replace the graph-based check with `len(aiTasks) > 0`. The `aiTasks` slice is populated from state modules where Terraform has already evaluated `count`, so it correctly reflects actual resource instances. ref: https://linear.app/codercom/issue/ECO-39/make-coder-tasks-respect-count > Generated with [Coder Agents](https://coder.com/agents) --------- Signed-off-by: 35C4n0r <work.jaykumar@gmail.com> Signed-off-by: Jay Kumar <jay.kumar@coder.com> |
||
|
|
caabb3c4ab |
fix(site): show Organizations in admin dropdown for single-org OSS deployments (#25175)
Fixes https://linear.app/codercom/issue/CODAGT-350 On OSS or no-license single-org deployments, the Organizations admin link was hidden because `canViewOrganizationSettings` was gated on `showOrganizations`, which requires either a multi-org entitlement or >1 org. The page was still reachable via direct URL, but the members view displayed a raw "Template RBAC is a Premium feature. Contact sales!" error from the groups API. Two fixes: 1. Always render the Organizations link inside the `DeploymentDropdown`. The dropdown itself is only shown to users with admin-level permissions, so Organizations is effectively gated on having admin access. 2. Remove `groupsByUserIdQuery.error` from the error chain on the members page. The groups endpoint is gated behind `templateRBACEnabledMW` on enterprise, returning a 403 on OSS. The groups data is already optional, so the page renders fine without it. > Generated by Coder Agents |
||
|
|
85792d08bc |
feat: add harness engineering layer for agent workflows (#24791)
This PR adds an opinionated harness-engineering layer for agent-driven workflows: a small set of agent-readable docs, mechanical structure checks, structured CI failure summaries, an architecture-lint umbrella, and per-worktree dev-server isolation. The goal is to make local dev, tests, and CI mechanically inspectable by agents without changing app runtime behavior. ## What landed **Agent docs and navigation** - `.claude/docs/OBSERVABILITY.md`, `.claude/docs/DEV_ISOLATION.md`, `.claude/docs/AGENT_FAILURES.md`: task-oriented guides for logs, tracing, Prometheus, dev-server isolation, and a seeded failure catalog. - `AGENTS.md`: added an `Agent navigation` block, then trimmed the file from 375 to 229 lines by migrating duplicated detail into `WORKFLOWS.md`, `GO.md`, `TESTING.md`, and `DATABASE.md`. The user-managed custom-instructions block is preserved. - `.agents/docs`: symlink mirror of `.claude/docs` for agent runtimes that look under `.agents`. **Mechanical checks** - `scripts/check_agents_structure.sh`: validates `@...` references in tracked `AGENTS.md` files and warns when root grows past 600 lines. Wired as `make lint/agents` and into `make lint`. - `scripts/audit-agent-readiness.sh`: report-first audit of harness readiness. Currently `10 ok, 0 warn, 0 fail`. - `scripts/check_architecture.sh` / `make lint/architecture`: umbrella architecture-lint target. Consolidates the existing `check_enterprise_imports.sh` and `check_codersdk_imports.sh` so they run exactly once via the umbrella. Slot is open for new high-confidence rules. **Structured CI failure summaries** - `scripts/playwright-failure-summary.sh`: parses `site/test-results/results.json` and writes Markdown to `$GITHUB_STEP_SUMMARY` on failure. Wired into the `test-e2e` matrix job. - `scripts/go-test-failure-summary.sh`: parses `go test -json` line-delimited output the same way. Wired into `test-go-pg`, `test-go-pg-17`, and `test-go-race-pg` by injecting `gotestsum --jsonfile` in the workflow without touching `Makefile`. JSON also uploaded as a CI artifact on failure. - `site/e2e/playwright.config.ts`: enables `screenshot: only-on-failure`, `trace: retain-on-failure`, JSON reporter, and HTML reporter alongside existing reporters. - `.github/workflows/ci.yaml`: failure artifact uploads for Playwright now use `if: failure()` and predictable names (`playwright-artifacts-<variant>-<sha>`). **Per-worktree dev-server isolation** (`scripts/develop/main.go`) - Deterministic FNV-64a hash of the worktree path produces a port offset in `[0, 1000)` (50 buckets, step 20 to avoid API/proxy overlap across adjacent buckets). - Offset is applied only to defaults; both env vars (`CODER_DEV_PORT`, `CODER_DEV_WEB_PORT`, `CODER_DEV_PROXY_PORT`, `CODER_DEV_PROMETHEUS_PORT`) and CLI flags retain priority. - Hardcoded ports `9090` (embedded Prometheus UI) and `12345` (Delve) are unchanged by design. - Startup banner shows each port's source: `default`, `offset`, or `explicit`. - Unit tests in `scripts/develop/main_test.go` cover determinism, bounds, no-overlap across the four ports, and explicit-skip behavior. - State (`.coderv2/`) was already worktree-isolated via `os.Getwd()`, so no state-dir changes were needed. ## Validation `make lint/agents`, `make lint/architecture`, `make lint/emdash`, `bash scripts/audit-agent-readiness.sh` (10 ok, 0 warn, 0 fail), `shellcheck` on all 5 new scripts, `go test ./scripts/develop/...`, and `js-yaml` parse of `ci.yaml` all pass. Synthetic fixtures verify both failure-summary scripts handle empty/missing input (silent exit 0), ANSI-stripped output, and parent/subtest formatting. ## Known follow-ups (deferred) - Frontend Storybook/Vitest failure summary: lowest-leverage slice of the failure-summary work. Skipping until observed pain. - Architecture lint currently only delegates to existing import checks; new rules (`InTx` outer-store detection, swagger-annotation lint) plug in as needed. - 50 port-offset buckets means two worktree paths can occasionally collide. The DEV_ISOLATION doc tells users to set the relevant env var when this happens. > Mux opened this PR on Mike's behalf. |
||
|
|
162acaf8bf |
feat: update UsersPage role editing to match new designs (#24857)
|
||
|
|
6149fc3619 |
feat(site): add colorblind-friendly themes for protan/deuter and tritan (#24672)
This is part 1 to lay the foundation for the theme changes. Part 2 which adds the UI implementation is in this PR, https://github.com/coder/coder/pull/24680 ----------------- Adds four sitewide colorblind-friendly theme palettes alongside the existing light and dark themes. The palettes retune the red/green and blue/yellow semantic axes so success/error, warning, and diff additions/deletions remain distinguishable under the most common color vision deficiencies. | Preference ID | Purpose | |---|---| | `dark-protan-deuter` / `light-protan-deuter` | Protanopia & deuteranopia (red/green). Success and additions shift to sky-blue; destructive and deletions shift to vermilion/orange; warning shifts to fuchsia so warning and destructive states do not collapse onto the same hue. | | `dark-tritan` / `light-tritan` | Tritanopia (blue/yellow). Warning shifts from amber to fuchsia; red/green semantic pair is preserved. | The diff panel and every semantic role (`success`, `error`, `warning`, `notice`, `danger`) pick up the new palette automatically because they consume the sitewide CSS variables in `site/src/index.css`. No backend or database change is required: `theme_preference` is already a free-form `text` column. The existing `"dark"`, `"light"`, and `"auto"` preferences are unchanged. This PR ships the palettes and the resolution machinery (`CONCRETE_THEMES`, `resolveThemeName`, `isConcreteThemeName`, and the ThemeProvider/AgentEmbedPage plumbing). It intentionally does **not** add UI to select the new themes; the follow-up PR #24680 adds a Theme mode dropdown (Sync with system / Single theme) that exposes every concrete theme, including the four added here. Produced with Coder Agents assistance. <details> <summary>Implementation plan and decision log</summary> Full plan (investigation, file layout, TDD phases, open risks) is attached to the chat that produced this PR. Key decisions: - **Sitewide, not agent-scoped.** The diff panel already consumes the sitewide theme via CSS variables. Keeping the change at the user appearance layer also fixes red/green accents in alerts, badges, and build states in one change, and matches how comparable products (e.g. GitHub) ship this feature. - **No backend change.** `codersdk/users.go` already accepts any `theme_preference` string, and `ThemeProvider` now has a shared resolver (`resolveThemeName`) that maps any persisted value to a concrete theme, tolerating unknowns and the legacy `"auto"` value. - **Palette provenance.** The protan/deuter palette is inspired by CVD-safe blue/orange palettes and tuned within the existing Tailwind color scales; the tritan palette keeps red/green semantics intact and shifts warning to fuchsia. This PR does not claim exact Okabe-Ito or WCAG AA derivation without recorded contrast data. - **UI deferred.** Selecting the new themes is gated on the follow-up PR #24680 which replaces the flat theme grid with a Theme mode dropdown. </details> |
||
|
|
79735f2d45 |
feat: plumb user secrets through provisioner chain to terraform (#24542)
This change passes user secrets from coderd to the Terraform process at workspace build time so the `data.coder_secret` data source in terraform-provider-coder can resolve values at plan time. Secrets traverse two proto hops: `provisionerdserver` fetches them via`ListUserSecretsWithValues`, attaches them to `AcquiredJob.WorkspaceBuild.user_secrets` on `provisionerd.proto`; `runner.go` forwards into `PlanRequest.user_secrets` on `provisioner.proto`; the Terraform provisioner encodes each as `CODER_SECRET_ENV_<name>` or `CODER_SECRET_FILE_<hex(path)>` before invoking `terraform plan`. Only plan requests carry secrets; apply runs with `nil` because values are baked into plan state. Fetch is gated on a workspace transitioning to start. stop and delete transitions never carry secrets, so revoking or deleting a stored secret cannot make a workspace unstoppable. DB errors on the fetch fail the job outright rather than silently continuing with an empty secret set. Note that user secrets will be stored in the workspace_builds table in provisioner_state with other Terraform state (including other sensitive data). |
||
|
|
66abd8a271 |
fix(site): require confirmation before executing terminal command from URL (#24650)
The terminal page auto-executed commands from the `?command=` query
parameter
on page load without user confirmation. Because session auth uses
`SameSite=Lax`
cookies, an attacker could craft a link (phishing email, Slack DM,
external page)
that executes arbitrary commands in a victim's workspace when clicked.
Adds a `ConfirmDialog` that shows the exact command and requires
explicit user
approval before it is passed to the terminal WebSocket. Canceling
removes the
`command` parameter from the URL and opens a plain terminal.
<details>
<summary>Implementation details</summary>
### Data flow (before)
`TerminalPage.tsx` reads `searchParams.get("command")` and passes it
directly
as `initialCommand` to `WorkspaceTerminal`, which embeds it in the
WebSocket
URL. `proxy.go` forwards it to the agent, which runs `bash -c
"<command>"`
immediately.
### Fix
- Added `commandConfirmed` state and `commandPendingConfirmation` flag
in
`TerminalPage.tsx`.
- The `loading` prop passed to `WorkspaceTerminal` includes
`commandPendingConfirmation`, keeping the terminal in loading state
until
the user confirms or cancels.
- The command is only passed as `initialCommand` after the user clicks
"Run command" in the confirmation dialog.
- Trusted `?app=` commands (resolved from agent apps) bypass the dialog.
- Cancel removes the `?command=` parameter from the URL entirely.
- No backend changes needed; the frontend gates the command before it
reaches the WebSocket.
### Terminal focus after dialog
`WorkspaceTerminal`'s autoFocus effect previously depended on
`[terminal, isVisible, autoFocus]` but not `loading`. It fired while the
Radix dialog's focus trap was active, so `terminal.focus()` was
intercepted. When `loading` became false after confirming the dialog,
the
effect did not re-fire. Fixed by adding `loading` to the effect deps and
skipping focus while `loading` is true.
### Files changed
| File | Change |
|------|--------|
| `site/src/pages/TerminalPage/TerminalPage.tsx` | Confirmation dialog,
`commandPendingConfirmation` in loading prop |
| `site/src/pages/TerminalPage/TerminalCommandConsentDialog.tsx` | New
dialog component |
| `site/src/pages/TerminalPage/TerminalCommandConsentDialog.stories.tsx`
| Storybook story for dialog |
| `site/src/pages/TerminalPage/TerminalPage.stories.tsx` |
`CommandConfirmation` story |
| `site/src/pages/TerminalPage/TerminalPage.test.tsx` | 4 new dialog
tests, `renderTerminalRaw` helper for non-blocking render |
| `site/src/modules/terminal/WorkspaceTerminal.tsx` | Add `loading` to
autoFocus effect deps |
| `site/e2e/helpers.ts` | Dismiss dialog in `openTerminalWindow` helper
|
| `site/e2e/tests/webTerminal.spec.ts` | Wait for
`data-status="connected"` + click terminal for focus |
</details>
> 🤖 Generated by Coder Agents
---------
Co-authored-by: Jakub Domeracki <jakub@coder.com>
|
||
|
|
2b8a2c9c5d |
fix(site/e2e): wait for file upload before submitting create template form (#24548)
> 🤖 This PR was written by Coder Agent on behalf of Jake Howell Fixes https://github.com/coder/internal/issues/1419 The `createTemplate` e2e helper clicks Save immediately after `setInputFiles`, but the file upload API call is fire-and-forget through the React component chain (`FileUpload.onChange` → `CreateTemplateForm.onUpload` → `UploadTemplateView.onUpload` — none of which `await` the returned promise up the chain). If the upload mutation has not settled when the form submits, `uploadedFile` (`uploadFileMutation.data`) is still `undefined`, and the `uploadedFile!.hash` non-null assertion throws a `TypeError`. Formik silently swallows the rejected promise, so the page stays on `/templates/new` and the URL assertion times out. The fix waits for the "Remove file" button to appear after `setInputFiles`. That button only renders when `!isPending && variables` — i.e. the upload mutation has succeeded and the hash is available. |
||
|
|
4b755e514a |
fix(site): stabilize app.spec.ts e2e test (#24400)
The `app` e2e test flakes in CI with `waitForEvent("page")` or
`isVisible()` timeouts when opening a workspace app in a new tab.
The test had several timing hazards: the HTTP server address was read
before the socket finished binding, the popup event listener was
registered after the click, and content visibility used a point-in-time
check with the default 5s timeout.
Rewritten to wait for the server to listen, register the popup promise
before clicking, use `getByRole("link")` for a precise locator, and
replace `isVisible()` with a retrying `toBeVisible({ timeout: 30_000 })`
assertion that tolerates the intermediate `about:blank` page and
app-proxy startup delay. The test body is wrapped in `try/finally` to
ensure the HTTP server is always closed.
Fixes coder/internal#577
> Generated by Coder Agents
---
someone wise once said
> and I really don't know what we could do to make it more reliable
other than maybe trying to just rewrite it from scratch.
|
||
|
|
aede045549 | chore: bump @biomejs/biome from 2.2 to 2.4.10 (#24074) | ||
|
|
3a4a0b7270 |
fix: rename "Add member" to "Add" on template permissions page (#23943)
The "Add member" button on the template permissions page is used to add both **users and groups**, so the label is misleading when adding a group. <img width="1238" height="672" alt="image" src="https://github.com/user-attachments/assets/dbdfc79e-9e2e-4f26-9258-418f2038511e" /> |
||
|
|
308053b0e4 |
fix: stop workspace before starting with new parameters (#23541)
This is required to prevent the agent from becoming unhealthy. Since we are stopping the workspace now, also add a confirmation dialog. Also add stories to test the new behavior and make a tweak to the permissions query in support of that. |
||
|
|
4fab372bdc | chore: modernize utils/ imports (#23698) | ||
|
|
5112ab7da9 |
fix(site/e2e): fix flaky updateTemplate test expecting transient URL (#23655)
_PR generated by Mux but reviewed by a human_
## Problem
The e2e test `template update with new name redirects on successful
submit` is flaky.
After saving template settings, the app navigates to
`/templates/<name>`, which immediately redirects to
`/templates/<name>/docs` via the router's index route (`<Navigate
to="docs" replace />`). The assertion used `expect.poll()` with
`toHavePathNameEndingWith(`/${name}`)`, which matches only the
**transient intermediate URL** — it only exists while `TemplateLayout`'s
async data fetch is pending. Once the fetch resolves and the `<Outlet
/>` renders, the index route fires the `/docs` redirect and the URL no
longer matches.
## Why it's flaky (not deterministic)
The flakiness depends on whether the template query cache is warm:
- **Cache miss → PASSES**: The mutation's `onSuccess` handler
invalidates the query cache. If `TemplateLayout` needs to re-fetch, it
shows a `<Loader />`, which delays rendering the `<Outlet />` that
contains the `<Navigate to="docs">`. This gives `expect.poll()` time to
see the transient `/new-name` URL → **pass**.
- **Cache hit → FAILS**: If the template data is still in the query
client, `TemplateLayout` renders immediately and the `<Navigate
to="docs" replace />` fires nearly instantly. By the time the first poll
runs, the URL is already `/new-name/docs` → **fail**.
## Fix
Assert the **final stable URL** (`/${name}/docs`) instead of the
transient one.
This is safe because `expect.poll()` is retry-based: it keeps sampling
until a match is found (or timeout). Seeing the transient `/new-name`
URL just causes harmless retries — once the redirect completes and the
URL settles on `/new-name/docs`, the poll matches and the test passes.
| Poll | URL | Ends with `/new-name/docs`? | Action |
|---|---|---|---|
| 1st | `/templates/new-name` | No | Retry |
| 2nd | `/templates/new-name` | No | Retry |
| 3rd | `/templates/new-name/docs` | Yes | **Pass** ✅ |
Closes https://github.com/coder/internal/issues/1403
|
||
|
|
0e69e0eaca | chore: modernize typescript api client/types imports (#23637) | ||
|
|
13703fb5aa |
fix: use auto-retrying assertion for bool parameter verification (#23315)
## Problem Flaky e2e test `create workspace and overwrite default parameters` — the boolean parameter verification reads `"true"` when it should be `"false"`. `verifyParameters` in `site/e2e/helpers.ts` used a one-shot `isChecked()` for boolean parameters (line 214), while the `string`/`number` path used Playwright's auto-retrying `toHaveValue()` with a 15-second timeout. When the settings/parameters page hydrates with React Query data, the Switch can briefly render the default value (`true`) before settling on the override (`false`). The one-shot check captures the stale state. ## Fix Replace the one-shot `isChecked()` + `expect().toEqual()` with Playwright's auto-retrying `toBeChecked()` / `not.toBeChecked()` assertions using a 15-second timeout, matching the pattern already used for string/number parameters. Fixes coder/internal#1414 Authored by coder agent 🤖 --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
1e07ec49a6 |
feat: add merge_strategy support for coder_env resources (#23107)
## Description Implements the server-side merge logic for the `merge_strategy` attribute added to `coder_env` in [terraform-provider-coder v2.15.0](https://github.com/coder/terraform-provider-coder/pull/489). This allows template authors to control how duplicate environment variable names are combined across multiple `coder_env` resources. Relates to https://github.com/coder/coder/issues/21885 ## Supported strategies | Strategy | Behavior | |----------|----------| | `replace` (default) | Last value wins — backward compatible | | `append` | Joins values with `:` separator (e.g. PATH additions) | | `prepend` | Prepends value with `:` separator | | `error` | Fails the build if the variable is already defined | ## Example ```hcl resource "coder_env" "path_tools" { agent_id = coder_agent.dev.id name = "PATH" value = "/home/coder/tools/bin" merge_strategy = "append" } ``` ## Changes - **Proto**: Added `merge_strategy` field to `Env` message in `provisioner.proto` - **State reader**: Updated `agentEnvAttributes` struct and proto construction in `resources.go` - **Merge logic**: Added `mergeExtraEnvs()` function in `provisionerdserver.go` with strategy-aware merging for both agent envs and devcontainer subagent envs - **Tests**: 15 unit tests covering all strategies, edge cases (empty values, mixed strategies, multiple appends) - **Dependency**: Bumped `terraform-provider-coder` v2.14.0 → v2.15.0 - **Fixtures**: Updated `duplicate-env-keys` test fixtures and golden files ## Ordering When multiple resources `append` or `prepend` to the same key, they are processed in alphabetical order by Terraform resource address (per the determinism fix in #22706). |
||
|
|
49e5547c22 | feat: add support for creating service accounts (#23140) | ||
|
|
f46692531f |
fix(site/e2e): increase webServer timeout to 120s (#22731)
The Playwright e2e `webServer` starts the Coder server via `go run -tags embed`, which must compile before serving. The default 60s timeout leaves no margin when the CI runner is slow. Failed run: https://github.com/coder/coder/actions/runs/22782592241/job/66091950715 Successful run: https://github.com/coder/coder/actions/runs/22782107623/job/66090828826 The server started and printed its banner, but with only ~4s left on the clock the health check (`/api/v2/deployment/config`) could not complete before the timeout fired. The same ~2x slowdown shows in the `make site/e2e/bin/coder` step (45s vs 67s), confirming this is runner performance variability. Increase timeout to 120s. Refs #22727 |
||
|
|
dba688662c |
feat: add <ErrorAlert /> debug details (#22462)
Closes #22140 Short simple and sweet PR to add a bunch of details to our `<Alert />` stacks. This means we aren't simply asking the user to read the developer console and surface things easier. - Implement `Response data` and `Stack trace` `<details />` - Fix overflow in `ErrorAlert` debug accordions so long `Response data` and `Stack Trace` content stays inside the alert. - Add horizontal scroll wrappers around both `<pre>` blocks used in debug details. - Update `Alert` layout with `min-w-0` on flex containers so nested content can shrink correctly and internal scrolling works as intended. <img width="739" height="550" alt="preview-validation" src="https://github.com/user-attachments/assets/a6f890d3-8f1f-4fd6-b9d0-882838db04a4" /> |
||
|
|
f07fdce20a |
flake: add page event logging to e2e tests (#22569)
I'm having a hard time reproducing [this Heisenbug](https://github.com/coder/internal/issues/1154) in PR CI, but it seems to happen pretty often on main, so I would like to add some logging for a few more page events to the ones we already have. |
||
|
|
b73f21662b |
flake: verify parameters in parallel in e2e tests (#22557)
This is an attempt to address coder/internal#1154 Tests appear to fail often on `verifyParameters`, which asserts input visibility and value in series for all expected parameters. This change makes the same assertions in parallel, hopefully completing before timeout. |
||
|
|
3daac86efe |
refactor(site): add tabbed layout for single group page (#22486)
## Summary Replaces the standalone **Settings** button on the single-group page with a tabbed layout containing **Group members** and **Group settings** tabs. This uses the new figma designs here: https://www.figma.com/design/klGTlHSPQwI4KBvAMdebrx/Customer-Usage-Controls-for-AI-Governance-Add-On?node-id=51-4907&m=dev <img width="797" height="371" alt="Screenshot 2026-03-02 at 22 53 28" src="https://github.com/user-attachments/assets/88d2ca8e-928f-404d-8569-ec4aba6c2ce4" /> ### What changed | File | Change | |------|--------| | `site/src/router.tsx` | Nested `settings` route under `:groupName` layout route; added `GroupMembersPage` lazy import | | `site/src/pages/GroupsPage/GroupPage.tsx` | Converted to shared layout: header + tabs (`Tabs`/`TabsList`/`TabLink`) + `<Outlet />` with context. Removed settings button and member-management code | | `site/src/pages/GroupsPage/GroupMembersPage.tsx` | **New file** — extracted member-management UI (add/remove members, member table) from GroupPage; consumes data via `useOutletContext` | | `site/src/pages/GroupsPage/GroupSettingsPage.tsx` | Switched from independent group query to outlet context; removed duplicate loading/error/title handling | | `site/src/pages/GroupsPage/GroupSettingsPageView.tsx` | Removed duplicate `ResourcePageHeader`; renders only the settings form | | `site/e2e/tests/organizationGroups.spec.ts` | Updated selectors from `"Settings"` link to `"Group settings"` link | ### How it works - `:groupName` route now renders `GroupPage` as a **layout route** with header, tabs, and `<Outlet />`. - Index child route renders `GroupMembersPage` (member table + add/remove). - `settings` child route renders `GroupSettingsPage` (group settings form). - Shared group data + permissions are passed via React Router outlet context, eliminating duplicate queries. - URL structure is unchanged: `/organizations/:org/groups/:groupName` (members) and `.../settings` (settings). ### Verification - `pnpm exec tsc --noEmit` — passes - `pnpm exec biome check --error-on-warnings` on all touched files — passes |
||
|
|
8bb80b060e |
fix(e2e): fix flaky verifyParameters assertion in updateWorkspace test (#22413)
## Problem The `update workspace, new required, mutable parameter added` e2e test has been flaking consistently ([internal#1328](https://github.com/coder/internal/issues/1328)). The error: ``` Error: Timed out 5000ms waiting for expect(locator).toHaveValue(expected) Locator: getByTestId('parameter-field-Sixth parameter').locator('input') Expected string: "99" Received string: "" ``` ## Root Cause A race between page navigation and data hydration in `verifyParameters`: 1. The page navigates with `waitUntil: "domcontentloaded"` which does not wait for API responses to settle 2. React Query may serve stale cached workspace data initially (from before the update), causing the form to render with empty/old parameter values 3. The `toHaveValue` assertion uses the default `actionTimeout` of 5000ms which isn't enough time for fresh data to arrive and the form to re-render ## Fix - Switch `verifyParameters` navigation to `waitUntil: "networkidle"` to ensure API responses (workspace data, build parameters) are settled before the form renders - Increase the `toHaveValue` timeout to 15s to handle cases where dynamic parameters hydrate slowly after initial render Fixes coder/internal#1328 --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> |
||
|
|
edee917d88 |
feat: add experimental agents support (#22290)
feat: add AI chat system with agent tools and chat UI Introduce the chatd subsystem and Agents UI for AI-powered chat within Coder workspaces. - Add chatd package with chat loop, message compaction, prompt management, and LLM provider integration (OpenAI, Anthropic) - Add agent tools: create workspace, list/read templates, read/write/ edit files, execute commands - Add chat API endpoints with streaming, message editing, and durable reconnection - Add database schema and migrations for chats, chat messages, chat providers, and chat model configs - Add RBAC policies and dbauthz enforcement for chat resources - Add Agents UI pages with conversation timeline, queued messages list, diff viewer, and model configuration panel - Add comprehensive test coverage including coderd integration tests, chatd unit tests, and Storybook stories - Gate feature behind experiments flag --------- Co-authored-by: Cian Johnston <cian@coder.com> Co-authored-by: Danielle Maywood <danielle@themaywoods.com> Co-authored-by: Jeremy Ruppel <jeremy@coder.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
15a2bab1cd |
feat: migrate from <GlobalSnackbar /> to sonner (#22258)
Replaces our custom `<GlobalSnackbar />` (MUI Snackbar + event emitter) with [`sonner`](https://github.com/emilkowalski/sonner). Deletes `GlobalSnackbar/`, the custom event emitter infra, and migrates ~80 source files to `toast.success()` / `toast.error()` from `sonner`. - ~47 error toasts now surface API error detail via `getErrorDetail(error)` in the toast description, not just a generic message. Coincides with #22229. - Toast messages follow an `{Action} "{entity}" {result}.` format (e.g. `User "alice" suspended successfully.`) since toasts persist across navigation now. - 17 uses of `toast.promise()` for loading → success → error lifecycle. - Some toasts include action buttons for quick navigation (e.g. "View task", "View template"). - Multiple toasts can stack and display simultaneously. --------- Co-authored-by: Kayla はな <mckayla@hey.com> |
||
|
|
60e3ab7632 |
feat(site)!: add consent prompt for auto-creation with prefilled parameters (#22011)
### Summary Workspace created via mode=auto links now require explicit user confirmation before provisioning. A warning dialog shows all prefilled param.* values from the URL and blocks creation until the user clicks `Confirm and Create`. Clicking `Cancel` falls back to the standard form view. <img width="820" height="475" alt="auto-create-consent-dialog" src="https://github.com/user-attachments/assets/8339e3bd-434f-4a04-9385-436bf95f49d7" /> ### Breaking behavior change Links using `mode=auto` (e.g., "Open in Coder" buttons) will no longer silently create workspaces. Users will now see a consent dialog and must explicitly confirm before the workspace is provisioned. Any existing integrations or automation relying on `mode=auto` for seamless workspace creation will now require manual user interaction. --------- Co-authored-by: Jake Howell <jacob@coder.com> |
||
|
|
52af6eac68 | fix: extend app test timeout (#21934) | ||
|
|
1a94aa67a3 |
feat(provisioner): associate resources with coder_devcontainer (#21602)
Closes https://github.com/coder/internal/issues/1239 Allow associating `coder_env`, `coder_script` and `coder_app` with `coder_devcontainer` resource. To do this we make use of the newly added `subagent_id` field in the `coder_devcontainer` resource added in https://github.com/coder/terraform-provider-coder/pull/474 |
||
|
|
d2044c2ee9 |
chore: update protobuf to reuse file request (#21447)
**This is just the protobuf changes for the PR https://github.com/coder/coder/pull/21398** Moved `UploadFileRequest` from `provisionerd.proto` -> `provisioner.proto`. Renamed to `FileUpload` because it is now bi-directional. This **is backwards compatible**. I tested it to confirm the payloads are identical. Types were just renamed and moved around. ```golang func TestTypeUpgrade(t *testing.T) { t.Parallel() x := &proto2.UploadFileRequest{ Type: &proto2.UploadFileRequest_ChunkPiece{ ChunkPiece: &proto.ChunkPiece{ Data: []byte("Hello World!"), FullDataHash: []byte("Foobar"), PieceIndex: 42, }, }, } data, err := protobuf.Marshal(x) require.NoError(t, err) // Exactly the same output // EhgKDEhlbGxvIFdvcmxkIRIGRm9vYmFyGCo= on `main` // EhgKDEhlbGxvIFdvcmxkIRIGRm9vYmFyGCo= on this branch fmt.Println(base64.StdEncoding.EncodeToString(data)) } ``` # What this does This allows provisioner daemons to download files from `coderd`'s `files` table. This is used to send over cached module files and prevent the need of downloading these modules on each workspace build. |
||
|
|
89f4d60e7b |
chore: remove experiment "terraform-directory-reuse" (#21397)
Experiment is no longer required, the new method will be released without an experiment and without a toggle Main PR is: https://github.com/coder/coder/pull/21398 |
||
|
|
61d7d2983f |
fix: remove state information from apply (#21373)
Delete builds were not deleting resources as the tf state being sent in the apply request was empty. State removed from apply request and read from the session instead. |
||
|
|
3194bcfc9e |
chore: distinct operations for provisioner's 'parse', 'init', 'plan', 'apply', 'graph' (#21064)
Provisioner steps broken into smaller granular actions. Changes: - `ExtractArchive` moved to `init` request (was in `configure`) - Writing `tfstate` moved to `plan` (was in `configure`) - Moved most plan/apply outputs to `GraphComplete` |
||
|
|
782a1052c8 |
test: use toHaveValue() to avoid flaky parameter verification (#20990)
## Problem The test `create workspace with default and required parameters` was flaky because `verifyParameters` in `site/e2e/helpers.ts` didn't wait for input values to be populated before asserting. After PR #20710 removed classic parameters, the form now uses dynamic parameters loaded asynchronously via WebSocket. The input field can be visible before its value is populated. Closes https://github.com/coder/internal/issues/1154 ## Fix Replace immediate read + assertion: ```typescript const value = await parameterField.inputValue(); expect(value).toEqual(buildParameter.value); ``` With Playwright's auto-retrying assertion: ```typescript await expect(parameterField).toHaveValue(buildParameter.value); ``` From [Playwright docs for `inputValue()`](https://playwright.dev/docs/api/class-locator#locator-input-value): > **NOTE** If you need to assert input value, prefer `expect(locator).toHaveValue(value[, options])` to avoid flakiness. See assertions guide for more details. --- This PR was fully generated by [mux](https://github.com/coder/mux), and reviewed by a human. |