Commit Graph
4192 Commits
Author SHA1 Message Date
Ethan f3c25de7aa fix(coderd/x/chatd): stabilize dial timeout recovery test (#26153)
Fixes a scheduler-dependent flake in chatd's dial-timeout recovery path.

The dial timeout now uses the server's quartz clock, and
`dialWithLazyValidation` also uses that clock for its validation-delay
timer. If a dial result races with a canceled parent context, the
cancellation now wins instead of treating the cancellation-produced dial
error as a fast failure that triggers eager validation.

The recovery-threshold test now traps and advances the mock clock, which
keeps strict DB expectations without depending on wall time or goroutine
scheduling.

Closes https://github.com/coder/internal/issues/1569
Closes ENG-2838
2026-06-11 16:08:32 +10:00
Callum StyanandMux 4e2c9f9cae fix: allow lifecycle code path to retry failed stop jobs (#26203)
Co-authored-by: Mux <mux@coder.com>
2026-06-10 16:29:00 -07:00
George K 0b99e67ce7 fix(coderd/workspaceapps): verify workspace owner matches app username (#26085)
fix(coderd/workspaceapps): verify workspace owner matches app username

When resolving a workspace app by workspace UUID, the URL's username
segment was never reconciled against the resolved workspace's owner.
A user could serve their own workspace app from a hostname embedding
another user's username, so the parsed origin username belonged to the
victim.  Combined with the username-equality CORS check, this allowed
credentialed cross-origin reads of the victim's app responses.

Reject the request with a 404 when the resolved workspace's owner does
not match the user named in the request.

Refs: https://linear.app/codercom/issue/PLAT-260
2026-06-10 16:26:45 -07:00
Zach 4198358402 fix: stabilize TestExecutorAutostopAIAgentActivity (#26004)
`TestExecutorAutostopAIAgentActivity` flaked when the test clock and the
database clock straddled a minute boundary, leaving the executor's
minute-aligned tick on the wrong side of the bumped deadline. Anchor
tick times to the deadline the database wrote after the bump.
2026-06-10 14:00:19 -06:00
Nick Vigilante cfb03f52db fix: update stale docs URLs across non-TS files (#25750)
Closes [DOCS-256](https://linear.app/coder/issue/DOCS-256). Sibling to
[DOCS-253](https://linear.app/coder/issue/DOCS-253) (#25740).

Updates docs URL references across the non-TypeScript surface of
`coder/coder` to match the current docs site structure. Source-of-truth
for redirects is `coder/coder.com/redirects.json` (parent ticket
[DOCS-209](https://linear.app/coder/issue/DOCS-209)).

## What changed

| Area | Files | URL mapping |
|---|---|---|
| Top-level README | `README.md` | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates` ->
`/docs/admin/templates`, `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Docs source | `docs/admin/security/0001_user_apikeys_invalidation.md`
| `/docs/admin/audit-logs` -> `/docs/admin/security/audit-logs` |
| Docs source | `docs/install/cloud/azure-vm.md` |
`/docs/coder-oss/latest/install` -> `/docs/install` |
| Dogfood | `dogfood/coder/guide.md` | `/docs/ides` ->
`/docs/user-guides/workspace-access` |
| Helm | `helm/coder/values.yaml` | `/docs/admin/workspace-proxies` ->
`/docs/admin/networking/workspace-proxies` |
| Enterprise coderd | `enterprise/coderd/coderd.go` |
`/docs/admin/encryption` -> `/docs/admin/security/database-encryption`
(error message) |
| Release tooling | `scripts/release/main_internal_test.go` |
`/docs/admin/upgrade` -> `/docs/install/upgrade` (test fixture, matches
`generate_release_notes.sh`) |
| AI bridge | `aibridge/client.go` | repinned to current `main` SHA on
renamed `docs/ai-coder/ai-gateway/monitoring.md`, line range `#L47-L57`
|
| Example templates | 12 `examples/templates/*/README.md`,
`examples/parameters/*`,
`examples/parameters-dynamic-options/README.md`,
`examples/workspace-tags/README.md`, `examples/parameters/main.tf`,
`examples/examples.gen.json` (regenerated) | `/docs/workspaces` ->
`/docs/user-guides/workspace-management`, `/docs/templates/parameters`
-> `/docs/admin/templates/extending-templates/parameters`,
`/docs/templates/dev-containers` ->
`/docs/admin/integrations/devcontainers`, `/docs/dotfiles` ->
`/docs/user-guides/workspace-dotfiles`,
`/docs/about/architecture#agents` ->
`/docs/admin/infrastructure/architecture#agents` |
| Live notification templates (DB) | New migration
`000510_fix_dormancy_notification_docs_urls.up.sql` and `.down.sql` plus
the four regenerated SMTP/webhook goldens under
`coderd/notifications/testdata/rendered-templates/` |
`/docs/templates/schedule#dormancy-threshold-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-threshold`,
`/docs/templates/schedule#dormancy-auto-deletion-enterprise` ->
`/docs/admin/templates/managing-templates/schedule#dormancy-auto-deletion`
|

The migration uses `REPLACE(body_template, ...)` scoped by template id
and `LIKE '%/docs/templates/schedule%'`, so it works regardless of which
intermediate state (`000232`, `000262`, `000305`, or `000311`) is
currently in the row.

## What did not change

Historical SQL migrations `000232`, `000262`, `000305`, and `000311` are
not modified because migrations are immutable history. The 18 remaining
stale URL references in those files are superseded at runtime by
migration `000510`. This decision matches the pattern used in the A1
sister PR (#25740).

## Verification

- `go test ./coderd/database/migrations/... -count=1` (UP+DOWN)
- `go test ./coderd/notifications/ -run TestNotificationTemplates_Golden
-update -count=1` to regenerate the four `.golden` files
- `go test ./scripts/release/ -run Test_removeMainlineBlurb -count=1`
- `make pre-commit` (gen + fmt + lint + slim build) ran clean as part of
the commit hook

I also fixed a pre-existing emdash on line 35 of
`examples/templates/azure-linux/README.md` that the lint flagged once
the file entered my diff. The line was already in `main`, but `make gen`
rewrites `examples/examples.gen.json` whenever a `README.md` changes, so
the line came back as a `+` in the diff against `origin/main` and the
`lint/emdash` step refused it.

<details>
<summary>Pre-mortem</summary>

| Risk | Mitigation |
|---|---|
| Migration overwrites future template edits | Used `REPLACE` instead of
full body overwrite. `WHERE id IN (...) AND body_template LIKE
'%/docs/templates/schedule%'` further scopes the write |
| Goldens drift from migrated body | Regenerated goldens via `-update`
after the migration was in place, so the goldens reflect the
post-migration state |
| Down migration leaves stale URLs | Down migration reverses the REPLACE
so a rollback restores the prior URLs |
| Fragment loss when redirect strips fragment | Verified the destination
`schedule.md` contains `## Dormancy threshold` and `## Dormancy
auto-deletion` anchors |
| Terraform parse breakage in `examples/parameters/main.tf` | Only
comments changed; Terraform parser is unaffected |
| Test fixtures in `scripts/release` diverging from
`generate_release_notes.sh` | Updated to match the script, which already
emits `/docs/install/upgrade` |

</details>

---

Generated by Coder Agent on behalf of @nickvigilante.
2026-06-10 13:40:50 -04:00
McKayla はな d830582b5e chore(coderd/idpsync): remove stale TODO (#26198) 2026-06-10 10:20:18 -06:00
Yevhenii Shcherbina 1cada0649c feat(coderd): resolve effective user AI budget (#26142)
Closes
https://linear.app/codercom/issue/AIGOV-287/add-effective-group-resolution

Implements the effective AI budget resolution from the AI Governance
cost-controls RFC: for a given user, a `user_ai_budget_overrides` row
wins if present, otherwise the deployment budget policy (`highest`)
picks the largest group budget across the user's groups, ties broken
alphabetically.

For now, I keep the logic under `coderd/aibridge/budget`, but that may
change during the implementation of budget enforcement.
2026-06-10 14:01:20 +00:00
Cian Johnston f9dfa18c46 fix(coderd/x/chatd): retry quota 429s (#26200)
Fixes CODAGT-495

Provider 429 responses that mention quota or billing were classified as
non-retryable usage limits before the rate-limit rule could run, which
suppressed retries for Gemini and Azure OpenAI rate limits.

- Treats broad quota and billing prose as rate-limit retryable when the provider returns HTTP 429.
- Preserves `insufficient_quota` as a terminal usage-limit signal for OpenAI billing exhaustion.
- Includes structured provider details in usage-limit matching so response-body error codes are classified consistently.

Generated by Coder Agents on behalf of @johnstcn.
2026-06-10 11:01:33 +01:00
Jon Ayers 35a7dc8ab9 fix: use a random value for a simulated hash for built-in users (#26205) 2026-06-10 02:18:44 -05:00
Ethan deb6eec68b fix: forward attached filenames to Anthropic chat models (#26051)
Previously, fantasy's Anthropic provider adapter accepted PDF and text
FileParts but dropped the filename on the floor, so Claude (direct or
via Bedrock) saw the document bytes without any handle and could not
answer questions like "what's in foo.pdf". Other providers (OpenAI,
Gemini, OpenRouter, Vercel) already forwarded filenames.

Bumps `coder/fantasy` past
[coder/fantasy#38](https://github.com/coder/fantasy/pull/38), which
sanitizes `FilePart.Filename` and sets it as the Anthropic
`DocumentBlockParam.Title` for both `application/pdf` and `text/*`
attachments, and emits a `CallWarning` for unsupported `FilePart` media
types instead of silently dropping them.

On this side, plumbs the resolved filename through `partsToMessageParts`
so the `FilePart` literal carries it into the provider. The existing
`TestModelFromConfig_AnthropicPDFFilePartReachesProvider` regression
test is extended to assert the outbound Anthropic request includes the
sanitized title (`quarterly_report.v1.pdf` becomes `quarterly report v1
pdf`).

Closes CODAGT-545
2026-06-10 11:37:33 +10:00
Yevhenii Shcherbina 360611ea15 feat: audit user AI budget override mutations (#25745)
Relates to
https://linear.app/codercom/issue/AIGOV-285/add-user-budget-overrides-table-and-crud-api

Adds audit-log support for `user_ai_budget_override` mutations. Without
it, an admin could quietly change a user's per-user spend cap (e.g. from
`$500` to `$50`), reassign it to a different group, or delete it
entirely with no record of who did it.

Both write (`create-or-update`) and delete actions now generate audit
log entries. Unlike group AI budgets, which only track `spend_limit`,
overrides also track `group_name`: an override can be reassigned to a
different attributed group, so that change needs to show up in the diff.
The raw `spend_limit_micros`, IDs, and timestamps are ignored in favor
of the human-readable `spend_limit` and `group_name`.

Depends on #25439.

## Screenshot

<img width="1343" height="514" alt="image"
src="https://github.com/user-attachments/assets/aee30f58-6e81-435e-9bca-5bc98f49d8d3"
/>
2026-06-10 00:29:06 +00:00
Paweł Banaszewski 0d2c9f904a fix: check user user is active in aibridge auth (#26173)
Adds check that user is active when authenticating request in AI
Gateway.
2026-06-09 19:01:23 +02:00
Michael SuchaczandSusana Cardoso Ferreira c349ea6b78 fix: preserve gemini thought signatures (#25933)
AI Bridge reserializes OpenAI chat-completions requests before sending
them upstream. For Gemini OpenAI-compatible routes, that OpenAI
typed-parameter round trip drops
`tool_calls[].extra_content.google.thought_signature`, so Google rejects
tool-result continuations with `Function call is missing a
thought_signature`.

This PR:
- patches the AI Bridge upstream serialization boundary for Gemini
OpenAI-compatible chat completions
- shares the Gemini thought-signature patching helpers with chatd's
OpenAI-compatible transport patch to keep behavior consistent
- treats direct Google OpenAI-compatible upstream endpoints as
Gemini-scoped even when the request model is an alias
- adds the Google fallback thought signature to every assistant tool
call in the active turn, including parallel tool calls
- covers the regression that `extra_content` is dropped before the
upstream body is patched

> Mux updated this PR description on behalf of Mike.

---------

Co-authored-by: Susana Cardoso Ferreira <susana@coder.com>
2026-06-09 12:11:43 +01:00
Susana Ferreira 01ec5e4577 feat: add key pool failover metrics to aibridge (#25901)
## Description

This PR adds Prometheus metrics for aibridge's API-key failover, giving visibility into key pool health and failover behavior per provider.

The following metrics are introduced:

- **`key_pool_state`** (gauge): number of keys currently in each state (`valid`, `temporary`, `permanent`) per provider, sampled at scrape time.
- **`key_pool_state_transitions_total`** (counter): key state transitions during failover, labeled by `reason` (`rate_limited`, `unauthorized`, `forbidden`).
- **`key_pool_exhaustions_total`** (counter): times a pool ran out of usable keys, labeled by `outcome` (`rate_limited`, `auth_failed`).
- **`key_pool_failover_attempts`** (histogram): keys attempted before success or exhaustion (per interception for bridged requests, per request for passthrough).

## Changes

- Moves `MarkKeyOnStatus` and key-pool error handling onto `*keypool.Pool`.
- Attaches metrics to each provider's key pool at install time, on construction and on provider reload.
- Adds a scrape-time state collector and a `KeyPools()` accessor on the bridge pool to feed it.
- Tracks per-request key attempts in the bridged and passthrough failover paths.
- Adds test coverage for the new metrics across the keypool unit tests, the bridged intercept failover tests, and the passthrough failover test.

Closes https://github.com/coder/internal/issues/1447
Closes https://linear.app/codercom/issue/AIGOV-198/aibridge-key-failover-observability

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
2026-06-09 10:49:47 +01:00
Kyle Carberry cd3692c0c2 feat: add agent-side workspace context sources and Agent API v2.10 PushContextState (#25983)
Adds the agent half of the workspace context sources RFC. The agent now
resolves instruction files, skills, and MCP configs into a typed
`Snapshot`, watches the relevant paths recursively, exposes the source
list over a workspace-agent HTTP API, and pushes each `Snapshot` to
coderd over a new `PushContextState` RPC on Agent API v2.10.

The coderd-side handler is a stub returning `Unimplemented` for now.
Real persistence to `workspace_agent_context`, chatd hydration on dirty
events, and the `KindMCPServer` MCP provider are tracked by
[CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd).
This matches the pattern used for v2.7 `ReportBoundaryLogs` in
[#21293](https://github.com/coder/coder/pull/21293), which bumped the
version and shipped a stub server so the wire and client could iterate
before the persistence layer landed.

## What ships

### agent/agentcontext (new package)

- `Source`, `Resource` (kinds `instruction_file`, `skill`, `mcp_config`,
`mcp_server` plus reserved `plugin`/`hook`/`subagent`/`command`),
`ResourceStatus`, `Snapshot`, `ComputeAggregateHash`.
- `Manager` owns the in-memory source list, performs the initial resolve
synchronously in `NewManager`, runs a re-resolve/watcher loop in `Run`,
exposes
`AddSource`/`RemoveSource`/`Sources`/`HasSource`/`Snapshot`/`SubscribeChanges`/`Resync`/`SeedSources`/`Close`.
- `Resolver` walks scan roots, classifies recognized files, enforces 64
KiB per-resource, 2 MiB aggregate, and 500-resource caps with
`StatusOversize`/`StatusExcluded`/`StatusUnreadable`/`StatusInvalid`
outcomes, skips `node_modules`/`vendor`/etc., validates symlink targets
stay inside the scan root, stamps `SourcePath` on user-derived
resources, and optionally pulls MCP server tool lists via an
`MCPProvider` interface. MCP config resources ship metadata only (size,
hash) so secrets in env blocks never leave the agent.
- `Watcher` is a recursive `fsnotify` wrapper with a 250 ms debounce,
dynamic arming of newly created directories, and an ENOSPC-tolerant
degraded mode that no-ops further syncs until the manager resyncs
explicitly.
- HTTP API for `GET/POST /sources`, `GET/DELETE /sources/{path}`, `POST
/resync` mounted at `/api/v0/context`.
- `Pusher` interface plus `RunPush` goroutine with exponential backoff
capped at 30 s. `DRPCPusher` adapts the generated `DRPCAgentClient210`
to `Pusher` and translates `drpcerr.Unimplemented` to
`ErrPushUnimplemented` so the push loop exits cleanly when talking to
coderd deployments that have not enabled the real handler.

### agent/proto (v2.10)

- New messages `ContextResource`, `PushContextStateRequest`,
`PushContextStateResponse` and the `PushContextState` RPC on `service
Agent`.
- Generated `DRPCAgentClient210` interface and
`codersdk/agentsdk.Client.ConnectRPC210` / `ConnectRPC210WithRole`.
- `tailnet/proto.CurrentMinor` bumped from `9` to `10`.

### Agent wiring

- `agent.Options.Client` declares both v2.9 and v2.10 connectors;
`run()` dials with `ConnectRPC210WithRole`.
- `apiConnRoutineManager` holds a `DRPCAgentClient210`. Existing v2.8
routines keep their narrower `DRPCAgentClient28` signature thanks to
interface embedding.
- `startAgentAPI210` is the v2.10 counterpart to `startAgentAPI` for
routines that need the new client. The push context state routine uses
it.
- A `contextManager` is constructed in `agent.init()`, seeded from the
existing `CODER_AGENT_EXP_*_DIRS` env vars, started in its own goroutine
under `gracefulCtx`, and closed in `agent.Close`.
- `handleManifest` calls `Manager.SeedSources` for sources rooted at the
manifest directory, then `Resync` after `manifest.Swap`, so the snapshot
reflects the workspace working directory immediately instead of waiting
for the next filesystem event.
- HTTP routes mounted at `/api/v0/context` when the manager is up.

### Coderd stub

`coderd/agentapi/context.go` returns `drpcerr.Unimplemented` for
`PushContextState`. The real handler that persists
`workspace_agent_context` rows, hydrates chats, and emits dirty events
lives in CODAGT-569.

## Tests

24 tests across `agent/agentcontext` cover types, paths, resolver
behavior with file caps, skill containers, MCP secret omission, symlink
target validation, the recursive watcher firing on real fsnotify events,
manager source CRUD / `Resync` / `SeedSources` / `Run` lifetime, the
HTTP API, the DRPC adapter, and the push retry / initial-flag /
unimplemented paths. Passes `go test -race -count=2`.

`TestAgent_ContextStatePushed` boots a full agent against
`agenttest.FakeAgentAPI` (which now records `PushContextState` traffic)
and asserts the seeded `AGENTS.md` appears in a snapshot push with
`schema_version = 1`.

<details>
<summary>Notes for reviewers</summary>

- Source CRUD is workspace-agent-token only; coderd is not in the path
for source mutation.
- Per-resource cap 64 KiB, aggregate 2 MiB, count cap 500; resources
past the cap ship with `StatusExcluded` and an empty payload so the
aggregate hash still detects content edits. MCP-emitted resources
enforce both a per-provider count cap and the aggregate byte cap.
- Symlinks inside the scan root are followed; symlinks pointing outside
(or broken) are rejected with `StatusExcluded` so credentials reachable
via a stray symlink stay off the wire.
- The initial push gates `lifecycle = ready` in the eventual full
design. For this PR the `SeedSources` plus `handleManifest`-driven
`Resync` keeps the snapshot fresh; the live push loop ships now and
DRPCPusher translates the coderd `Unimplemented` stub into a clean exit.
- The `PLUGIN`/`HOOK`/`SUBAGENT`/`COMMAND` kinds are reserved in proto
and Go enums but unused; the Claude Code plugin resolver ships in a
follow-up that does not need a schema migration.
- Two follow-ups remain, both tracked by
[CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd):
(1) the chatd-side handler that persists snapshots and dirties chats;
(2) the `coder exp chat context` CLI command set for
`list`/`show`/`add`/`remove`/`refresh`.

</details>

_This PR was authored by Coder Agents on Kyle Carberry's behalf._
2026-06-08 12:08:40 -07:00
Danielle Maywood d751b46a19 fix: scope combined chat source filters (#26137) 2026-06-08 15:05:27 +01:00
Danielle Maywood 1e5dd83a95 fix: limit shared chats to ACL grants (#26123) 2026-06-08 12:21:16 +01:00
Danielle Maywood fa56224eda feat: show shared chats in agents sidebar (#26056) 2026-06-06 00:12:20 +01:00
Steven Masley f17f8392bd feat(coderd): gate org-member workspace elevation behind experiment (#26027)
Gates the workspace-ops elevation on `organization-member` and `organization-service-account` behind the `minimum-implicit-member` experiment.
2026-06-05 15:31:33 -05:00
Steven Masley 8a5e04e90f test: include per-org default roles in rbac user subjects (#26003)
Aligns the `coderdtest` user subject helper with production so per-org default member roles surface in tests.
2026-06-05 15:01:45 -05:00
Steven Masley 938c2080f3 feat: configurable default org member roles (#25994)
Refs #25936. 
Adds a configurable per-org default member role set. Unioned into each member's effective roles at read time.

<sub>with Coder Agents on behalf of @Emyrk.</sub>
2026-06-05 14:33:13 -05:00
Mathias Fredriksson 578793be1d fix(coderd/x/chatd): disclose execute shell is POSIX sh (#26101) 2026-06-05 19:02:52 +03:00
Zach d1f2dec4ff fix: align autostart tests with persisted next_start_at (#26037)
`TestExecutorAutostartOK` and its sibling positive autostart tests
compute the autobuild tick from
`sched.Next(workspace.LatestBuild.CreatedAt)`, but the server persists
`next_start_at` from the build's completion time. When build creation
and completion straddle the schedule's next fire time, the persisted
value advances past the test's tick, the executor's eligibility query
(`next_start_at <= tick`) drops the workspace, and the test fails with
an empty transitions map. This surfaced in flaky test runs.

Add `coderdtest.NextAutostartTick(t, workspace)` which returns
`*workspace.NextStartAt`, and use it across the affected positive
autostart paths in `coderd/autobuild`, `coderd`, and
`enterprise/coderd`.

Generated with assistance from Coder Agents.
2026-06-05 09:55:32 -06:00
Jon Ayers 1f8a8e8356 fix: fix flakiness in nats localsub test (#26084)
fixes https://github.com/coder/internal/issues/1552
2026-06-05 10:47:36 -05:00
Steven Masley bec2115e75 refactor: extract organization-workspace-access role (#25929)
<!-- Authored by Coder Agents on behalf of @Emyrk. -->

Refs
[PLAT-217](https://linear.app/codercom/issue/PLAT-217/rfc-for-gateway-accounts).

Extracts an `organization-workspace-access` role so workspace elevation
can be split off the organization-member floor without changing
behavior.

- New role holds the workspace-side resources currently granted by
`organization-member`.
- The `MinimumImplicitMember` floor preserves the existing behavior
until #26027 shrinks it.
- Prebuilds orchestrator inserts memberships via
`dbauthz.AsSystemRestricted` and no longer needs `OrganizationMember` or
`AssignOrgRole` grants.

<details><summary>Agent context</summary>

- `coderd/rbac/roles.go`: `OrgWorkspaceAccessMemberPerms()` grants
`Workspace`, `WorkspaceDormant`, `File` (Create+Read),
`ProvisionerDaemon` (Create+Read), and `Task`. Deliberate omissions
(`Template`, `Group`, `WorkspaceProxy`, etc.) are documented inline.
- `coderd/rbac/roles_test.go`: `orgWorkspaceAccessUser` is added to
`requiredSubjects`. `UserProvisionerDaemons` is split into
`UserProvisionerDaemonsCreate` and `UserProvisionerDaemonsUpdateDelete`
because the new role grants Create+Read only and the test framework
requires uniform pass/fail per case.
- `codersdk/rbacroles.go`: exposes `RoleOrganizationWorkspaceAccess`.
- `enterprise/coderd/prebuilds/membership.go`:
`InsertOrganizationMember` runs under `dbauthz.AsSystemRestricted`. The
orchestrator never acts with the elevation role; the membership row only
exists so prebuilt workspaces have a valid owner.
- `coderd/database/dbauthz/dbauthz.go`: drops the now-dead
`OrganizationMember` and `AssignOrgRole` permissions from the
prebuilds-orchestrator role and the orchestrator's entry in
`assignRoles`.

</details>

---

<sub>Coder Agents on behalf of @Emyrk.</sub>
2026-06-05 10:30:31 -05:00
Cian Johnston 63cd8a8c01 fix: clamp template port sharing level in SubAgentAPI (#26061)
Fixes an issue where sub-agent apps created via CreateSubAgent would
bypass the check for the template's max port sharing level:

- Clamps dynamically inserted `workspace_apps` to the template max
sharing level in `coderd.agentapi.SubAgentAPI`.
- Emits a warning when clamping occurs.
- Adds unit test coverage for the max sharing level matrix.
- Adds an integration-ish test through the devcontainer sub-agent client
path.

> 🤖 Generated by Coder Agents with guidance from a human.
2026-06-05 16:30:15 +01:00
Michael Suchacz c4792cf104 fix: show Anthropic Opus 4.7+ thinking (#26026)
## Summary

- Updates Coder's pinned `github.com/coder/fantasy` fork to include
coder/fantasy#39.
- Exposes Anthropic `thinking_display` as a typed chat model provider
option with `summarized` and `omitted` values.
- Validates configured `thinking_display` values and maps them to
`fantasyanthropic.ProviderOptions.ThinkingDisplay`.
- Regenerates the API/UI option schemas so the admin model config form
gets a generated select field.

## Tests

- `go mod tidy`
- `make gen`
- `go test ./codersdk ./coderd/x/chatd/chatprovider ./coderd -run
'TestChatModelProviderOptions|TestAnthropicThinkingDisplayFromChat|TestProviderOptionsFromChatModelConfig_AnthropicThinkingDisplay|TestMergeMissingProviderOptions_AnthropicThinkingDisplay|TestValidateChatModelProviderOptions_AnthropicThinkingDisplay'`
- `go test ./coderd/x/chatd/... ./codersdk`
- `go test ./coderd -run
'TestValidateChatModelProviderOptions_AnthropicThinkingDisplay'`
- `pnpm --dir site exec -- biome lint --error-on-warnings
src/api/chatModelOptionsGenerated.json src/api/typesGenerated.ts`
- pre-commit hook, including fmt, lint, and slim build

> Mux working on behalf of Mike.
2026-06-05 08:37:54 +02:00
Callum StyanandMux 4627b01415 fix: reduce agentfake manager startup time (#25669)
Signed-off-by: Callum Styan <callumstyan@gmail.com>
Co-authored-by: Mux <noreply@coder.com>
2026-06-04 15:28:13 -07:00
Michael Suchacz 242c4d791b fix(coderd): isolate OIDC fake IDP in parallel subtests (#26075) 2026-06-04 23:03:02 +02:00
Garrett Delfosse d5b0e93c6c fix!: reject OIDC login when email_verified claim is non-bool or absent (#25713)
## Problem

The OIDC callback checks `email_verified` via a Go type assertion
(`verifiedRaw.(bool)`). When an IdP returns the claim as a string
(`"false"`), a number, or omits it entirely, the assertion fails
silently and the email is implicitly treated as verified. Several real
IdPs (SAML-to-OIDC bridges, certain Azure AD B2C configurations) emit
string-typed booleans, making this reachable in practice.

## Fix

Add `coerceEmailVerified()` to handle `bool`, `string`
(`"true"`/`"false"`/`"1"`/`"0"` via `strconv.ParseBool`), `float64`,
`json.Number`, and `int`/`int64` variants. Rewrite the check to be
fail-closed: an absent claim, an unrecognized type, or any non-truthy
value is treated as unverified and rejected. The existing
`IgnoreEmailVerified` config option remains as an escape hatch.

Fixes https://linear.app/codercom/issue/PLAT-228

> Generated with [Coder Agents](https://coder.com) by @f0ssel

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

### Production code (`coderd/userauth.go`)
- Added `encoding/json` import
- Added `coerceEmailVerified(v interface{}) (verified bool, ok bool)`
helper near EOF
- Replaced the type-assertion block (lines ~1342-1363) with fail-closed
logic that uses `coerceEmailVerified`

### Unit tests (`coderd/userauth_internal_test.go`, new file)
- Table-driven test covering: `bool`, `string` (`"true"`, `"false"`,
`"1"`, `"0"`, `"TRUE"`, `"t"`, `"f"`, `"invalid"`, `""`), `json.Number`,
`float64`, `int`, `int64`, `nil`, `[]string{}`, `map[string]string{}`

### Integration tests (`coderd/userauth_test.go`,
`coderd/users_test.go`)
- Added 3 new test cases: `EmailVerifiedMissingIgnored` (200),
`EmailVerifiedAsStringTrue` (200), `EmailVerifiedAsStringFalse` (403)
- Updated existing test cases that omitted `email_verified` and expected
success to include `"email_verified": true`

### FakeIDP (`coderd/coderdtest/oidctest/idp.go`)
- `encodeClaims` now defaults `email_verified` to `true` (like `exp`,
`aud`, `iss`) so tests that don't care about the verification flow are
unaffected
</details>
2026-06-04 14:37:19 -04:00
Garrett DelfosseandCoder Agents 53d287a139 fix(coderd)!: restrict OIDC email fallback to first-time account linking (#25712)
## Problem

`findLinkedUser` in `coderd/userauth.go` falls back to email-based user
lookup when no `linked_id` match is found. This fallback was used for
**all logins**, not just first-time linking. An attacker who registers
the victim's email at the IdP (with a different OIDC subject) bypasses
the `linked_id` check and gets matched to the victim's Coder account.

Combined with the `email_verified` type assertion bypass (PLAT-228),
this creates a chained account-takeover vector.

## Fix

Restrict the email fallback in `findLinkedUser` so that when a user
found by email already has a `user_link` with a non-empty `linked_id`
that **differs** from the current login's `linked_id`, the function
returns no user. This blocks account takeover while preserving:

- **First-time linking**: No existing `user_link` exists, email fallback
works as before.
- **Legacy links**: Empty `linked_id` (pre-migration), email fallback
still works.
- **Normal logins**: Matching `linked_id` resolves via the primary path,
no fallback needed.

Also adds a `UpdateUserLinkedID` query to backfill `linked_id` on legacy
links (only when currently empty) during login, gradually migrating them
to the secure path.

The `findLinkedUser` signature now accepts `loginType` explicitly
instead of relying on `user.LoginType`, ensuring the correct link is
checked in the legacy lookup.

## Breaking change

Marked `release/breaking`. An account whose `user_link` already has a
populated `linked_id` that does not match the subject the IdP presents
will now be denied login (403) instead of silently resolving via the
email fallback. The most likely trigger is changing
`CODER_OIDC_ISSUER_URL` (the `linked_id` is `issuer||subject`), or two
identities sharing one email. Accounts with an empty (legacy)
`linked_id` are unaffected and are backfilled on their next login.

Fixes: https://linear.app/codercom/issue/PLAT-229

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

### Files changed

- `coderd/userauth.go`: Core fix in `findLinkedUser` + backfill logic in
`oauthLogin`
- `coderd/database/queries/user_links.sql`: New `UpdateUserLinkedID`
query
- `coderd/database/dbauthz/dbauthz.go`: Authorization for new query
(`ActionUpdate` on the user object, matching `InsertUserLink`)
- `coderd/userauth_test.go`: New OIDC and GitHub tests
- Generated files: `queries.sql.go`, `querier.go`, `dbmock.go`,
`querymetrics.go`

### New tests

- `TestUserOIDC/OIDCEmailFallbackBlockedByExistingLink`: Attacker with a
different `sub` but the same email is rejected (403) when the victim has
an existing link (covers signups enabled and disabled).
- `TestUserOIDC/OIDCFirstTimeLinkByEmailAllowed`: User created via
SCIM/API (no `user_link`) can still link via email on first OIDC login,
and the `linked_id` is populated.
- `TestUserOIDC/OIDCLegacyLinkBackfill`: User with empty `linked_id` can
login and their `linked_id` is backfilled with the correct value.
- `TestUserOIDC/OIDCEmailFallbackBlockedByIssuerChange`: Existing link
recorded under a previous issuer is rejected (403) after the issuer
changes (documents the breaking behavior).
- `TestUserOAuth2Github/EmailFallbackBlockedByExistingLink`: GitHub
attacker with a different user ID but the victim's email is rejected
(403).

</details>

> [!NOTE]
> This PR was authored by Coder Agents on behalf of @f0ssel.

---------

Co-authored-by: Coder Agents <agents@coder.com>
2026-06-04 14:36:56 -04:00
Garrett Delfosse 76bf462bbf fix(coderd): prevent user-admin from resetting owner password (#25709)
`PUT /api/v2/users/{user}/password` was protected only by
`ActionUpdatePersonal`, which the built-in `user-admin` role holds
site-wide. No guard prevented targeting an owner. The old-password check
is skipped for non-self resets, so a user-admin could reset any owner's
password and authenticate as them, gaining full deployment control.

Add an owner-role guard to `putUserPassword` that refuses password-reset
requests when the target holds the owner role unless the caller is also
an owner. This is modeled on the guard in `putUserStatus`, but differs
in that it conditionally allows owner-to-owner resets (whereas
`putUserStatus` blocks all suspension of owners regardless of caller).

Fixes https://linear.app/codercom/issue/PLAT-227

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

- Guard inserted after the `Authorize` check, before `httpapi.Read`
- `apiKey.UserID != user.ID` gates the check so self-password-change is
unaffected
- Acting user's roles fetched from DB to verify owner status (same
pattern as `putUserStatus`)
- Returns HTTP 400 consistent with sibling handler error style
- Two new test cases: `UserAdminCannotResetOwnerPassword`,
`OwnerCanResetOwnerPassword`

</details>

> Generated with [Coder Agents](https://coder.com) by @f0ssel
2026-06-04 14:36:25 -04:00
Andrew Aquino 6bd413163f fix(coderd): update references to workspace's include_deleted query param (#25826)
fixes DEVEX-206
2026-06-04 09:12:45 -07:00
Ethan 6366c380fe fix: correct user:* scope typo (#26049)
The wildcard entry in `externalLowLevel` was `"user.*"` (period) instead
of `"user:*"` (colon). Every other entry uses the `resource:action`
colon convention, and `parseLowLevelScope` rejects the period form, so
the wildcard was silently dropped from `ExternalScopeNames()` and could
not be requested via `coder tokens create --scope=user:*`.

Closes https://github.com/coder/coder/issues/25623
2026-06-05 01:14:20 +10:00
Michael Suchacz 502c5acca8 fix(coderd): preserve gateway model names (#26039)
OpenAI-compatible gateway providers such as OpenRouter require
slash-namespaced model IDs to reach the intended upstream model, but
native OpenAI routing strips those prefixes.

Preserve full model IDs for gateway provider types, reject
OpenRouter-like providers configured as native `openai` when a slash
model would be stripped, and validate chat model config changes under
the provider reference lock while still allowing unrelated edits to
existing configs.

Split from #26005.

> Mux created this PR on behalf of Mike.
2026-06-04 15:33:00 +02:00
Sas Swart c5631a853a feat(coderd/aibridged): add boundary correlation fields to RecordInterceptionRequest (#25884)
Add `optional string boundary_session_id` (field 15) and `optional int64
boundary_sequence_number` (field 16) to `RecordInterceptionRequest` in
the AI Bridge proto definition. Regenerate Go bindings. No behavior
change.

## Context

The [Gateway and Firewall Correlation
RFC](https://www.notion.so/coderhq/Gateway-and-Firewall-Correlation-RFC-31ad579be592803aa8b3d48348ccdde9)
defines a system for linking Agent Firewall (boundary) audit events with
AI Bridge interceptions so that admins can trace an LLM request back to
the exact network activity that produced it.

The correlation mechanism works as follows:

1. Each boundary process generates a session UUID on startup and assigns
a monotonically increasing sequence number to every audit event it
records.
2. When boundary proxies a request to AI Bridge, it injects
`X-Coder-Agent-Firewall-Session-Id` and
`X-Coder-Agent-Firewall-Sequence-Number` headers.
3. AI Bridge reads these headers, records them on the interception, and
strips them before forwarding to the upstream LLM provider.
4. The persisted session ID and sequence number allow the frontend to
discover which boundary session an interception belongs to, and to fetch
only the boundary audit events that occurred between any two
interceptions by filtering on the sequence number range.

This PR implements the first step: adding the proto fields that carry
the correlation data from AI Bridge to coderd's recording service.

## How these fields will be used

The two immediate downstream issues depend on these fields:

**AIGOV-260** adds `boundary_session_id UUID NULL` and
`boundary_sequence_number BIGINT NULL` columns to the
`aibridge_interceptions` database table, with a partial index on
`boundary_session_id`. The `RecordInterception` server handler
(`coderd/aibridgedserver/aibridgedserver.go`) will read the new proto
fields via `GetBoundarySessionId()` and `GetBoundarySequenceNumber()`
and pass them through to the database insert query.

**AIGOV-259** adds the capture-and-strip logic in the AI Bridge
interception processor (`aibridge/bridge.go`). It reads the
`X-Coder-Agent-Firewall-Session-Id` and
`X-Coder-Agent-Firewall-Sequence-Number` headers from the incoming
request, adds `BoundarySessionID *string` and `BoundarySequenceNumber
*int64` fields to the `InterceptionRecord` struct
(`aibridge/recorder/types.go`), and strips the headers before forwarding
upstream. The translator (`coderd/aibridged/translator.go`) will then
map these struct fields onto the proto fields added here.

Fixes https://linear.app/codercom/issue/AIGOV-252

> [!NOTE]
> This PR was generated by [Coder Agents](https://coder.com).
2026-06-04 11:19:57 +02:00
Ethan 3ab1323bc9 fix!: rename chat stream silence timeout error (#25973)
Renames the Agents chat stream-silence error from `startup_timeout` to
`stream_silence_timeout` now that the timeout applies to any gap between
provider stream parts, not just first-token startup.

Updates the SDK enum, generated API docs/types, chat error copy, and
Agents UI stories/status labels so the user-facing wording describes a
stalled provider response instead of startup delay.

> **Breaking change:** This is a very minor breaking change for the
Coder Agents API: the public chat error kind enum no longer includes
`startup_timeout`, so clients matching that specific value should handle
`stream_silence_timeout` instead.
2026-06-04 18:36:02 +10:00
Ethan becc858fa8 fix(coderd/x/chatd): retry provider stream cancellations (#26010)
Closes CODAGT-541.

## Problem

An Agents chat stream could die with a terminal `context cancelled`
error and surface to the user as a permanent chat failure, even when no
context in our process had actually been canceled. The cancellation was
a provider-returned error value (HTTP/2 RST_STREAM mid-body surfacing as
`context.Canceled` from Go's net/http2), not a real caller cancel.

The chain that produced the bug:

- fantasy passed the provider's `context.Canceled` through unchanged.
- `chaterror.Classify` short-circuited any `errors.Is(err,
context.Canceled)` (or `"context canceled"` text) as terminal generic,
before checking HTTP status codes or other retry signals.
- `chatretry.Retry` did not retry.
- The frontend rendered `type:"error"` and the chat was dead.

The same short-circuit also masked retryable 5xx responses whose
underlying transport error happened to wrap `context.Canceled`.

## Approach

`context.Canceled` has no inherent intent. The same error value can mean
a user pressing Stop, a server shutdown, the silence guard firing, or a
provider-side stream reset. The only layer that can disambiguate is the
one holding both the returned error and the caller context. That is
`chatretry`.

This PR centralizes the policy there and keeps `chaterror` context-free.

## Changes

`coderd/x/chatd/chaterror/classify.go`

- Add `ErrProviderTransportReset` sentinel to explicitly mark
provider-side stream cancellations.
- Remove the broad `context.Canceled` / `"context canceled"`
short-circuit so status codes and other retry signals can win.
- Classify `ErrProviderTransportReset` (with no status code) as a
retryable timeout.
- Keep a fallback that classifies bare `context.Canceled` as
terminal-generic when no other signal is present, so legitimate caller
cancels still terminate cleanly.

`coderd/x/chatd/chatretry/chatretry.go`

- Add `contextError(ctx)` that returns `context.Cause(ctx)` when set,
falling back to `ctx.Err()`, so caller-owned cancel causes
(`ErrInterrupted`, `errStreamSilenceTimeout`, server shutdown sentinels)
propagate cleanly out of the retry loop.
- Add `classifyProviderAttemptError(err)` that wraps a bare
`context.Canceled` in `ErrProviderTransportReset` and reclassifies.
Errors that already classify as retryable or carry a status code are
left alone.
- Restructure `Retry` so the policy is explicit and readable: check
caller cancellation before attempting, run the attempt, check caller
cancellation again before normalizing the provider error, then classify
and retry.

## End-to-end behavior

- Provider returns `context.Canceled` while caller context is healthy:
classified as a retryable timeout, retried, the user sees a brief
`type:"retry"` event and the chat continues.
- User presses Stop: `contextError(ctx)` returns `ErrInterrupted`. Retry
stops. `chatloop` flushes partial content and persists.
- Stream-silence guard fires: `attemptCtx` is canceled with
`errStreamSilenceTimeout`, `guardedStream` produces a classified
retryable error, retry proceeds normally on the still-alive parent.
- Server shutdown: parent context's cause propagates out, retry stops.
2026-06-04 12:52:37 +10:00
Jon Ayers 167ac7b879 feat: add nats experiment (#25703) 2026-06-03 15:37:19 -05:00
Steven Masley f1ebc42859 refactor(coderd/rbac): enumerate org-member and org-service-account perms (#25928)
`organization-member` was created from `allPermsExcept(...)`. This is changed to an explicit enumeration of capabilities.

- New resources no longer auto-grant to org members or service accounts.
- Adding one now requires an explicit decision in `coderd/rbac/roles.go`.
2026-06-03 08:14:11 -05:00
Paweł Banaszewski 96e3a64b12 feat: add AI Gateway coderd key CRUD endpoints (#25565)
Adds create, list and delete endpoints for AI Gateway keys.
Those keys are used to authenticate into Coderd.
All endpoints require Owner permission.
2026-06-03 13:50:33 +02:00
Mathias Fredriksson 7a84a851ce fix(coderd): subscribe to pubsub before accepting websocket in watchChats (#25663)
The watchChats handler called SubscribeWithErr after websocket.Accept,
creating a window where clients could trigger events before the
subscription was active. Move the subscription before the accept so
events accumulate in the pubsub internal queue and drain naturally
once the encoder is ready.

Fixes CODAGT-480
2026-06-03 13:18:57 +03:00
Mathias Fredriksson faf0add985 test(coderd/coderdtest/oidctest): scope IDP NotFound errors to IDP paths (#25892)
The FakeIDP mux.NotFound handler called t.Errorf for any unrecognized
HTTP request, failing the owning test. It also never wrote an HTTP
response, so the stale caller got a 200 with an empty body, hiding
the problem on the caller side.

When the IDP runs as a real HTTP server (WithServing), OS port reuse
across concurrent test binaries can route stale connections to the IDP
port. The source is enterprise provisionerd reconnects and DERP
clients from parallel tests whose coderd servers have shut down.

Check whether the NotFound request path starts with a known IDP route
prefix (/oauth2/, /.well-known/, /login/, /external-auth-validate/).
IDP paths: t.Errorf, logger.Error, and 404 response. Non-IDP paths:
t.Logf, logger.Warn, and 421 Misdirected Request response. Both
branches now return a proper HTTP error so the offending caller can be
traced.
2026-06-03 13:06:46 +03:00
Cian Johnston 8b058dc949 feat: add coderd_api_websocket_probes_total metric (#25012)
Relates to CODAGT-115

Adds metric `coderd_api_websocket_probes_total`. Every successful
heartbeat for a given path will increment the metric.

Comparing this with `coderd_api_concurrent_websockets` will give an
indication of how many websocket connections are open but in a 'wedged'
state (when heartbeats stopped versus when we closed the connection).
2026-06-03 10:46:07 +01:00
Michael Suchacz 7703e7a26e fix: preserve AI provider preset types (#25925)
> Mux created this PR on behalf of Mike.

AI provider creation previously collapsed OpenAI-compatible presets like
Google and generic OpenAI-compatible providers to `openai`, which lost
the backend provider discriminator.

Preserve selected provider types in the create payload, keep explicit
stored types authoritative when reconstructing edit form values, and add
frontend plus backend regressions for the supported preset types.
2026-06-03 09:24:08 +02:00
Jon Ayers ec19bc41d8 fix: escape appearance values in HTML output (#25804) 2026-06-02 13:19:16 -05:00
George K 2f011fd2a3 fix: reject oversized and invalid zip uploads (#25877)
Enforce aggregate limits when converting uploaded ZIP archives to tar
so compressed inputs cannot expand without bound in memory.

Also treat malformed ZIP entry metadata and content mismatches as
client errors during conversion, returning 400 for invalid archives and
413 when expanded tar output exceeds the upload limit.

Ref: https://linear.app/codercom/issue/PLAT-274/zip-upload-decompressed-without-aggregate-size-limit-sec-103
2026-06-02 10:11:49 -07:00
Zach 170c33a475 feat: encrypt gitsshkeys.private_key at rest via dbcrypt (#25872)
Adds an optional dbcrypt wrapper around gitsshkeys.private_key. The
column is encrypted on insert and update through enterprise/dbcrypt when
external token encryption is configured, and decrypted on read.

A new private_key_key_id column references
dbcrypt_keys(active_key_digest) so revocation safety is enforced by the
existing foreign key. Rows with a NULL key_id stay plaintext and remain
readable. Existing plaintext rows can be backfilled by running `coder
server dbcrypt rotate`.

Generated with assistance from Coder Agents.
2026-06-02 08:36:01 -06:00
Ethan 9fe75587ae fix: forward user-uploaded PDFs to Anthropic and Bedrock (#25946)
Previously, user-uploaded PDFs were silently dropped by fantasy's
Anthropic provider adapter, so Claude (direct or via Bedrock) only saw
the user's text and replied as if no document had been attached. Other
providers (OpenAI, Gemini, OpenRouter, Vercel) were unaffected.

Bumps `coder/fantasy` past
[coder/fantasy#37](https://github.com/coder/fantasy/pull/37)
(cherry-pick of upstream
[charmbracelet/fantasy#197](https://github.com/charmbracelet/fantasy/pull/197)),
which emits an Anthropic `document` content block with a base64 PDF
source for `fantasy.FilePart{MediaType: "application/pdf"}` and counts
`OfDocument` as user-visible so a PDF-only user message is no longer
culled as empty.

Adds a regression test
(`TestModelFromConfig_AnthropicPDFFilePartReachesProvider`) that drives
a `fantasy.FilePart` through the real Anthropic provider against a
`chattest.NewAnthropic` stub and asserts the outbound request contains a
base64 document block. The test was verified to fail on the previous
fantasy pin (the request leaves with zero messages and `Generate`
returns EOF) and pass on the new one.

Manually verified end-to-end with `./scripts/develop.sh`: uploading a
PDF to a Claude-backed Coder Agents chat now lets the model read it.

Closes CODAGT-540
2026-06-03 00:16:01 +10:00
Steven Masley d2697dc5b0 test: data race for TestAIGatewayKeysTableConstraints - shadowed error (#25980)
Closes https://github.com/coder/coder/issues/25979

error is shadowed and shared by parallel subtests
2026-06-02 14:02:29 +00:00