> 🤖 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>
> 🤖 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>
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._
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.
## 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.
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>
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
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.
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>
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).
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>
> 🤖 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.
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.
Fixescoder/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.
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.
_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
## 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.
Fixescoder/internal#1414
Authored by coder agent 🤖
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
## 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).
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
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"
/>
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.
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.
## 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
Fixescoder/internal#1328
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
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>
### 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>
**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.
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.
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`
## 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.
Adds some extra meta data sent to provisioners. Also adds a field
`reuse_terraform_workspace` to tell the provisioner whether or not to
use the caching experiment.
## Overview
Links in announcement banners are now underlined to make them visually
distinguishable without requiring users to hover over them.
Context:
[Slack](https://codercom.slack.com/archives/C0989BZU23T/p1759503061267819)
## Changes
- Added `text-decoration: underline` to links in the announcement banner
component
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: blink-so[bot] <157993532+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Michael Smith <michaelsmith@coder.com>
I tried to break this work into smaller pieces, but since there are a
lot of dependent components, I decided to handle it in one larger chunk
and rely on Storybook to catch any bugs.
That said, let me know if you’d prefer a different approach!
Closes https://github.com/coder/internal/issues/978
- Introduce `CODER_TASK_ID` and `CODER_TASK_PROMPT` to the provisioner
environment
- Make use of new `app_id` field in provider, with a fallback to
`sidebar_app.id` for backwards compatibility
**For now** I've left the `taskPrompt` and `taskID` as a TODO as we do
not yet create these values.
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder.
Depends on: https://github.com/coder/terraform-provider-coder/pull/424
* GET /api/v2/init-script - Gets the agent initialization script
* By default, it returns a script for Linux (amd64), but with query parameters (os and arch) you can get the init script for different platforms
* GET /api/v2/workspaces/{workspace}/external-agent/{agent}/credentials - Gets credentials for an external agent **(enterprise)**
* Updated queries to filter workspaces/templates by the has_external_agent field