Commit Graph
15189 Commits
Author SHA1 Message Date
Danny Kopping 08a6359cac feat: record all tool call types (#26855)
## Summary

The Responses interceptor previously recorded only `function_call` and `custom_tool_call` output items, so interceptions that did real work via built-in tools (`web_search_call`, `computer_call`, `shell_call`, `mcp_call`, etc.) recorded no tool usage at all.

`recordNonInjectedToolUsage` now whitelists every tool-call output type and records it, with the tool name falling back to the item type when none is set.

`ToolUsageRecord` also gains an `ItemID` field so the two distinct Responses identifiers are captured without conflation (addresses review feedback on coder/aibridge#273):

- `ItemID`: the output item's unique `id` (always present).
- `ToolCallID`: the `call_id` correlation id (empty for hosted tools the provider runs server-side).

## Tests

- Extends `TestRecordToolUsage` with cases for the new hosted/agentic tool types.
- Adds blocking and streaming `web_search` fixtures (scrubbed of credentials and identifying metadata) plus `TestResponsesOutputMatchesUpstream` cases asserting a hosted tool records with an empty `ToolCallID` and a populated `ItemID`.

Linear: AIGOV-96

---
*This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-06 09:10:21 +02:00
Jaayden HalkoandTracy Johnson 14d17abae6 chore: remove frontend related regenerate chat title code (#26867)
Co-authored-by: Tracy Johnson <tracy@coder.com>
2026-07-06 06:24:08 +01:00
Jake Howell 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>
2026-07-06 03:56:50 +00:00
Itay Dafna d7ad85f7f6 feat: support multiple OIDC redirect URIs (#25408)
This PR adds a new opt-in setting, `CODER_OIDC_REDIRECT_ALLOWED_HOSTS`,
that lets a single Coder deployment complete OIDC login on more than one
hostname. When the allowlist is non-empty, Coder picks the OIDC
`redirect_uri` based on the incoming request's Host header (validated
against the list) instead of always using the static URL derived from
`CODER_ACCESS_URL`. When unset, the (default) behavior is identical to
today.

The motivation is that a single Coder deployment is frequently reachable
via multiple hostnames - for example, an internal hostname for users on
a corporate VPN and a different hostname routed through a zero-trust
gateway for users off-VPN - but OIDC login today only works on whichever
single hostname `CODER_ACCESS_URL` points to, because the `redirect_uri`
sent to the IdP is fixed at server startup. Users who reach the
deployment on any other valid hostname can see the login page but fail
the OIDC callback, since the IdP redirects them back to a hostname they
can't reach (or whose cookies they don't have).
2026-07-05 06:36:33 +02:00
Yevhenii Shcherbina 121107f151 docs: document the Bedrock external ID for role assumption (#26973)
Document the Bedrock external ID for role assumption.
2026-07-02 19:06:16 +00:00
Yevhenii Shcherbina ab69fa2f0d fix(aibridge/provider): disable keep-alive on the STS assume-role client (#26971)
A Bedrock provider that assumes an IAM role kept failing with
`AssumeRole` `AccessDenied` for several minutes after its target role's
trust policy was changed, and only recovered on a gateway restart or a
long wait. The request itself was correct: the AWS CLI, using the same
identity and the same `ExternalId`/role/region, accepted the identical
request immediately against the same endpoint.

The difference is the connection. The Go SDK reuses a keep-alive
connection for the STS client, so every `AssumeRole` rides one
connection pinned to a single STS endpoint. After a trust-policy change,
that connection kept returning `AccessDenied` for minutes while a fresh
connection (the AWS CLI) accepted the identical request at once; it
recovered only when the connection recycled or the process restarted.
The exact STS-internal reason is unconfirmed (likely per-endpoint
propagation of the change) — what is verified is that a fresh connection
per call recovers promptly.

Disable keep-alive on the STS client so each `AssumeRole` opens a fresh
connection and a trust-policy update takes effect quickly. `AssumeRole`
runs at most once per credential-cache lifetime, so keep-alive bought
nothing here. The change is scoped to the STS client only; Bedrock model
requests are signed by a separate client and keep their connection
pooling.

## What the data proves

| | CLI | Gateway |

|--------------------|-------------------------------------------|------------------------------------------|
| Identity / key | `bedrock-base-user-useless` / `AKIA…44NL` | same |
| STS endpoint | `sts.us-east-2.amazonaws.com` | same |
| Request params | `ExternalId=QL53…`, role, session, 900 | same |
| Recovery after fix | 7 seconds (21:27:54) | ~4.5 minutes (21:32:17) |
| Re-hitting AWS? | new call each time | yes — 77 fresh `AssumeRole`s,
all denied |

Same identity, params, and endpoint, concurrent — yet the gateway was
denied for ~4.5 minutes while the CLI recovered in 7 seconds, and the
gateway made a fresh `AssumeRole` on every request (so it was not
caching a failure). The only difference was connection reuse.

After disabling keep-alive, the same break/fix experiment brought
gateway recovery down from ~4.5 minutes to ~7 seconds, in lockstep with
the AWS CLI.
2026-07-02 18:58:17 +00:00
Susana Ferreira 1989db0e2b feat(coderd): enforce ai budget on pre-request path (#26915)
## Description

Adds pre-request AI budget enforcement to `aibridged`. Requests are rejected with HTTP 403 when the user's aggregated spend for the current period has reached their effective limit.

## Changes

- Add `IsBudgetExceeded` RPC to `aibridgedserver`. Resolves the user's effective budget, aggregates spend over the caller-supplied `[period_start, now]` window, and returns whether the limit has been reached along with the effective limit.
- Wire the check into `aibridged`'s HTTP handler. The caller computes the period start (monthly for now) and passes it in the request.
- Reject exceeded requests with HTTP 403 Forbidden and a message directing the user to contact an administrator.
- Add `dbtime.StartOfMonth` alongside `StartOfDay` for period computation.
- Add real-DB tests covering the enforcement path: month-boundary excludes prior-period spend, and a new user override unblocks a previously-exceeded user.

Closes https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:53:36 +01:00
Susana Ferreira be9c95c8f5 feat(coderd): accumulate user daily AI spend on token usage (#26741)
## Description

Adds post-response spend accumulation to `RecordTokenUsage`.

## Changes

- Wrap the token usage insert and daily spend increment in a single transaction.
- Skip the spend update when the user is unbudgeted, the model is unpriced, or the computed cost is non-positive.

Depends on #26562

Closes https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:42:37 +01:00
Jake Howell b1ef07c79d feat(site): add linear.svg icon (#26967)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Adds a bundled `linear.svg` icon at `site/static/icon/linear.svg` so
template authors can reference Linear as a first-party `/icon/`.

`site/src/theme/icons.json` is regenerated by `make
site/src/theme/icons.json` — the diff is a single-line insertion between
`lakefs.svg` and `lxc.svg`.

## Verification

- `go test ./scripts/gensite -run TestSVGIconAttributes/linear.svg` —
PASS (`width="256"`, `height="256"`, `viewBox="0 0 256 256"`)
- `make lint/site-icons` — PASS
- `go test ./scripts/gensite -count=1` — PASS (full SVG attribute sweep)
2026-07-02 15:41:27 +00:00
Susana Ferreira fcdd029d74 feat: add ai_user_daily_spend table and queries (#26562)
## Description

Adds the spend tracking table and queries needed by [AIGOV-427](https://linear.app/codercom/issue/AIGOV-427/add-post-response-spend-accumulation) (post-response accumulation) and [AIGOV-428](https://linear.app/codercom/issue/AIGOV-428/add-pre-request-budget-enforcement) (pre-request enforcement).

## Changes

- Add `ai_user_daily_spend` table to aggregate per-user, per-effective-group AI spend by UTC day.
- Add `UpsertUserAIDailySpend` and `GetUserAISpendSince` queries.

Closes https://linear.app/codercom/issue/AIGOV-426/add-daily-spend-table-and-queries

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-07-02 16:29:25 +01:00
Yevhenii ShcherbinaandJake Howell dee41c34e6 feat: show Bedrock external ID in the provider edit form (#26919)
Surfaces the server-generated STS external ID on the Bedrock provider
edit form. When a provider assumes a role, the form shows the external
ID read-only with a copy icon and a short note to add it to the target
role's trust policy as an sts:ExternalId condition.

The value is display-only: it is passed to the form as its own prop
rather than as an editable form value, so it is never submitted back.
This matches the backend contract, where the external ID is server-owned
and a changed value is rejected.

Builds on the backend in #26869. Follow-up to #26578.

---------

Co-authored-by: Jake Howell <jake@hwll.me>
2026-07-02 10:55:50 -04:00
Paweł Banaszewski 79a74fc817 Revert "chore: hide AI Gateway key management UI/CLI/API (#26879)" (#26913)
Reverting
https://github.com/coder/coder/commit/377c1309b7a42ead9cfbd864f0af8e9b6e472851
since release 2.35 was already cut:
https://github.com/coder/coder/tree/release/2.35
2026-07-02 13:45:05 +00:00
Danielle Maywood 332e32d42b fix(site/src): keep short mobile dropdowns above the software keyboard (#26965) 2026-07-02 13:37:13 +00:00
Danielle Maywood 8bcc24f032 fix(site/src/pages/AgentsPage/components/ChatElements): keep model picker visible above mobile keyboard (#26964) 2026-07-02 12:47:01 +01:00
Cian Johnston 843754a547 refactor(coderd/x/chatd): remove dead model-routing dispatch shim (#26942)
Follow-up to #26862 ("remove direct chat routing"), which collapsed the
routing discriminated union into a single `aiGatewayModelRoute` but left
a one-path dispatch shim behind in `model_routing.go`.

Removes
`resolveModelRouteForConfig`/`resolveModelRouteForProviderType`/`newModel`
wrapper functions that did nothing but call their `*AIGateway*`
counterparts, and renames the `*AIGateway*` targets to take over those
names directly. Also collapses a redundant if/else in
`title_override.go` where both branches called the same function with
the same effective argument, and has `chatutil.NormalizedStringPointer`
delegate to the existing `coderd/util/strings.EmptyToNil` instead of
reimplementing empty-string-to-nil logic.

No behavior change.

<details>
<summary>Investigation notes / decision log</summary>

Two independent read-only investigations were run over `coderd/x/chatd`
looking for cleanup opportunities following #26862: one focused on
residue from that PR specifically, one a general over-engineering pass
on the whole package. Both independently converged on the
`model_routing.go` shim as the top finding (verified zero divergent call
sites).

Other candidates considered and explicitly deferred/rejected for this
PR:

- Renaming away the vestigial `AIGateway` prefix package-wide:
cosmetic-only, touches many call sites, skipped.
- Inlining the `chatcost` subpackage into `chatd`: unrelated to #26862,
skipped.
- Deleting the deprecated `AIGatewayRoutingEnabled` deployment flag:
confirmed dead/no-op, but intentionally kept as a back-compat shim per
#26862; removal should follow the same deprecation cadence as other
deprecated deployment options, as a separate, differently-timed change.
- Folding `chatutil` entirely into `chatprovider`/`chatopenai`:
`NormalizedStringPointer` overlapped with
`coderd/util/strings.EmptyToNil` (now reused), but `NormalizedEnumValue`
has no equivalent elsewhere in the repo and still has 2 real call sites,
so the package stays.

</details>

---
Generated by Coder Agents on behalf of @johnstcn.
2026-07-02 11:22:56 +01:00
Atif AliandJake Howell e2e1d99485 chore(site): add Omnigent icon to static assets (#26828)
Adds the Omnigent SVG icon to the built-in site icon assets so modules
and templates can reference `/icon/omnigent.svg`.

## Validation

- `./scripts/check_site_icons.sh`
- Verified `site/src/theme/icons.json` is sorted and includes
`omnigent.svg`
- Parsed `site/static/icon/omnigent.svg` as valid XML

> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻

---------

Co-authored-by: Jake Howell <jake@hwll.me>
2026-07-02 12:47:55 +05:00
Jon Ayers cda7a9d4f4 fix: reword autostop reminder to use relative countdown (#26948) 2026-07-01 21:24:42 -05:00
Jon Ayers 40bceeaf8d fix: nats timing flakes (#26944) 2026-07-01 17:57:42 -05:00
Andrew AquinoandJeremy Ruppel 2ab3d1010f fix(site): enable sticky positioning inside #main-content (#26907)
ref DEVEX-567

Fixes a bug I noticed where #26881 claims to have made the template
builder's `SelectionSummary` sidebar sticky-positioned, but the sidebar
position wasn't actually sticky in practice:

## template builder's `SelectionSummary` (top right)

### before


https://github.com/user-attachments/assets/1e9360b5-4b0e-45b4-a859-f8edab51add7

### after


https://github.com/user-attachments/assets/3cee75e9-9813-401e-9922-d2563185f8f5

---

Why I removed `overflow-y-auto` from `#main-content`:

>`position: sticky` resolves against the nearest ancestor scroll
container — any ancestor whose overflow is not visible. `#main-content`
had `overflow-y-auto`, so it _was_ that container for the sidebar. But
because the layout wrapper is `min-h-screen`, `<main>` grows to fit its
content and never actually scrolls — the window scrolls. So the sidebar
was pinning relative to a container that never moves → no effect.

That was necessary for the main goal of this PR, which was to fix sticky
positioning for the template builder's `SelectionSummary` sidebar.
However, removing that style from `#main-content` affected 2 other
sticky-positioned elements within the site:

>Two other pages have sticky elements that, like our sidebar, were
resolving against the non-scrolling `<main>` and were therefore
effectively inert:
>- CreateWorkspacePageView.tsx:397 — `sticky top-5` side panel
>- modules/templates/TemplateFiles/TemplateFiles.tsx:64 — `sticky top-8`
file tree

^Like `SelectionSummary`, these 2 elements hadn't been behaving with
sticky positioning as expected; they would just scroll away past the top
of the screen.

For all 3 of these sticky elements, since their `top` is now relative to
the window instead of `#main-content`, they have to be positioned
farther downward so that they don't get covered by the navbar.

## `TemplateFiles`' `TemplateFileTree` (top left)

### before


https://github.com/user-attachments/assets/ee79ea36-1a47-4b1b-86ef-c65b493ea455

### after


https://github.com/user-attachments/assets/a1b0a949-b5e3-4434-acaa-ab24e82b1669

## `CreateWorkspacePageView`'s "Go back" button (top left)

### before


https://github.com/user-attachments/assets/00b076f6-8e55-4a96-8b92-d33d92c85334

### after


https://github.com/user-attachments/assets/10ae05e1-3112-4dfc-84b4-2c44fee23e06

co-authored with Claude Code

---------

Co-authored-by: Jeremy Ruppel <jeremyruppel@users.noreply.github.com>
2026-07-01 15:41:18 -07:00
Andrew Aquino 60254c85e9 feat(site): clarify module listing's empty state copy if selected base template has no modules (#26947)
ref DEVEX-578

One base template I'm aware of where you can test this state is AWS EC2
(Windows):

<img width="1840" height="1191" alt="image"
src="https://github.com/user-attachments/assets/ffd22fd2-a811-424d-a840-be6114b38560"
/>

The previous copy is still shown if you choose a different base and
search modules for a term with no matches:

<img width="1840" height="1191" alt="image"
src="https://github.com/user-attachments/assets/3db95c6a-42e6-48c1-8800-e482680cb109"
/>
2026-07-01 15:16:09 -07:00
Garrett Delfosse b1ead5f085 fix: set git identity for release tagging and surface git stderr (#26945)
## What happened

The [Tag and Release
run](https://github.com/coder/coder/actions/runs/28549109434/job/84641825784)
failed in the `prepare-release` job at the step "Prepare release
(calculate version, create tag and branch)" with:

```
error: create tag v2.35.0-rc.0: exit status 128
```

## Root cause

`prepare-release` creates an **annotated** tag via `git tag -a`
(`scripts/release-action/prepare.go`), which records a tagger and
therefore requires a git identity. The job never ran `git config
user.name/user.email`, and runners have none configured, so git aborts
with exit status 128. The real `fatal:` message was hidden because
`realExecutor.RunMutation` discarded the command's stderr.

## Changes

- **`.github/workflows/tag-and-release.yaml`**: add a "Configure git
identity" step (`ci@coder.com` / `Coder CI`) to the `prepare-release`
job, before the release tool runs. This matches the identity pattern
already used later in the same workflow.
- **`scripts/release-action/cmdexec.go`**: capture stderr in
`RunMutation` and include it in the returned error, so a failing
mutation surfaces the underlying command output (e.g. git's `fatal:`
line) instead of only `exit status N`.
- **`scripts/release-action/cmdexec_test.go`**: add a test asserting
stderr is surfaced on failure.

## Testing

- `go test ./scripts/release-action/...` passes.
- `go vet ./scripts/release-action/...` and `gofmt` clean.
- `actionlint .github/workflows/tag-and-release.yaml` clean.
- Reproduced the failure locally: `git tag -a` with no usable identity
exits 128 (`fatal: no email was given and auto-detection is disabled`);
with an identity configured it succeeds.

<details>
<summary>Root-cause analysis / decision log</summary>

**Failing step** runs `go run ./scripts/release-action prepare-release
--type create-release-branch --ref main --commit cb1a87b…`.

1. The tool computes the next version `v2.35.0-rc.0` and calls
`createAndPushTag`, which runs `git tag -a v2.35.0-rc.0 -m "Release
v2.35.0-rc.0" <targetRef>` (`prepare.go:56`).
2. That git command exits **128**, wrapped as `error: create tag
v2.35.0-rc.0: exit status 128`.

**Why it's the identity, and not something else:**

- No `git config user.name/user.email` step exists in the
`prepare-release` job; the `setup-mise` action does not set it; and the
tool itself never sets an identity. Annotated tags require a tagger, so
`git tag -a` fails on runners whose auto-detected identity is bogus
(`…@runner.(none)`), which is rejected under git's strict identity
check.
- Not a pre-existing tag collision: no `v2.35.0*` tag exists on the
remote, and the code pre-checks for an existing tag (and would emit a
different "already exists" error).
- Not an unresolved ref: `targetRef` resolves to the provided commit
SHA, checked out at `fetch-depth: 0`.
- The log was unhelpful because `RunMutation` used `cmd.Run()` without
wiring git's stderr (`cmdexec.go`), discarding the `fatal:` line and
leaving only `exit status 128`. This PR fixes that too.
- The sibling `release.yaml` explicitly sets `git config
user.email/user.name` before its git mutations; that step was simply
missing from the newer `tag-and-release.yaml` `prepare-release` job.

</details>

---

> Generated by Coder Agents on behalf of @f0ssel.
2026-07-01 15:10:45 -07:00
McKayla はな 5a56a0c776 chore: make ubuntu 26.04 the default (#26943) 2026-07-01 15:34:37 -06:00
Jeremy Ruppel 2318b0e60d feat(site/src/pages/TemplateBuilder): disable create button when no provisioners (#26938)
## Summary

Add provisioner awareness to the Template Builder wizard: disable the
Create Template button when the selected organization has no
provisioners, and reset the customizations step when navigating back.

## Changes

- Query provisioner daemons for the selected org in
`TemplateCustomizationsStep` and show a warning alert when none are
found
- Track `hasProvisioners` in wizard state via `SET_HAS_PROVISIONERS`
action; `computeCanContinue` disables the Create Template button when
`hasProvisioners === false`
- Add `RESET_CUSTOMIZATIONS` action to clear customization fields (name,
displayName, description, icon, organizationId, hasProvisioners) when
navigating back from the customizations step
- Clear the create mutation error on back navigation via
`onClearCreateError` callback

Follows up on #26935 which added the provisioner warning alert to the
Template Builder.

> 🤖 Generated by Coder Agents on behalf of @jeremyruppel
2026-07-01 17:30:09 -04:00
Andrew Aquino 04d523b9ab fix(site): set minimum width for TemplateCustomizationsStep (#26939)
fixes DEVEX-577

Now the template customizations step gets a horizontal scrollbar if the
window is too narrow to nicely display its 2 columns of inputs:


https://github.com/user-attachments/assets/888f723c-8d54-4e37-b53a-e31b6b486e88
2026-07-01 14:18:29 -07:00
Danielle Maywood 0c006a40f3 feat(site): add searchable agent model picker (#26927) 2026-07-01 21:15:35 +00:00
Garrett Delfosse 5573190530 fix(.github/workflows): restore mise tool setup in tag-and-release (#26937)
The `tag-and-release` workflow fails at startup with `Can't find
'action.yml', 'action.yaml' or 'Dockerfile' under
.../.github/actions/setup-go`. #26422 reintroduced stale references to
the `./.github/actions/setup-go` and `./.github/actions/setup-node`
composite actions, both of which were removed in #25727 when CI migrated
shared tool setup to `mise`.

This replaces both with the `setup-mise` pattern already used elsewhere
in the same workflow. The `prepare-release` job now installs `go` via
`setup-mise` (dropping the old `use-cache: false`, since Go caching is
now opt-in through the `go-cache` action). The `update-docs` job
installs `node pnpm` via `setup-mise` and adds `pnpm-install` so
`scripts/update-release-calendar.sh` still has the dependencies it needs
for `make fmt/markdown`.

`actionlint` and the `pre-commit` hook pass locally.

<details>
<summary>Root cause and decision log</summary>

**Symptom:** `tag-and-release.yaml` references
`./.github/actions/setup-go`, but that directory has no
`action.yml`/`action.yaml`/`Dockerfile`.

**How it broke:**

- #25727 (`ci: refactor CI to use mise for shared tool setup`) deleted
`.github/actions/setup-go/action.yaml` and
`.github/actions/setup-node/action.yaml`, migrating every workflow to
`./.github/actions/setup-mise`.
- #26422 (`feat: add dry-run flag via CommandExecutor interface`)
rewrote `tag-and-release.yaml`. It adopted `setup-mise` in one job but
left two stale references: `setup-go` (prepare-release) and `setup-node`
(update-docs), likely a rebase/merge artifact.

**Scope check:** Swept every workflow for local-action references
pointing at missing directories. The only genuine misses were `setup-go`
and `setup-node`. `create-task-action` is an external action checked out
at runtime via `actions/checkout` (not a repo-local action), and
`embedded-pg-cache`/`test-cache` resolve to existing `download`/`upload`
subdirectories.

**Mapping decisions:**

- `setup-go` (`use-cache: false`) to `setup-mise` with `install-args:
"go"`. The old action also installed `gotestsum`/`mtimehash` and
pre-warmed modules, but `prepare-release` only runs `go run
./scripts/release-action`, so `go` alone is sufficient. Caching stays
off, matching the original `use-cache: false`.
- `setup-node` to `setup-mise` with `install-args: "node pnpm"` plus
`pnpm-install`. The old action provided node + pnpm and installed
`node_modules`; `make fmt/markdown` (invoked at the end of the calendar
script) needs `node_modules/.installed` and `pnpm exec
markdown-table-formatter`.
</details>

---
Generated by Coder Agents on behalf of @f0ssel.
2026-07-01 23:13:28 +02:00
Yevhenii Shcherbina db7f4438b4 feat: generate STS external ID for Bedrock role assumption (#26869)
Implements:
https://linear.app/codercom/issue/AIGOV-495/add-externalid-to-prevent-confused-deputy-problem

When a Bedrock provider assumes an IAM role via STS, the gateway now
generates a unique external ID for it and sends that value on every
`AssumeRole` call. The external ID guards against the [confused deputy
problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
on cross-account role assumption. Per [AWS's
recommendation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html),
the gateway generates and owns the value rather than accepting one from
the operator; that ownership is what makes it effective, since a party
who knows another's external ID can't induce the gateway to send it.

The external ID is server-owned and read-only over the API. It is
generated once, when a provider first has a `role_arn`, and is stable
thereafter. Clients cannot set it: create rejects any supplied
`external_id`, and update rejects a value that differs from the stored
one. An update may echo the stored value back unchanged, so the normal
read-modify-write flow (GET the provider, change a field, PATCH the full
settings object) keeps working. The value is not a secret and is
returned on GET so operators can copy it into the target role's trust
policy as an `sts:ExternalId` condition.

It is persisted in the existing JSON settings blob, so there is no
migration or audit-table change.
2026-07-01 20:44:15 +00:00
Jeremy Ruppel 1101a0f974 feat(site/src/pages/TemplateBuilder): add provisioner warning to Template Builder form (#26935)
## Summary

Show a warning in the Template Builder customizations step when the
selected organization has no provisioner daemons connected. This matches
the existing behavior in the current `CreateTemplateForm`.

## Changes

- Added a `provisionerDaemons` query in `TemplateCustomizationsStep`,
gated on the selected org
- Renders an `Alert` warning with a link to provisioner docs when no
daemons are found
- Warning is informational only and does not block form submission

<details>
<summary>Implementation plan</summary>

The current `CreateTemplateForm` (used for upload/starter/duplicate
flows) shows a warning when the selected organization has no provisioner
daemons connected. The new Template Builder wizard
(`/templates/new/builder`) had an org picker in
`TemplateCustomizationsStep` but did not perform this check.

### What was done

1. Imported `provisionerDaemons` query helper, `Alert`, `Link`, and
`docs` utility into `TemplateCustomizationsStep.tsx`
2. Added a `useQuery` call for provisioner daemons, enabled only when an
org is selected
3. Computed `showProvisionerWarning` (true when the provisioners list is
empty)
4. Added a local `ProvisionerWarning` component rendering the same
warning text and docs link as `CreateTemplateForm`
5. Rendered the warning below the `OrganizationAutocomplete` picker

### Design decisions

- Warning is informational only (does not block submission), matching
the existing form behavior and the TODO rationale: a user may connect a
provisioner without refreshing
- Used the project's `Link` component (`#/components/Link/Link`) instead
of MUI Link
- Kept `ProvisionerWarning` as a local component rather than extracting
to shared, since the component is small (~8 lines)

</details>

> 🤖 Generated by Coder Agents on behalf of @jeremyruppel

<img width="2574" height="1274" alt="Screenshot 2026-07-01 at 3 56
58 PM"
src="https://github.com/user-attachments/assets/45f0d81b-1f9a-4a93-a2d3-5f037e82d1d0"
/>
2026-07-01 16:27:24 -04:00
Garrett Delfosse ff7e0bc193 feat: add dry-run flag via CommandExecutor interface (#26422)
## Summary

Adds a `--dry-run` capability to the `release-action` Go tool and
exposes it through a **new** manual workflow, `tag-and-release.yaml`,
without disturbing the existing `release.yaml` pipeline.

PR #25162 had rewritten `release.yaml` in place to be driven by
`scripts/release-action`, which changed its `workflow_dispatch` inputs
from `release_channel`/`release_notes`/`dry_run` to
`release_type`/`commit_sha`. That broke `scripts/releaser`, which
dispatches `release.yaml` with the original inputs. This PR restores
`release.yaml` and moves the Go-driven pipeline to its own workflow.

## Workflow layout after this PR

| Workflow | Trigger | Driven by | Purpose |
|---|---|---|---|
| `release.yaml` | `scripts/releaser` (`gh workflow run`) | legacy
inline shell | Existing pipeline, restored to pre-#25162 state |
| `tag-and-release.yaml` | Manual (Actions UI) |
`scripts/release-action` Go tool | New pipeline with `prepare-release` +
`dry_run` |

`release.yaml` is restored byte-for-byte to its pre-#25162 version, so
its inputs match what `scripts/releaser` sends again.

## `release-action` design

### CommandExecutor interface

Abstracts CLI command execution behind read-only and mutating methods:

| Method | Purpose | Dry-run behavior |
|---|---|---|
| `RunOutput` | Read-only, capture stdout | Executes normally |
| `Run` | Read-only, exit code only | Executes normally |
| `RunMutation` | Changes remote state, no output | **Prints command,
skips execution** |
| `RunMutationStdout` | Changes remote state, streaming I/O | **Prints
command, skips execution** |

Two implementations: `realExecutor` (executes via `os/exec`) and
`dryRunExecutor` (delegates read-only calls, prints mutating calls).

### `prepare-release` subcommand

Composes `calculateNextVersion` with idempotent tag and branch
creation+push, emitting the same JSON as `calculate-version`. Matching
existing refs are skipped; mismatched refs error.

### `tag-and-release.yaml` `dry_run` input

When enabled: `prepare-release` runs with `--dry-run` (version
calculated, plan printed, nothing pushed), notes are generated for
inspection, and the build+publish job is skipped via an `if` guard
(cascading to homebrew/winget/docs).

## Mutating commands covered by `--dry-run`

| Command | Call site |
|---|---|
| `git tag -a <version> ...` | `createAndPushTag` |
| `git push origin refs/tags/...` | `createAndPushTag` |
| `git push origin <sha>:refs/heads/...` | `createAndPushBranch` |
| `gh release create ...` | `publishRelease` |

`git fetch --tags --force origin` is intentionally not a mutation; it
only updates local remote-tracking refs and must run for accurate
version calculation.

## Changes

- **New**: `scripts/release-action/cmdexec.go`, `prepare.go` (+ tests)
- **Refactored**: `git.go`, `github.go`, `calculate.go`, `notes.go`,
`commit.go`, `publish.go` to thread `CommandExecutor`; added `gitMutate`
- **Updated**: `main.go` adds `--dry-run` flag and `prepare-release`
subcommand
- **New**: `.github/workflows/tag-and-release.yaml` (manual, Go-driven,
with `dry_run`)
- **Reverted**: `.github/workflows/release.yaml` to its pre-#25162 state

> [!NOTE]
> Generated by Coder Agents on behalf of @f0ssel
2026-07-01 16:20:00 -04:00
Cian Johnston 4936ff9808 refactor: deprecate AIGatewayRoutingEnabled, remove direct chat routing (#26862)
This PR removes the now-dead direct-routing code:

- Deletes the direct routing implementation.
- Collapses the resolvedModelRoute discriminated union into aiGatewayModelRoute.
- Removes the dead providerKeys cascade.
- Deletes the preferredShortTextCandidates quickgen function.
- Simplifies the advisor override error handling.
- Deprecates the AIGatewayRoutingEnabled deployment option. It is now a no-op so as to not break existing deployments on upgrade.

Once direct routing was gone, the AI Gateway became mandatory for chat, which surfaced gaps in how the product behaves with the gateway disabled:

- Exposes ai-gateway-enabled to the frontend via embedded page metadata.
- Disables the chat composer via the existing AgentSetupNotice when the gateway is disabled, for both new and existing chats.
- Fixes nil/typed-nil chatDaemon panics on startup and shutdown when gateway is disabled.
- Fixes chat WebSocket from retrying the still-gated stream endpoint forever when the gateway is disabled.
2026-07-01 20:15:03 +01:00
McKayla はな 3e0875d236 fix(site): redirect to new organization after create (#26890) 2026-07-01 13:01:35 -06:00
Kyle Carberry 58f70b4488 fix(coderd/x/chatd): sanitize workspace MCP tool names (#26928)
## Summary

Workspace MCP tools (servers a workspace declares in `.mcp.json`) take
their model-facing name from the server key joined with the tool name as
`serverName__toolName`. That name reached the model **unsanitized**, so
a server or tool name containing a character outside
`^[a-zA-Z0-9_-]{1,128}$` (for example `@`) produced an invalid tool
name. Anthropic and Bedrock reject the whole request with `HTTP 400`:

```
tools.N.custom.name: String should match pattern '^[a-zA-Z0-9_-]{1,128}$'
```

which fails the entire turn, not just the one tool. The remote MCP path
(`mcpclient`) and the AI Gateway path (`aibridge/mcp`) already sanitize;
the workspace path did not.

Alternative to #26853 (thanks @ibdafna for the report and repro).

## Fix

Sanitize and length-cap the **model-facing** name, and keep the original
`serverName__toolName` as a `routingName` the workspace agent uses to
reach the original server and tool. `NewWorkspaceMCPTools` builds a
whole set and disambiguates names that collide after sanitization (for
example server keys `foo.bar` and `foo_bar` both exposing `echo`) so
every tool stays addressable in the model's name-keyed dispatch map.
Names already within the allowed set are unchanged, so there is no
behavior change for valid names.

The sanitizer is local to `coderd/x/chatd/chattool`; the fix does
**not** touch the `aibridge` package or the remote MCP client.

### Changes
- `coderd/x/chatd/chattool/mcpworkspace.go`: local provider-safe
sanitizer + length cap, `routingName` for the agent proxy, and
`NewWorkspaceMCPTools` for set-level collision disambiguation.
- `coderd/x/chatd/chatd.go`: build the pinned workspace tool set via
`NewWorkspaceMCPTools`.

## Why sanitize here (not at `.mcp.json` / agent parse)?

The agent uses `serverName__toolName` to route to the real downstream
server (it splits on `__` and calls the original tool name), so
sanitizing at parse time would break routing or merely relocate the
original->sanitized mapping. Sanitization is also a provider constraint
the agent has no knowledge of, and coderd/agent version skew means
coderd must sanitize at its own boundary regardless. The model-facing
boundary in chatd is the right place.

## Test plan
- `@` in a name is sanitized for the model while the original routes to
the agent; a valid name is unchanged; an over-length name is truncated;
colliding names in a set are disambiguated while each still routes to
its own original name.
- `go build`, `go vet`, `golangci-lint`, and `go test
./coderd/x/chatd/chattool/...` pass locally.

<details>
<summary>Design notes / decision log</summary>

**Constraint that drives the design.** The tool name is both the
identifier shown to the model (and the key the model layer dispatches
tool calls by) and, for the workspace path, the string the agent splits
on `__` to route back to the original server and tool. Those roles
conflict once sanitization changes the name, so the name is sanitized
for the model while the unsanitized form is kept as `routingName`.

**Options considered.**
1. **Chosen:** sanitize in the workspace path only, with helpers local
to `chattool`. Smallest blast radius; no new cross-package dependency.
This matches the shape of the other MCP paths (`mcpclient` keeps
`originalName` + `configID`) without sharing code.
2. Sanitize at `.mcp.json` parse time or in the agent. Rejected: breaks
routing (the agent needs the original name), pushes a provider concern
into the agent, and coderd must still defend its own boundary because
the agent and coderd version independently. Tool names also come from
the downstream server at list time, not from `.mcp.json`, so parsing
cannot fully validate them.
3. Extract a shared sanitize/truncate/dedupe helper into `aibridge/mcp`
and adopt it in `mcpclient` too (so the remote path also gains collision
disambiguation). This DRYs all paths, but it grows chatd's coupling to
the `aibridge` subsystem and expands scope/behavior/tests in the remote
path for what is a workspace-path bug. Left out deliberately to keep
this change minimal and self-contained; it can be a separate refactor.
4. Sanitize once at the provider serialization boundary (chat loop). The
only truly generic spot, but the model dispatches by name, so it needs a
reverse (sanitized -> original) mapping and set-wide collision handling
in the model layer. Larger, riskier change.

**Notes.**
- The workspace path defines its own sanitizer (`[^a-zA-Z0-9_-]` -> `_`)
and a `maxModelToolNameLen = 64` constant that mirrors the strictest
provider limit (OpenAI 64, Bedrock 128), rather than importing
`aibridge/mcp`, so it carries no new dependency.
- The set builder sorts before assigning suffixes so disambiguation is
stable across turns.

</details>

---

_Opened by Coder Agents on behalf of @kylecarbs. Alternative to #26853._
2026-07-01 20:34:25 +02:00
Danielle Maywood 52fa49a10f fix(site): update providers docs link (#26916) 2026-07-01 18:34:12 +01:00
Andrew Aquino 88329db59c fix(site): position ModuleCard's checkmark relative to checkbox (#26925)
fixes DEVEX-571

## before


https://github.com/user-attachments/assets/e0e2fff2-4297-433f-9c99-e423c6e9e307

## after


https://github.com/user-attachments/assets/15f92377-910e-4c95-8621-c37532aec5ca
2026-07-01 17:13:18 +00:00
Jeremy Ruppel 1f04d27144 fix: link to /templates/new from Template Builder options (#26924)
- "Upload an existing template" now points to `/templates/new`
- Removes a bit of top margin for the additional actions box

<img width="1099" height="168" alt="Screenshot 2026-07-01 at 1 02 57 PM"
src="https://github.com/user-attachments/assets/12d4683e-2ee8-45e3-b94f-e04a3a583c75"
/>
2026-07-01 13:12:34 -04:00
Denis Afonso f77d0065ed fix: correct gvisor replace directive to match module path (#26822)
## Summary

Fixes the gvisor `replace` directive in `go.mod` to target the correct
module path.

## Problem

PR #23055 added a replace directive to use the coder/gvisor fork (which
fixes an integer overflow causing `panic: length < 0` crashes). However,
the directive targeted the wrong module path:

```
replace gvisor.dev => github.com/coder/gvisor v0.0.0-20260313164934-7a658db7b714
```

The actual module path declared in gvisor's `go.mod` is
`gvisor.dev/gvisor`, not `gvisor.dev`. Go module replace directives
require an exact module path match, so the previous directive was a
no-op and the patched fork was never used.

## Fix

```diff
-replace gvisor.dev => github.com/coder/gvisor v0.0.0-20260313164934-7a658db7b714
+replace gvisor.dev/gvisor => github.com/coder/gvisor v0.0.0-20260313164934-7a658db7b714
```

## Validation

Verified locally with `go list -m`:

**Before (no-op replace):**
```
$ go list -m gvisor.dev/gvisor
gvisor.dev/gvisor v0.0.0-20240509041132-65b30f7869dc
```

**After (correct replace):**
```
$ go list -m gvisor.dev/gvisor
gvisor.dev/gvisor v0.0.0-20240509041132-65b30f7869dc => github.com/coder/gvisor v0.0.0-20260313164934-7a658db7b714
```

The `=>` confirms the fork is now applied.


Fixes https://github.com/coder/coder/issues/20885

---

<details>
<summary>Investigation context</summary>

- The coder/gvisor fork (commit `7a658db7b714`) declares `module
gvisor.dev/gvisor` in its go.mod
- Customer runtime stack traces show
`gvisor.dev/gvisor@v0.0.0-20240509041132-65b30f7869dc` (unpatched
upstream), confirming the fork was not applied
- The crash is `panic: length < 0` in
`gvisor.dev/gvisor/pkg/tcpip/transport/tcp.(*sender).splitSeg`
- Related Linear ticket: ENT-118

</details>

---
*Generated by [Coder Agents](https://coder.com/agents) on behalf of
@denisra*
2026-07-01 17:11:47 +01:00
Jon Ayers b33ff2d851 fix: redact env var values in agent debug manifest endpoint (#26904) 2026-07-01 10:46:35 -05:00
Cian Johnston 679eb00ec4 fix: surface workspace delete failures from AgentsPage archive flow (#26900)
Right-clicking **Archive & delete workspace** on an agent chat could
leave the workspace behind without the user noticing. The archive step
ran first and removed the chat from the sidebar, so when the delete
enqueue failed the user lost the surface to retry and the workspace
lingered.

## Fix

- Delete the workspace first, archive the chat second. If the delete
enqueue fails for anything other than 404/410, the chat stays in the
list so the user can retry. 404/410 are still treated as "already gone"
so the archive proceeds.
- Errors are wrapped in `ArchiveAndDeleteError` tagged with `step:
"delete" | "archive"`. The toast branches on the tag: `delete` failures
show an actionable "Open workspace" link, `archive` failures explain the
delete already ran so no manual deletion is needed.
- Both mutation call sites navigate away on `onSuccess` only (previously
`onSettled`), so a delete failure keeps the chat's retry surface
reachable. The confirm dialog still closes on `onSettled`.
- When the archive step fails after a successful delete,
workspace-related caches are invalidated to keep the rest of the app in
sync with the ongoing deletion.
- On successful enqueue, warn when the build response's
`matched_provisioners.count` is 0. That field is populated on `POST
/workspacebuilds`; `job.queue_position` / `job.queue_size` are not.

> 🤖 This PR was generated by Coder Agents on behalf of @johnstcn.
2026-07-01 15:33:52 +01:00
Mathias Fredriksson 047c47495b refactor: drop chat_model_configs provider column (#26877)
The provider type already lives authoritatively in ai_providers.type,
reachable on every active row through ai_provider_id, which the
chat_model_configs_ai_provider_required_when_active CHECK makes
mandatory. The stored provider string was a denormalized copy the system
kept in sync with a startup backfill and no longer needs.

Every surface now derives provider type from the linked ai_providers
row. Telemetry is the one exception: it keeps emitting provider, now
sourced from ai_providers.type via a JOIN, so the BigQuery column and the
Nexus dashboards that read it are unaffected. The experimental HTTP/SDK
response drops provider and makes ai_provider_id required, since those
endpoints return only active configs; consumers resolve provider type
from ai_provider_id and the AI providers listing.

This ships in a single release with no compatibility window: production
reads the table via SELECT *, so a pre-drop binary fails config reads the
moment the column is gone. Operators must scale to zero before upgrading,
and there is no rollback.

Closes CODAGT-599
2026-07-01 15:59:55 +03:00
Danny Kopping cf75dd0f46 feat: support Anthropic /v1/messages route on Copilot (#26911)
Implements
[AIGOV-481](https://linear.app/codercom/issue/AIGOV-481/featai-gateway-support-anthropic-v1messages-route-on-the-copilot).
Adds the Anthropic-style `/v1/messages` route to the Copilot provider.

GitHub Copilot CLI (1.0.65) using the default `Claude Sonnet 4.6
(default)` model sends Anthropic-style `/v1/messages` requests through
`aibridgeproxyd` to the Copilot provider. The provider only registered
the OpenAI-compatible `/chat/completions` and `/responses` routes, so
these requests failed:

```
CAPIError: 404 404 404 route not supported: POST /copilot/v1/messages
```

*This PR was produced by opencode (agent) using the
`anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-01 12:16:32 +00:00
Danielle Maywood 2839cd2427 fix(site): link manage agents to AI settings (#26912) 2026-07-01 12:55:04 +01:00
Danny Kopping e3ac65aa5f docs: add AI Gateway rebranding migration guide (#26854)
## Summary

Adds an operator-facing migration guide for the AI Bridge to AI Gateway
rebrand, as a new docs page under **AI Coder > AI Gateway** (last child
in the section).

The guide documents:

- **Config aliases** (env vars, CLI flags, YAML group keys): old
`aibridge` names still work as hidden, deprecated aliases; new
`ai_gateway` names are canonical. Includes full env-var mapping tables
and the mechanical substitution rules.
- **HTTP API**: canonical path is now `/api/v2/ai-gateway`; legacy
`/api/v2/aibridge` routes retained.
- **Metrics**: prefixes renamed `coder_aibridged_*` ->
`coder_ai_gateway_*` and `coder_aibridgeproxyd_*` ->
`coder_ai_gateway_proxy_*`. Both old and new names are emitted today, so
dashboards keep working; guidance to migrate before the old names are
removed, plus an optional `metric_relabel_configs` drop snippet.
- **No database changes** and no required config changes to upgrade.

Implements the docs/release-notes portion of
[AIGOV-240](https://linear.app/codercom/issue/AIGOV-240) (parent:
[AIGOV-207](https://linear.app/codercom/issue/AIGOV-207)).

## Notes

- Content reflects what actually shipped in the codebase (metrics are
*aliased*, not hard-renamed), which differs from the original RFC that
assumed a hard rename.
- Registered in `docs/manifest.json` with `state: ["ai governance
add-on"]` to match sibling pages.


---

*This PR was produced by opencode (agent) using the
`anthropic/claude-opus-4-8` model, under human direction and review.*

---------

Signed-off-by: Danny Kopping <danny@coder.com>
2026-07-01 10:54:19 +02:00
Jakub Domeracki 28447f16ea ci(.github/workflows): update checkout to v7 (#26909)
Update GitHub Actions workflows to use `actions/checkout` v7.0.0 pinned
to `9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0`, following the GitHub
Actions checkout hardening changes announced in:

-
https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/
-
https://github.blog/changelog/2026-06-18-control-who-and-what-triggers-github-actions-workflows/

Audited the existing `pull_request_target` workflows and did not add any
`allow-unsafe-pr-checkout` opt-outs, since these workflows do not
intentionally check out fork PR head code.

Generated by Coder Agents.

<details>
<summary>Plan notes</summary>

- Update all `.github/workflows` `actions/checkout` references to v7.0.0
using the pinned SHA.
- Preserve SHA pinning, including the newly added MCP registry workflow.
- Validate that old checkout pins are removed and no unsafe checkout
opt-outs are introduced.

</details>
2026-07-01 10:17:35 +02:00
Jon Ayers 6b3341aad3 fix!: require org membership for user ACLs (#26852) 2026-07-01 02:15:08 -05:00
Ethan ac8cda66f7 feat: make Coder Agents right sidebar app and port tabs generally available (#26906)
The workspace-app and port preview tabs in the Coder Agents right panel
were gated behind the `agent-app-tabs` deployment experiment. This
removes the experiment entirely and renders the app and port tabs
unconditionally, so the add-panel dropdown, workspace-app tabs, and port
preview tabs are always available alongside terminals.

## Changes

- Remove the `ExperimentAgentAppTabs` constant, its `DisplayName()`
case, and its `ExperimentsKnown` registration in
`codersdk/deployment.go`, then regenerate
`site/src/api/typesGenerated.ts`, `coderd/apidoc/docs.go`,
`coderd/apidoc/swagger.json`, and `docs/reference/api/schemas.md`.
- Drop the frontend experiment gate in `AgentChatPageView.tsx`
(including the now-unused `useDashboard`/`experiments` usage) so
persisted app and port tabs are no longer filtered out.
- Remove the `appExperimentEnabled` prop from `RightPanelAddTabControl`
and render the add-panel dropdown unconditionally; update the stories
accordingly.

This reverses the gating introduced in #26395.

note: the diff is tiny if you hide whitespace changes
2026-07-01 16:58:10 +10:00
Jake Howell cb1a87b9c0 fix(site): stop click propagation so popover opens inside clickable rows (#26875)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

<img width="628" height="353" alt="image"
src="https://github.com/user-attachments/assets/16fbf58f-282a-4629-b0de-45160117adb6"
/>

Linear:
[DES-22051](https://linear.app/codercom/issue/DES-22051/adjust-workspace-list-icon-and-agent-link-affordance)

## Problem

[#23374](https://github.com/coder/coder/pull/23374) swapped the
underlying `Tooltip` for a `Popover` under the `HelpTooltip` →
`HelpPopover` rename, turning the trigger from a hover surface into a
click surface.

On the workspaces list, `WorkspaceOutdatedTooltip` is rendered inside a
`<TableRow>` wired up by `useClickableTableRow`, whose `onClick`
navigates to the workspace page. Other interactive children in the same
row already stop propagation (checkbox, actions cell, agent badge). The
outdated tooltip didn't, so clicking the info icon bubbled up to the
row's `onClick` and navigated away before the popover could open.

## Fix

Stop click and keydown propagation on both trigger variants
(`HelpPopoverTrigger asChild` span and `HelpPopoverIconTrigger`) in
`WorkspaceOutdatedTooltip`. The popover still opens because Radix
composes its own click handler on top of the user-provided one via
`composeEventHandlers`; `stopPropagation()` does not set
`defaultPrevented`, so Radix's toggle still runs.

## Regression coverage

Added an `InsideClickableRow` story that mounts the tooltip inside a
`useClickableTableRow` row with a tracked `onRowClick`, clicks the
trigger, asserts the popover dialog opens, and asserts `onRowClick` was
not called. Verified locally that the new story fails on `main` (1 call
to `onRowClick`) and passes with this fix.

<details>
<summary>Decision log</summary>

- Fix lives on the component (rather than at the `WorkspacesTable.tsx`
call site) so every consumer is safe by default. The popover is
interactive in a way the parent shouldn't have to know about, and the
existing call site in `TaskPage.tsx` is unaffected because its parent
has no click handler.
- `onKeyDown` propagation is also stopped so the trigger keeps working
when activated via keyboard, and to satisfy
`lint/a11y/useKeyWithClickEvents` on the `<span>` trigger variant.
- Considered pushing the guard down into the `HelpPopoverIconTrigger` /
`HelpPopover` primitives so every consumer is covered — left as a
separate follow-up since today only `WorkspaceOutdatedTooltip` is
embedded in a `useClickableTableRow` row, and a blanket change would
need a broader audit.

</details>
2026-07-01 03:21:26 +00:00
Jake Howell 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>
2026-07-01 13:14:23 +10:00
Jeremy Ruppel a73e677639 fix: several UI fixes for Template Builder (#26903)
- Module selection summary now has `rounded-sm` corners to fit the icon
- Modules without configuration now say so with a check mark
- Correct "Additional settings" label
2026-06-30 20:01:48 -04:00
Jeremy Ruppel c56a8f1d99 feat(site/src/pages/TemplateBuilder): add scroll overflow and sticky sidebar to wizard steps (#26881)
Add vertical overflow scrolling to the TemplateBuilder wizard step
content areas and make the selection sidebar sticky.

## Changes

- **Scroll on inner content**: Each step's inner content area (below the
heading and any tabs/search) gets `max-h-[calc(100vh-Npx)]
overflow-y-auto`, keeping headings and tab bars visible while the
content scrolls.
- Base template select and module select grids: 420px offset (accounts
for navbar, page header, card padding, tab bar, and nav controls)
  - Base template parameters and module settings: 340px offset
  - Customizations step: no scroll, flows naturally
- **Sticky sidebar**: The selection summary sidebar uses `sticky top-0
self-start` to stay visible while scrolling.
- **Scroll reset on navigation**: `window.scrollTo(0, 0)` in
Back/Continue handlers so users start at the top of each step.

> 🤖 Generated by Coder Agents

<details><summary>Implementation plan</summary>

The approach uses `max-h` with `overflow-y-auto` on the inner content
divs of each step component, placed below the step heading and any
tabs/search controls. This keeps the heading fixed while the content
scrolls. The offset values account for the vertical space consumed by
the navbar, page header, card border/padding, and navigation controls.

The `DashboardFullPage` flex-fill approach was explored but abandoned
because the viewport-constrained card was too short to show content on
smaller screens. The standard `Margins` + `pb-12` layout with per-step
`max-h` provides a better balance.
</details>
2026-06-30 20:00:56 -04:00
Jeremy Ruppel 3d966d48b5 fix(coderd/templatebuilder): fix archive bundling for nested static files and counted agents (#26901)
Fixes two template builder bugs that caused AWS EC2 (Linux) template
imports to fail:

1. **Missing directory entries in tar archive**: `BundleTar` wrote
static files with nested paths (e.g.
`cloud-init/cloud-config.yaml.tftpl`) without emitting `TypeDir` entries
for parent directories. The provisioner's archive extractor requires
explicit directory entries and failed with "no such file or directory".

2. **Incorrect agent reference for counted resources**:
`ExtractAgentResourceName` returned `dev` for the AWS Linux base
template, but the agent uses `count =
data.coder_workspace.me.start_count`, so module templates need
`coder_agent.dev[0].id`. The function now detects `count`/`for_each` and
appends `[0]`.

> [!NOTE]
> Generated by Coder Agents (on behalf of @jeremyruppel)
2026-06-30 19:37:30 -04:00