Commit Graph
12780 Commits
Author SHA1 Message Date
Kyle Carberry 30a63009aa fix(agents): persist right panel open/closed state to localStorage (#22906)
Removes the auto-open/close behavior that would force the right-side
panel open whenever diff status or git repository data appeared.
Instead, the panel's visibility is now persisted via the
`agents.right-panel-open` localStorage key (matching the existing
`agents.right-panel-width` pattern for the panel width).

This gives users a consistent UX when switching between chats — the
panel stays in whatever state they last set it to.

## Changes

- **Removed** two auto-open blocks in `AgentDetailView` that tracked
`prevHasDiffStatus` / `prevHasGitRepos` and forced `showSidebarPanel =
true`
- **Added** `localStorage` persistence for the panel open/closed state
under key `agents.right-panel-open`
- Initial state is read from localStorage on mount (defaults to closed)
- Every toggle/close writes through to localStorage via
`handleSetShowSidebarPanel`
- Panel width was already persisted via `agents.right-panel-width` in
`RightPanel.tsx` — no changes needed there
2026-03-10 15:43:55 +00:00
Matt Vollmer f22450f29b docs: add early access state to agent child pages and fix video URL (#22908)
## Changes

- Add `"state": ["early access"]` to all child pages under Coder Agents
in `docs/manifest.json` (Architecture, Models, Platform Controls, Early
Access).
- Point the Coder Agents video `<source>` directly at
`raw.githubusercontent.com` instead of the `github.com/blob/` URL with
`?raw=true`.
2026-03-10 11:41:21 -04:00
Kyle Carberry 01f25dd9ae fix(agents): write WebSocket cache updates to infinite query key (#22905)
## Problem

Chat sidebar title/status updates from WebSocket events don't take
effect immediately — they only appear after a full server re-fetch.

**Root cause:** All `setQueryData(chatsKey, ...)` calls write to cache
key `["chats"]`, but the rendered chat list reads from
`useInfiniteQuery(infiniteChats())` on key `["chats", undefined]`.
TanStack Query v5 `setQueryData` requires an exact key match, so these
are different cache entries.

WebSocket events (`title_change`, `status_change`, `created`, `deleted`)
and `updateSidebarChat` were all updating a cache entry that nothing
rendered from. The only way changes reached the UI was via
`invalidateQueries` (which prefix-matches), triggering a full server
re-fetch. This caused visible flicker when the re-fetch raced with
subsequent events.

## Fix

Add `updateInfiniteChatsCache()` helper that uses `setQueriesData({
queryKey: chatsKey })` — this **prefix-matches** all infinite query
variants (`["chats", undefined]`, `["chats", { archived: true }]`, etc.)
and correctly updates the `{ pages, pageParams }` structure.

Replace all direct `setQueryData(chatsKey, ...)` calls:
- WebSocket handler in `AgentsPage.tsx` (deleted, created, title_change,
status_change events)
- `updateSidebarChat` in `ChatContext.ts`
- Archive/unarchive optimistic updates in `chats.ts`

Also adds `readInfiniteChatsCache()` helper for reading the flat chat
list from the infinite query (used by the chime status lookup).

## Files changed

| File | Change |
|------|--------|
| `site/src/api/queries/chats.ts` | Added helpers, updated
archive/unarchive mutations |
| `site/src/pages/AgentsPage/AgentsPage.tsx` | WebSocket handler uses
new helpers |
| `site/src/pages/AgentsPage/AgentDetail/ChatContext.ts` |
`updateSidebarChat` uses new helper |
| `site/src/api/queries/chats.test.ts` | Tests seed/read infinite query
format |
| `site/src/pages/AgentsPage/AgentDetail/ChatContext.test.tsx` | Tests
seed/read infinite query format |
2026-03-10 15:24:46 +00:00
Kyle Carberry b6d1a11c58 feat(chatd): add user-level custom prompt for agent chats (#22896)
Adds a user-level custom prompt to the database.

I'll be doing a follow-up for the UI, as we currently do not have
user-level settings (it's just admin). I'll also make it very obvious
for chats where there is a user-level prompt, but I don't know how yet.
2026-03-10 11:17:52 -04:00
Danielle MaywoodandKyle Carberry 6489d6f714 feat(chatd): use last assistant message as push notification summary (#22671)
Instead of the static 'Agent has finished running.' text, extract a
summary from the last assistant message to give users meaningful context
about what the agent accomplished. Falls back to the static text if no
suitable message is found.

Co-authored-by: Kyle Carberry <kyle@carberry.com>
2026-03-10 15:14:15 +00:00
Cian Johnston 12bdbc693f docs: remove experimental chat API from generated docs (#22897)
The chat API is experimental (behind `ExperimentAgents`) and not ready
for public documentation yet. This removes swagger annotations from the
chat handlers so they no longer appear in the generated API reference at
https://coder.com/docs/reference/api/chats.

## Changes
- Remove `@swagger` annotations from 5 chat handlers in
`coderd/chats.go`
- Regenerate `coderd/apidoc/swagger.json` and `docs.go`
- Delete `docs/reference/api/chats.md`
- Remove Chats entry from `docs/manifest.json`
2026-03-10 15:04:08 +00:00
Michael Suchacz f5e5bd2d64 chore(dogfood): bump mux to 1.4.0 (#22899)
## Summary
- bump the dogfood template Mux module from 1.3.1 to 1.4.0

## Validation
- terraform -chdir=dogfood/coder validate
- terraform fmt -check dogfood/coder/main.tf
2026-03-10 15:54:58 +01:00
Kyle Carberry fee5cc5e5b fix(chatd): fix flaky TestCloseDuringShutdownContextCanceledShouldRetryOnNewReplica (#22893)
Fixes https://github.com/coder/internal/issues/1371

## Root causes

Two independent races cause this test to flake at ~2–3/1000:

### 1. Title-generation requests racing with the streaming request
counter

`maybeGenerateChatTitle` fires in a `context.WithoutCancel` goroutine
(line 2130) and makes a **non-streaming** request to the mock OpenAI
handler. The test handler was not filtering by request type, so these
title requests incremented the `requestCount` atomic — throwing off the
coordination logic that uses `requestCount == 1` to identify the first
streaming request and hold it open until shutdown.

**Fix:** Guard the test handler to return a canned response for
non-streaming requests before touching `requestCount`.

### 2. Phantom acquire: `AcquireChat` commits in Postgres but Go sees
`context.Canceled`

During `Close()`, the main loop's `select` can randomly pick
`acquireTicker.C` over `ctx.Done()` (Go spec: when multiple cases are
ready, one is chosen uniformly at random). This calls `processOnce(ctx)`
with an already-canceled context.

In the pq driver, `QueryContext` does **not** check `ctx.Err()` up
front. Instead it calls `watchCancel(ctx)` which spawns a goroutine
monitoring `ctx.Done()`, then sends the query on the existing
connection. When `ctx` is already canceled, a race ensues:

- **pq's watchCancel goroutine** immediately sees `<-done`, opens a
  *new* TCP connection to Postgres, and sends a cancel request.
- **The query** is sent concurrently on the existing connection.

Because the `AcquireChat` UPDATE is fast (sub-millisecond, single row
with `SKIP LOCKED`), it often commits before the cancel arrives via the
second connection. Meanwhile in `database/sql`, `initContextClose`
spawns an `awaitDone` goroutine that fires immediately (context is
already canceled), stores `contextDone`, and calls `rs.close(ctx.Err())`
— which races with `Row.Scan` → `rows.Next()`. If `awaitDone` wins,
`Next()` sees `contextDone` is set and returns false, causing Scan to
return `context.Canceled` (or `ErrNoRows`).

**Result:** Postgres committed the UPDATE (chat is now `running` with
serverA's worker ID), but Go sees an error and never spawns a goroutine
to process it. The chat is stuck as `running` with no worker.

If the previous `processChat` cleanup already set the chat back to
`pending`, this phantom acquire flips it back to `running` — which is
exactly what the debug logs showed: after `Close()` returns, the DB
shows `status=running` with serverA's worker ID.

**Fix:** Three guards in `processOnce`:

1. Early `ctx.Err()` check — catches the common case where `select`
   picked the ticker after cancellation.
2. `context.WithoutCancel(ctx)` for `AcquireChat` — prevents the pq
   `watchCancel` race entirely, ensuring the driver sees the query
   result if Postgres executed it.
3. Post-acquire `ctx.Err()` check — if the context was canceled while
   `AcquireChat` ran (or between the early check and the call),
   immediately release the chat back to `pending`.

## Verification

Passes 2000/2000 iterations (previously flaked at ~2–3/1000):

```
go test -run "TestCloseDuringShutdownContextCanceledShouldRetryOnNewReplica" \
  -count=2000 -timeout 1800s -failfast ./coderd/chatd/
```
2026-03-10 14:22:39 +00:00
Matt Vollmer 72fb0cd554 docs: add Early Access page under Coder Agents (#22872)
Adds a new child page at `/docs/ai-coder/agents/early-access` describing
the Coder Agents Early Access, including what it includes, what it does
not include, feature scope, licensing, and how to provide feedback.
2026-03-10 10:22:25 -04:00
Kyle Carberry ba764a24ea fix(site): upgrade @pierre/diffs to 1.1.0-beta.19 (#22895)
Fixes a race condition in `DiffHunksRenderer` where a stale async
highlight callback overwrites the render cache with an old diff, causing
a hunk count mismatch:

```
DiffHunksRenderer.renderHunks: lineHunk doesn't exist
```

## Root cause

The `DiffHunksRenderer` in `@pierre/diffs@1.0.11` caches highlighted AST
results keyed by diff object reference. When the shiki highlighter isn't
fully loaded, it fires `asyncHighlight(diff)` which captures the current
diff in a closure. If the diff changes before that promise resolves,
`onHighlightSuccess` unconditionally overwrites `renderCache` with the
stale diff/result pair. The subsequent `rerender()` then iterates the
new diff's hunks against the old result's `code.hunks` array, crashing
at an out-of-bounds index.

## Fix

Upgrades `@pierre/diffs` from `1.0.11` to `1.1.0-beta.19`, which
completely refactors the rendering pipeline:

- Replaces the per-hunk `code.hunks[hunkIndex]` lookup with flat
`additionLines`/`deletionLines` arrays indexed directly by line index
- Uses a new `iterateOverDiff` callback pattern instead of the
`renderHunks` method
- The `lineHunk doesn't exist` error is gone from the codebase entirely

The only code change on our side is adapting `extractDiffContent()` in
`FilesChangedPanel.tsx` to the new `ChangeContent`/`ContextContent`
types where `deletions`, `additions`, and `lines` are now counts with
index pointers into top-level
`FileDiffMetadata.deletionLines`/`additionLines` arrays.
2026-03-10 14:18:42 +00:00
Kyle Carberry 8c70170ee7 fix(site): polish agent UI styling (#22889)
Fixes several small UI issues on the agent detail and sidebar pages:

- **Sidebar lines changed indicator**: removed monospace font, matched
styling to model text (text-[13px] leading-4)
- **Git panel**: always shown instead of "No panels available" fallback
- **Git tab active state**: added `text-content-primary` so the tab
looks selected
- **Attachment button**: switched to `subtle` variant (lighter color, no
border)
- **Context indicator / attachment button**: matched sizes (`size-7`
container, `size-icon-sm` icon) and swapped positions
2026-03-10 14:10:44 +00:00
Kyle Carberry e18ce505ec feat(coderd): add pagination to chat list endpoint (#22887)
Adds offset and cursor-based pagination to the `GET
/api/experimental/chats` endpoint, following the exact same patterns
used by `GetUsers` and `GetTemplateVersionsByTemplateID`.

## Changes

### Database
- Add `after_id`, `offset_opt`, `limit_opt` params to
`GetChatsByOwnerID` SQL query
- Use composite `(updated_at, id) DESC` cursor for stable, deterministic
pagination
- Add migration with composite index on `chats (owner_id, updated_at
DESC, id DESC)`

### Backend
- Use `ParsePagination()` in `listChats` handler (matches `users.go`
pattern)
- Add `Pagination` field to `ListChatsOptions` SDK struct

### Frontend
- Add `infiniteChats()` query factory using `useInfiniteQuery` with
offset-based page params (same pattern as `infiniteWorkspaceBuilds`)
- Update `AgentsPage` to use `useInfiniteQuery`
- Add "Show more" button at the bottom of the agents sidebar (matches
`HistorySidebar` pattern)
- Keep existing `chats()` query for non-paginated uses (e.g., parent
chat lookup in `AgentDetail`)

### Tests
- Add `TestListChats/Pagination` covering `limit`, `after_id` cursor,
`offset`, and no-limit behavior
2026-03-10 13:55:33 +00:00
Mathias Fredriksson beed379b1d fix(agent): handle ignored filepath.Walk error in filefinder (#22853)
Log a warning when filepath.Walk fails during recursive directory
watching instead of silently discarding the error.
2026-03-10 15:43:24 +02:00
Danny Kopping 2948400aef fix(cli): skip CODER_SESSION_TOKEN check when --use-token-as-session is set (#22888)
_Disclaimer: implemented with Opus 4.6 and Coder Agents._

Follow-up to #22879.

## Problem

The `CODER_SESSION_TOKEN` guard added in #22879 blocks `coder login`
unconditionally when the env var is set. This conflicts with
`--use-token-as-session`, which intentionally uses the provided token
(including from the env var) directly as the session token.

## Fix

Add `&& !useTokenForSession` to the check so that `coder login
--use-token-as-session` still works when `CODER_SESSION_TOKEN` is set.

## Testing

Added `TestLogin/SessionTokenEnvVarWithUseTokenAsSession` — sets the env
var with a valid token and passes `--use-token-as-session`, verifying
login succeeds.

---------

Signed-off-by: Danny Kopping <danny@coder.com>
2026-03-10 15:40:54 +02:00
Kyle Carberry f35b99a4fa fix(chatd): preserve context.Canceled in persistStep during shutdown (#22890)
## Problem

When a chat worker shuts down gracefully (e.g. Kubernetes pod SIGTERM)
while a tool is executing (like `wait_agent` polling for a subagent),
the chat gets stuck in `waiting` status forever — no other worker will
pick it up.

### Root Cause

`persistStep` in `chatd.go` unconditionally returned
`chatloop.ErrInterrupted` for **any** canceled context:

```go
if persistCtx.Err() != nil {
    return chatloop.ErrInterrupted  // BUG: doesn't check WHY the context was canceled
}
```

During shutdown, the context cause is `context.Canceled` (not
`ErrInterrupted`). But because `persistStep` returned `ErrInterrupted`,
the error handling in `processChat` hit the `ErrInterrupted` check first
(line 2011) and set status to `waiting` — the `isShutdownCancellation`
check (line 2017) was never reached:

```go
// Checked FIRST — matches because persistStep returned ErrInterrupted
if errors.Is(err, chatloop.ErrInterrupted) {
    status = database.ChatStatusWaiting  // Stuck forever
    return
}
// NEVER REACHED during shutdown
if isShutdownCancellation(ctx, chatCtx, err) {
    status = database.ChatStatusPending  // Would have been correct
    return
}
```

### Trigger scenario (from production logs)

1. Chat spawns a subagent via `spawn_agent`, then calls `wait_agent`
2. `wait_agent` blocks in `awaitSubagentCompletion` polling loop
3. Worker pod receives SIGTERM → `Close()` cancels server context
4. Context cancellation propagates to `awaitSubagentCompletion` →
returns `context.Canceled`
5. Tool execution completes, `persistStep` is called with canceled
context
6. `persistStep` returns `ErrInterrupted` (wrong!) → status set to
`waiting` (stuck!)

## Fix

Check `context.Cause()` before deciding which error to return:

```go
if persistCtx.Err() != nil {
    if errors.Is(context.Cause(persistCtx), chatloop.ErrInterrupted) {
        return chatloop.ErrInterrupted  // Intentional interruption
    }
    return persistCtx.Err()  // Shutdown → context.Canceled
}
```

This preserves `context.Canceled` for shutdown, allowing
`isShutdownCancellation` to match and set status to `pending` so another
worker retries the chat.

## Test

Added `TestRun_ShutdownDuringToolExecutionReturnsContextCanceled` which:
1. Streams a tool call to a blocking tool (simulating `wait_agent`)
2. Cancels the server context (simulating shutdown) while the tool
blocks
3. Verifies `Run` returns `context.Canceled`, NOT `ErrInterrupted`
2026-03-10 13:01:45 +00:00
Kyle Carberry b898e45ec4 feat(site): rewrite localhost URLs in agent chat to port-forward links (#22891)
Uses streamdown's built-in `urlTransform` prop to intercept
`http://localhost:PORT` URLs in agent chat messages and rewrite them to
port-forwarded workspace URLs.

When the agent outputs a bare URL like `http://localhost:3000` or a
markdown link like `[app](http://localhost:8080/path)`, the URL is
rewritten to the workspace's port-forward subdomain (e.g.
`https://3000--agent--workspace--user.wildcard.host`). This makes links
clickable directly from the chat without manual port-forwarding.

## How it works

The transform is built in `AgentDetail` where workspace and proxy
context are available, then threaded as an optional prop through the
component tree:

```
AgentDetail → AgentDetailView → AgentDetailTimeline → ConversationTimeline → Response → Streamdown
```

- Uses streamdown's first-class `urlTransform` API — no monkey-patching
or rehype plugins
- Reuses the existing `portForwardURL()` utility from
`utils/portForward`
- Matches the same localhost detection as the terminal page
(`localhost`, `127.0.0.1`, `0.0.0.0`)
- Preserves pathname and search params
- Gracefully degrades: when any required context is missing (no
workspace, no wildcard proxy host), URLs pass through unchanged

## What gets transformed

| Markdown input | Transformed? |
|---|---|
| `http://localhost:8080` (bare URL, auto-linked by remark-gfm) | Yes |
| `[my app](http://localhost:3000/path)` (explicit link) | Yes |
| `\`http://localhost:8080\`` (inline code) | No (correct — code spans
are literal) |
| `https://example.com` (non-localhost) | No |
2026-03-10 12:57:59 +00:00
Danielle Maywood d61772dc52 refactor(site): separate AgentsPage and AgentDetail into container/view pairs (#22812) 2026-03-10 12:09:48 +00:00
Cian Johnston c933ddcffd fix(agents): persist system prompt server-side instead of localStorage (#22857)
## Problem

The Admin → Agents → System Prompt textarea saved only to the browser's
`localStorage`. The value was never sent to the backend, never stored in
the database, and never injected into chats. Entering text, clicking
Save, and refreshing the page showed no changes — the prompt was
effectively a no-op.

## Root Cause

Three disconnected layers:
1. **Frontend** wrote to `localStorage`, never called an API.
2. **`handleCreateChat`** never read `savedSystemPrompt`.
3. **Backend** hardcoded `chatd.DefaultSystemPrompt` on every chat
creation — no field in `CreateChatRequest` accepted a custom prompt.

## Changes

### Database
- Added `GetChatSystemPrompt` / `UpsertChatSystemPrompt` queries on the
existing `site_configs` table (no migration needed).

### API
- `GET /api/experimental/chats/system-prompt` — returns the configured
prompt (any authenticated user).
- `PUT /api/experimental/chats/system-prompt` — sets the prompt
(admin-only, `rbac: deployment_config update`).
- Input validation: max 32 KiB prompt length.

### Backend
- `resolvedChatSystemPrompt(ctx)` checks for a custom prompt in the DB,
falls back to `chatd.DefaultSystemPrompt` when empty/unset.
- Logs a warning on DB errors instead of silently swallowing them.
- Replaced the hardcoded `defaultChatSystemPrompt()` call in chat
creation.

### Frontend
- Replaced `localStorage` read/write with React Query
`useQuery`/`useMutation` backed by the new endpoints.
- Fixed `useEffect` draft sync to avoid clobbering in-progress user
edits on refetch.
- Added `try/catch` error handling on save (draft stays dirty for
retry).
- Save button disabled during mutation (`isSavingSystemPrompt`).
- Query key follows kebab-case convention (`chat-system-prompt`).

### UX
- Added hint: "When empty, the built-in default prompt is used."

### Tests
- `TestChatSystemPrompt`: GET returns empty when unset, admin can set,
non-admin gets 403.
- dbauthz `TestMethodTestSuite` coverage for both new querier methods.
2026-03-10 11:46:53 +00:00
Atif Ali a21f00d250 chore(ci): tighten permissions for AI workflows (#22471) 2026-03-10 16:43:36 +05:00
Mathias Fredriksson 3167908358 fix(site): fix chat input button icon sizing and centering (#22882)
The Button icon variant applies [&>svg]:size-icon-sm (18px) and
the base applies [&>svg]:p-0.5, both of which silently override
h-*/w-* set directly on child SVGs. This caused the stop icon to
render at 18px instead of 12px and the send arrow to shift
off-center due to uncleared padding.

Pin each icon size via !important on the parent className so the
values are deterministic regardless of Tailwind class order:

- Attach: !size-icon-sm (18px, unchanged visual)
- Stop: !size-3 (12px, matches original intent)
- Send: !size-5 (20px, matches prior visual after padding)

Add Streaming and StreamingInterruptPending stories for the stop
button.
2026-03-10 12:57:08 +02:00
Hugo Dutka 45f62d1487 fix(chatd): update the spawn_agent tool description (#22880)
I keep running into the same couple of issues with subagents:

- when I request code analysis, the main agent tends to spawn subagents
to read files and output them verbatim to the main chat
- when I request to implement a feature, the main agent often spawns
subagents that edit the same files and conflict with one another,
reverting each other's changes.

This PR updates the `spawn_agent` tool description to mitigate those
issues.
2026-03-10 11:46:50 +01:00
Danielle Maywood b850d40db8 fix(site): remove redundant success toasts from agents feature (#22884) 2026-03-10 10:32:27 +00:00
Mathias Fredriksson 73bf8478d8 fix(cli): fix flaky TestGitSSH/Local_SSH_Keys on Windows CI (#22883)
The `TestGitSSH/Local_SSH_Keys` test was flaking on Windows CI with a
context deadline exceeded error when calling `client.GitSSHKey(ctx)`.

Two issues contributed to the flake:

1. `prepareTestGitSSH` called `coderdtest.AwaitWorkspaceAgents` without
   passing the caller's context. This created a separate internal 25s
   timeout, wasting time budget independently of the setup context.
   Changed to use `NewWorkspaceAgentWaiter(...).WithContext(ctx).Wait()`
   so the agent wait shares the caller's timeout.

2. The `Local SSH Keys` subtest used `WaitLong` (25s) for its setup
   context, but this subtest does more work than `Dial` (runs the
   command twice). Bumped to `WaitSuperLong` (60s) to give slow
   Windows CI runners enough time.

Fixes coder/internal#770
2026-03-10 12:12:15 +02:00
Mathias Fredriksson 41c505f03b fix(cli): handle ignored errors in ssh and scaletest commands (#22852)
Handle errors that were previously assigned to blank identifiers in the
`cli/` package.

- ssh.go: Log ExistsViaCoderConnect DNS lookup error at debug level
  instead of silently discarding it. Fallthrough behavior preserved.
- exp_scaletest_llmmock.go: Log srv.Stop() error via the existing
  logger instead of discarding it.
2026-03-10 12:08:40 +02:00
Mathias Fredriksson abdfadf8cb build(Makefile): fix lint/go recipe by using bash subshell (#22874)
The `lint/go` recipe used `$(shell)` inside a recipe to extract the
golangci-lint version. When `MAKE_TIMED=1` (set by pre-commit/pre-push),
make expands `.SHELLFLAGS = $@ -ceu` for `$(shell)` calls, passing the
target name as the first argument to `timed-shell.sh`. Since the target
name doesn't start with `-`, the timing code path runs and its banner
output contaminates the captured value, causing intermittent failures:

```
bash: line 3: lint/go: No such file or directory
```

Replace with bash command substitution (`$$()`), which is the correct
approach under `.ONESHELL` and avoids the `SHELL`/`.SHELLFLAGS`
interaction entirely. Also replaces deprecated `egrep` with `grep -oE`.
2026-03-10 12:07:44 +02:00
Danny Kopping d936a99e6b fix(cli): error when CODER_SESSION_TOKEN env var is set during login (#22879)
_Disclaimer: created with Opus 4.6 and Coder Agents._

## Problem

When `CODER_SESSION_TOKEN` is set as an environment variable with an
invalid value, `coder login` fails with a confusing error:

```
error: Trace=[create api key: ]
You are signed out or your session has expired. Please sign in again to continue.
Suggestion: Try logging in using 'coder login'.
```

The suggestion to run `coder login` is what the user just did, making it
circular and unhelpful.

## Root cause

The `--token` flag is mapped to `CODER_SESSION_TOKEN` via serpent. When
the env var is set, `coder login` picks it up as the session token and
tries to use it to create a new API key, which fails because the token
is invalid. Even if login were to succeed and write a new token to disk,
subsequent commands would still use the env var (which takes precedence
over the on-disk token), so the user would remain stuck.

## Fix

Before attempting login, check if `CODER_SESSION_TOKEN` is set in the
environment. If so, return a clear error telling the user to unset it:

```
the environment variable CODER_SESSION_TOKEN is set, which takes precedence
over the session token stored on disk. Please unset it and try again.

    unset CODER_SESSION_TOKEN
```

## Testing

Added `TestLogin/SessionTokenEnvVar` that verifies the error is returned
when the env var is set.
2026-03-10 09:41:05 +00:00
Zach 14341edfc2 fix(cli): fix coder login token failing without --url flag (#22742)
Previously `coder login token` didn't load the server URL from config,
so it always required --url or CODER_URL when using the keyring to store
the session token. This command would only print out the token when
already logged in to a deployment and file storage is used to store the
session token (keyring is the default on Windows/macOS). It would also
print out an incorrect token when --url was specified and the session
token stored on disk was for a different deployment that the user logged
into.

This change fixes all of these issues, and also errors out when using
session token file storage with a `--url` argument that doesn't match
the stored config URL, since the file only stores one token and would
silently return the wrong one.

See https://github.com/coder/coder/issues/22733 for a table of the
before/after behaviors.
2026-03-10 08:57:27 +01:00
Jon Ayers e7ea649dc2 fix: optimize GetProvisionerJobsByIDsWithQueuePosition query (#22724) 2026-03-09 16:47:02 -05:00
Mathias Fredriksson 56960585af build(Makefile): add per-target timing via SHELL wrapper (#22862)
pre-commit and pre-push only reported total elapsed time at the end,
making it hard to identify which jobs are slow.

Add a `MAKE_TIMED=1` mode that replaces `SHELL` with a wrapper
(`scripts/lib/timed-shell.sh`) to print wall-clock time for each
recipe. pre-commit and pre-push enable this on their sub-makes.

Ad-hoc use: `make MAKE_TIMED=1 test`
2026-03-09 23:07:33 +02:00
Cian Johnston f07e266904 fix(coderd): use dbtime.Now() for tailnet telemetry timestamps (#22861)
Fixes a flaky test (`TestUserTailnetTelemetry/invalid_header`) caused by
sub-microsecond precision mismatch between `time.Now()` calls on
Windows.

The server used `time.Now()` (nanosecond precision) for `ConnectedAt`
and `DisconnectedAt`, while the test compared against its own
`time.Now()`. On Windows, wall-clock jitter can cause the server
timestamp to appear slightly before the test's `predialTime`.

Switch to `dbtime.Now()` which rounds to microsecond precision (matching
Postgres), consistent with all other timestamps in `workspaceagents.go`.

Relates to: https://github.com/coder/internal/issues/1390
2026-03-09 20:37:05 +00:00
Mathias Fredriksson 9bc884d597 docs(docs/ai-coder): upgrade Codex to full resume support (#22594)
The codex registry module v4.2.0 wires `enable_state_persistence`
through to agentapi, completing session resume support. Combined with
the `--type codex` flag added in v4.1.2, Codex now fully preserves
conversation context across pause and resume cycles.

Refs coder/registry#783
Refs coder/registry#785
2026-03-09 21:41:16 +02:00
Mathias Fredriksson f46692531f fix(site/e2e): increase webServer timeout to 120s (#22731)
The Playwright e2e `webServer` starts the Coder server via
`go run -tags embed`, which must compile before serving. The default 60s
timeout leaves no margin when the CI runner is slow.

Failed run:
https://github.com/coder/coder/actions/runs/22782592241/job/66091950715

Successful run:
https://github.com/coder/coder/actions/runs/22782107623/job/66090828826

The server started and printed its banner, but with only ~4s left on the
clock the health check (`/api/v2/deployment/config`) could not complete
before the timeout fired. The same ~2x slowdown shows in the
`make site/e2e/bin/coder` step (45s vs 67s), confirming this is runner
performance variability.

Increase timeout to 120s.

Refs #22727
2026-03-09 19:06:45 +00:00
Mathias Fredriksson 6e9e39a4e0 fix(agent/reaper): stop reaper goroutine in tests to prevent ECHILD race (#22844)
Each ForkReap call started a reap.ReapChildren goroutine that never
stopped (done=nil). Goroutines accumulated across subtests, racing to 
call Wait4(-1, WNOHANG) and stealing the child's wait status before 
ForkReap's Wait4(pid) could collect it.

Add a WithDone option to pass the done channel through to ReapChildren,
and use it in tests via a withDone(t) helper.
2026-03-09 17:34:44 +00:00
Mathias Fredriksson 1a2eea5e76 build(Makefile): harden make pre-push (#22849)
- Fix dead docker pull retry loop (Make ate bash expansions)
- Make test-postgres-docker idempotent so Phase 2 stops restarting it
  mid-test
- Run migrate-ci at recipe time, not parse time
- Install Playwright browsers before e2e tests
- Set test timeout to 20m, 5m shy of CI's 25m job limit
- Cap parallelism at nproc/4 via PARALLEL_JOBS
- Add phase banners and elapsed time
2026-03-09 17:26:34 +00:00
Mathias Fredriksson 9e7125f852 fix(scripts): handle ignored enc.Encode error in telemetry server (#22855)
Check the `json.Encoder.Encode` error and print to stderr.

Part of the effort to enable `errcheck.check-blank` in golangci-lint.
2026-03-09 19:03:06 +02:00
Atif Ali e6983648aa chore: add Linear release integration workflow (#22310) 2026-03-09 21:32:06 +05:00
Kyle Carberry 47846c0ee4 fix(site): inject permissions and organizations metadata to eliminate loading spinners (#22741)
## Problem

Two network requests were blocking the initial page render with
fullscreen `<Loader fullscreen />` spinners:

1. **`POST /api/v2/authcheck`** (permissions) — blocked in `RequireAuth`
via `AuthProvider.isLoading`
2. **`GET /api/v2/organizations`** — blocked in `DashboardProvider`

All other bootstrap queries (`user`, `entitlements`, `appearance`,
`experiments`, `build-info`, `regions`) already used server-side
metadata injection via `index.html` meta tags and resolved instantly.
These two did not.

## Solution

Follow the existing `cachedQuery` + `<meta>` tag pattern to inject both
datasets server-side:

### Server-side (`site/site.go`)
- Add `Permissions` and `Organizations` fields to `htmlState`
- Fetch organizations via `GetOrganizationsByUserID` in parallel with
existing queries
- Evaluate all `permissionChecks` using the RBAC authorizer directly
- Inject results as HTML-escaped JSON into `<meta>` tags

### Frontend
- Register `permissions` and `organizations` in `useEmbeddedMetadata`
- Update `checkAuthorization()` to accept optional metadata and use
`disabledRefetchOptions` when available
- Update `organizations()` to accept optional metadata and use
`cachedQuery` when available
- Wire metadata through `AuthProvider` and `DashboardProvider`

### Note
The Go `permissionChecks` map in `site/site.go` mirrors
`site/src/modules/permissions/index.ts` and must be kept in sync.
2026-03-09 16:12:04 +00:00
Danielle Maywood ff715c9f4c fix(coderd/rbac): speed up TestRolePermissions to reduce Windows CI timeout (#22657) 2026-03-09 15:57:55 +00:00
blinkagent[bot]andblink-so[bot] f4ab854b06 fix: mark context limit as required in model form (#22845)
## Summary

The backend requires `context_limit` to be a positive integer when
creating a model config, but the frontend form did not visually indicate
this to the user. This caused a confusing error after submission
("Context limit is required. context_limit must be greater than zero.").

## Changes

- Added required asterisk (`*`) to the **Context Limit** label, matching
the existing **Model Identifier** field pattern
- Added Yup `.required()` validation to the `contextLimit` field so the
form catches the missing value client-side before submission

## Before

The "Context Limit" label had no required indicator. Users could submit
the form without filling it in, only to receive a backend error.

## After

The "Context Limit" label now shows a red `*` (consistent with "Model
Identifier"), and the form validates the field as required before
allowing submission.

Created on behalf of @uzair-coder07

---------

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
2026-03-09 10:54:00 -05:00
Danielle Maywood c6b68b2991 refactor(site): use standard Spinner in AgentsPage (#22811) 2026-03-09 15:50:36 +00:00
Danielle Maywood 5dfd563e4b fix(site): remove orphaned DiffStatsInline story (#22846) 2026-03-09 15:49:33 +00:00
Mathias Fredriksson 4957888270 fix(agent/agentssh): make X11 max port configurable to fix test timeout (#22840)
TestServer_X11_EvictionLRU was timing out under -race because it created
190 sequential SSH shell sessions (~0.55s each = ~105s), exceeding the
90s test timeout. The session count was derived from the production
X11MaxPort constant (6200).

Add a configurable X11MaxPort field to Config so the test can use a
small port range (5 ports instead of 190). This reduces the number of
sessions from 190 to 4, completing in ~3.8s under -race.
2026-03-09 17:03:22 +02:00
Danielle Maywood 26adc26a26 refactor(site): compute selected model as derived state in AgentDetail (#22816) 2026-03-09 14:57:55 +00:00
Danielle Maywood b33b8e476b test(site): add missing stories for AgentsPage components (#22808) 2026-03-09 14:57:41 +00:00
Mathias Fredriksson 95bd099c77 fix(coderd/agentapi/metadatabatcher): use clock.Since instead of time.Since in flush (#22841)
The `flush` method sets `start := b.clock.Now()` but later computes
duration with `time.Since(start)` instead of `b.clock.Since(start)` for
the `FlushDuration` metric and the debug log. Line 352 already uses
`b.clock.Since(start)` correctly — this makes the rest consistent.

Test output before fix:
```
flush complete  count=100  elapsed=19166h12m30.265728663s  reason=scheduled
```

After fix:
```
flush complete  count=100  elapsed=0s  reason=scheduled
```
2026-03-09 16:51:46 +02:00
Kyle Carberry 3f939375fa refactor: unify agent sidebar into generic tabbed panel with Git sub-views (#22837)
## Summary

Refactors the right-side panel in the Agents page into a generic tabbed
container with a unified Git panel.

### Changes

**Architecture**
- `SidebarTabView` is now a generic tabbed container with no
git-specific logic, ready for additional tabs
- All Git content lives in a new `GitPanel` component with an internal
Remote/Local segmented control

**Git Panel**
- Remote view: branch/PR diff via `FilesChangedPanel`
- Local view: working tree changes with per-repo headers, commit &
refresh actions
- Split/unified diff toggle restored in the toolbar
- `DiffStatBadge` rendered inside the Remote/Local segmented buttons
(full-height, no rounding, inactive opacity 50%)

**Visual polish**
- Active/inactive/hover states match the sidebar agent selection styles
(`bg-surface-quaternary/25`, `hover:bg-surface-tertiary/50`)
- Inactive tab text uses `text-content-secondary` (not primary)
- Tab button sizing fixed: `min-w-0` + `px-2` to prevent inflated width
- Chat title centered via absolute positioning when panel is fullscreen
- Polished empty states with boxed icons (`GitCompareArrowsIcon` for
Remote, `FileDiffIcon` for Local)
- Unified header styles between Remote and Local sections (both use
`bg-surface-secondary` with consistent icon/text sizing)
- Panel toggle always visible in top bar (not gated on having diff data)

**Cleanup**
- Removed dead code: `DiffStatsInline`, `computeDiffStats` export,
`workingDiffStats` memo, `ChatDiffStatusResponse` import
- Simplified `RepoChangesPanel` to a pure `DiffViewer` wrapper
- Simplified `TopBar` to use a generic `panel` prop instead of
diff-specific props
2026-03-09 10:44:56 -04:00
Mathias Fredriksson a072d542a5 fix(site): clear stream state on WebSocket reconnect to prevent text duplication (#22838)
When the chat WebSocket reconnects, the server replays all buffered
`message_part` events in the initial snapshot. The client's `onOpen`
callback only cleared the stream error but **not** the stream state, so
replayed parts appended to the stale accumulator, doubling (or further
multiplying) the visible text with each reconnect. A page refresh would
clear the issue temporarily since it creates a fresh `ChatStore`.

This was caused by:

- **Server** (`coderd/chatd/chatd.go`): `Subscribe()` unconditionally
  includes all buffered `message_part` events in the snapshot sent to
  new connections. The `afterMessageID` parameter only filters durable
  DB messages, not ephemeral stream parts.
- **Client** (`ChatContext.ts`): The `onOpen` callback in
  `createReconnectingWebSocket` called `store.clearStreamError()` but
  not `store.clearStreamState()`. When the reconnected stream replays
  buffered `message_part` events, `applyMessagePartToStreamState`
  blindly appends text via `appendTextBlock`.

The fix was to add `store.clearStreamState()` in the `onOpen` callback
so replayed parts build from a clean slate instead of appending to stale
content.

A red/green verification test was added to ensure the fix works as
expected.
2026-03-09 16:36:17 +02:00
Mathias Fredriksson a96ec4c397 build: remove defunct test-postgres rule (#22839)
The `test-postgres` Makefile rule was redundant — CI never used it (it
runs `test-postgres-docker` + `make test` via the `test-go-pg` action),
and `make test` auto-starts a Postgres Docker container when needed via
`dbtestutil`.

- Remove the `test-postgres` rule from Makefile
- Update `pre-push` to run `test-postgres-docker` in the first phase
(alongside gen/fmt) and `make test` in the second phase
- Fix stale comments in CI workflows referencing `make test-postgres`
- Remove redundant "Test Postgres" entries from docs since `make test`
handles Postgres automatically
2026-03-09 16:24:40 +02:00
Danielle Maywood 2eb3ab4cf5 fix: skip redundant navigate in service worker notificationclick handler (#22836) 2026-03-09 13:27:54 +00:00
Ethan 51a627c107 ci: remove unnecessary brew install google-chrome from macOS CI (#22835)
Closes https://github.com/coder/internal/issues/1391

## Problem

The `test-go-pg (macos-latest)` job hit its 25m timeout without ever
running
tests because `brew install google-chrome` stalled for 23+ minutes
downloading
from the Homebrew CDN:

```
==> Fetching downloads for: google-chrome
Error: The operation was canceled.
```

## Why this is safe to remove

`brew install google-chrome` was added in Oct 2023 (`70a4e56c0`) the day
after
chromedp was integrated into the scaletest/dashboard package
(`1c48610d5`). At
that time, `run.go` called `initChromeDPCtx` directly (hardcoded), so
the unit
test actually launched a real Chrome process.

In Jun 2024, #13650 refactored this to accept a mock `InitChromeDPCtx`
via the
`Config` struct, and the test now passes a stub that never launches a
browser.
No test file in the repo references `chromedp` directly — the only test
(`scaletest/dashboard/run_test.go`) fully mocks Chrome initialization.

The `chromedp` Go library compiles fine without Chrome installed; it
only needs
the binary at runtime, and no test exercises that path.

## Impact

- Removes a ~200MB+ download from every macOS CI run
- Eliminates a fragile external dependency on Homebrew CDN availability
- Saves several minutes per run even when the download succeeds

_Generated with mux but reviewed by a human_
2026-03-10 00:07:35 +11:00