`TestActiveServer_BasicAssistantGenerationAndPromptPreparation` could
race by reassigning a request recorder captured by concurrent callbacks.
Keep the recorder immutable across both scenarios.
Closes https://github.com/coder/internal/issues/1626
Closes https://github.com/coder/internal/issues/1615. The affected test
was starting coderd with a live chatd worker, but assumed that the chat
would not be processed by a worker. The fix was to start coderd without
a chatd worker. I noticed that some other tests in the file could suffer
from the same flake root cause, so I fixed them too.
Addresses https://github.com/coder/internal/issues/1589. Supersedes
https://github.com/coder/coder/pull/26455. The previous fix did not
handle the case where there were 2 runners for the same chat: one
shutting down and one being bootstrapped. The one shutting down could
queue a stale state update which would cause the new runner to
immediately exit.
Prevents slow chat auto-archive runs from causing a constant archival
loop by resetting the ticker only after each run completes.
Also documents the UTC midnight cutoff used for archive eligibility so
chats with activity on the same UTC calendar date stay eligible or
ineligible for the full day.
Addresses deferred review comments:
- https://github.com/coder/coder/pull/26109#discussion_r3380197310
- https://github.com/coder/coder/pull/26109#discussion_r3380219922
Generated by Coder Agents and closely reviewed by Hugo.
Closes
[CODAGT-629](https://linear.app/codercom/issue/CODAGT-629/agents-can-get-stuck-and-ignore-stop-or-nudge).
A stuck chat had these logs associated with it:
```
1781735334322 2026-06-17T22:28:54.322Z 2026-06-17 22:28:54.322 [debu] coderd.chatd.processor: workspace context build: workspace agent not resolvable chat_id=d4524ebb-4494-47df-b258-d933c0248942 owner_id=d96bf761-3f94-46b3-a1da-6316e2e4735d
1781735334298 2026-06-17T22:28:54.298Z 2026-06-17 22:28:54.298 [debu] coderd.chatd.processor: plan path instruction: agent not reachable chat_id=d4524ebb-4494-47df-b258-d933c0248942 owner_id=d96bf761-3f94-46b3-a1da-6316e2e4735d chat_id=d4524ebb-4494-47df-b258-d933c0248942 ...
error= workspace has no running agent: the workspace is likely stopped. Use the start_workspace tool to start it:
github.com/coder/coder/v2/coderd/x/chatd.init
<autogenerated>:1
```
"workspace agent not resolvable" is printed by
[`fetchContextForBuild`](<https://github.com/coder/coder/blob/fecc991ac9f9d673d7a772b0042ca821d19b6296/coderd/x/chatd/workspace_context_builder.go#L145>).
this causes
[`buildWorkspaceContext`](<https://github.com/coder/coder/blob/fecc991ac9f9d673d7a772b0042ca821d19b6296/coderd/x/chatd/workspace_context_builder.go#L60>)
to exit with a `errWorkspaceContextUnavailable` error. That in turn is
interpreted by
[`persistWorkspaceContext`](<https://github.com/coder/coder/blob/fecc991ac9f9d673d7a772b0042ca821d19b6296/coderd/x/chatd/generation.go#L838>)
as an "expected exit" scenario. That's a bug: because the task exits
without changing the chat state, the runner never issues another task to
process the chat any further. But even if it did, it'd go through the
same code path and exit again. We need to ensure that
`persistWorkspaceContext` commits a marker file even if it cannot reach
the agent.
Closes https://github.com/coder/internal/issues/1601. Fixes a stream
parts WebSocket close race. If the peer closed first, the session read
loop could close the connection before `StreamPartsSession.Close()` ran,
causing cleanup to return a wrapped `net.ErrClosed`. The fix treats
expected transport close errors as successful cleanup.
Closes
[CODAGT-610](https://linear.app/codercom/issue/CODAGT-610/add-an-architecturemd-file-to-chatd).
Adds an ARCHITECTURE.md file which describes the architecture of the
chatd subsystem. It's meant for reading by both humans, who would like
to understand chatd better, and agents. It's an edited version of the
chatd stabilization RFC.
Using `WaitGroup.Go` must be synchronized with `WaitGroup.Wait`
according to [go docs](https://pkg.go.dev/sync#WaitGroup.Go):
> If the WaitGroup is empty, Go must happen before a
[WaitGroup.Wait](https://pkg.go.dev/sync#WaitGroup.Wait).
There were a couple of places in chatd that violated this principle.
This was caught as a data race in
https://github.com/coder/internal/issues/1599. This PR ensures that all
functions that spawn inflight goroutines synchronize with each other.
I also noticed that inflight goroutines may be spawned after the server
is closed, which was surprising and looked like a bug. This PR therefore
also introduces a mechanism that disallows spawning inflight goroutines
after the server is closed, and ensures that any code that tries doing
it logs an error.
Closes https://github.com/coder/internal/issues/1599.
This PR adds logging when the chat runner retries and exits because of
an error. It also adds a 15-minute task timeout to ensure that stuck
tasks do not hang forever.
Noticed when enabling the goleak checker in chatd:
```
=== FAIL: coderd/x/chatd (0.00s)
PASS
goleak: Errors on successful test run: found unexpected goroutines:
[Goroutine 108179 in state select, with github.com/coder/coder/v2/tailnet.(*Conn).AwaitReachable on top of the stack:
github.com/coder/coder/v2/tailnet.(*Conn).AwaitReachable(0x2c35e171d760, {0x74b37a8?, 0x2c35f6886330?}, {{0x0?, 0x0?}, {0x2c35def838c0?}})
/home/runner/work/coder/coder/tailnet/conn.go:647 +0x2ae
github.com/coder/coder/v2/codersdk/workspacesdk.(*agentConn).AwaitReachable(0x2c35e4f18440, {0x74b37e0?, 0x2c35f45680a0?})
/home/runner/work/coder/coder/codersdk/workspacesdk/agentconn.go:172 +0x12b
github.com/coder/coder/v2/codersdk/workspacesdk.(*agentConn).apiRequest.(*agentConn).apiClient.func1({0x74b37e0, 0x2c35f45680a0}, {0x61a8946?, 0x60fa2a0?}, {0x2c35e0af7380, 0x2b})
/home/runner/work/coder/coder/codersdk/workspacesdk/agentconn.go:1381 +0x212
net/http.(*Transport).dial(0x2c35e0af6210?, {0x74b37e0?, 0x2c35f45680a0?}, {0x61a8946?, 0xa0e255?}, {0x2c35e0af7380?, 0xa1456f?})
/home/runner/work/_temp/mise-data/installs/go/1.26.4/src/net/http/transport.go:1307 +0xd2
net/http.(*Transport).dialConn(0x2c35f8d8b380, {0x74b37e0, 0x2c35f45680a0}, {{}, 0x0, {0x2c35fc5a3e50, 0x4}, {0x2c35e0af7380, 0x2b}, 0x0}, ...)
/home/runner/work/_temp/mise-data/installs/go/1.26.4/src/net/http/transport.go:1815 +0x847
net/http.(*Transport).dialConnFor(0x2c35f8d8b380, 0x2c35e287a580)
/home/runner/work/_temp/mise-data/installs/go/1.26.4/src/net/http/transport.go:1648 +0xd2
net/http.(*Transport).startDialConnForLocked.func1()
/home/runner/work/_temp/mise-data/installs/go/1.26.4/src/net/http/transport.go:1629 +0x35
created by net/http.(*Transport).startDialConnForLocked in goroutine 107872
/home/runner/work/_temp/mise-data/installs/go/1.26.4/src/net/http/transport.go:1628 +0x112
Goroutine 108180 in state select, with github.com/cenkalti/backoff/v4.(*Ticker).run on top of the stack:
github.com/cenkalti/backoff/v4.(*Ticker).run(0x2c35f8517860)
/home/runner/go/pkg/mod/github.com/cenkalti/backoff/v4@v4.3.0/ticker.go:70 +0x13f
created by github.com/cenkalti/backoff/v4.NewTickerWithTimer in goroutine 108179
/home/runner/go/pkg/mod/github.com/cenkalti/backoff/v4@v4.3.0/ticker.go:49 +0x16c
]
FAIL github.com/coder/coder/v2/coderd/x/chatd 126.492s
```
Closes https://github.com/coder/internal/issues/1595
Addresses
[CODAGT-620](https://linear.app/codercom/issue/CODAGT-620/session-can-get-stuck-at-compaction-with-request-failed).
We have logic that checks whether message compaction still leaves the
chat over the context limit. We want to abort if it does - if we didn't,
we'd get into an endless compaction loop. The check's logic was faulty.
This PR changes fixes it. The new flow is:
1. In iteration 1, a chat runner commits a message compaction summary.
2. In iteration 2, the runner submits the newly compacted conversation
to the LLM provider in order to generate the next message.
3. In iteration 3, 4, 5, etc., if the conversation needs compaction, the
runner looks up the configured context limit and the first assistant
message after the last compaction summary. It compares the context usage
on that message with the context limit. If the usage is over the limit,
it returns an error.
Frontend for https://github.com/coder/coder/pull/24022.
From that PR's description:
> The agents chat interface displays thumbnails for videos recorded by
the computer use agent. Currently, to display a thumbnail, the frontend
downloads the entire video and shows the first frame.
#24022 adds a thumbnail file id to `wait_agent` tool results, and this
PR displays it instead of fetching the entire video.
The agents chat interface displays thumbnails for videos recorded by the
computer use agent. Currently, to display a thumbnail, the frontend
downloads the entire video and shows the first frame. This PR starts
storing a new thumbnail file in the database for every recorded video,
and exposes the file id in the `wait_agent` tool result alongside the
recording file id, so the frontend can fetch just the thumbnail.
This PR modifies the `wait_agent` tool call card to display screen
recordings of computer use subagents. The backend logic was added in
https://github.com/coder/coder/pull/23894.
There's one big inefficiency in the current implementation: to display
video thumbnails, the frontend downloads the entire video files from the
backend. Our backend does not support HTTP range requests to only fetch
the first frame. I'll be fixing that in a later PR.
https://github.com/user-attachments/assets/684cea8b-66a9-45f8-96b2-57433da41c1c
This PR introduces screen recording of the computer use agent using the
virtual desktop.
- Screen recording is triggered by a `wait_agent` tool call. Recording
is stopped by a successful `wait_agent` tool call or when there hasn't
been any desktop activity for 10 minutes.
- Recordings are handled by the `portabledesktop` cli via the `record`
command. The videos are sped up in periods of inactivity.
- Recordings are saved to the database to the `chat_files` table.
There's a hard limit of 100MB per recording. Larger recordings are
dropped.
- A successful `wait_agent` on a computer use subagent tool call returns
a `recording_file_id`, later allowing the frontend to display the
corresponding video.
Adds preview cards for the `spawn_computer_use_agent` tool. Spawning
that agent now renders a rich saying "Spawning computer use sub-agent".
The "waiting for" tool now displays an inline desktop preview which can
be clicked to reveal the desktop in the sidebar.
https://github.com/user-attachments/assets/e486ca0e-a569-4142-bb12-db3b707967b8
If the desktop viewer component was hidden, for example after collapsing
the sidebar, the next time it was shown the viewer would be blank. This
PR fixes that.
We had a bug where computer use base64-encoded screenshots would not be
interpreted as screenshots anymore once saved to the db, loaded back
into memory, and sent to Anthropic. Instead, they would be interpreted
as regular text. Once a computer use agent made enough screenshots and
stopped, and you tried sending it another message, you'd get an out of
context error:
<img width="808" height="367" alt="Screenshot 2026-03-23 at 12 02 54"
src="https://github.com/user-attachments/assets/f0bf6be2-4863-47ca-a7a9-9e6d9dfceeed"
/>
This PR fixes that.
The `ComputerUseProviderTool` function needed a little bit of an
adjustment because I changed `NewComputerUseTool`'s signature in
upstream fantasy a little bit.
This PR changes agents desktop resolution from 1366x768 to 1920x1080.
Anthropic requires the that the resolution of desktop screenshots fits
in 1,150,000 total pixels, so we downscale screenshots to 1280x720
before sending them to the LLM provider.
Resolution scaling was already implemented, but our code didn't exercise
it. The resolution bump showed that there were some bugs in the scaling
logic - this PR fixes these bugs too.
This PR fixes a bug where if a tool result contained binary data it
wouldn't be persisted to the database.
`jsonb` in Postgres is unable to store null bytes which are sometimes
output by tool results. This change makes it so that we encode them with
a special escape sequence before saving them to the database, and decode
them on read.
<img width="808" height="637" alt="Screenshot 2026-03-11 at 13 14 06"
src="https://github.com/user-attachments/assets/9be353eb-ff26-40ec-9f0a-195022b11f43"
/>
Our dogfood image already included chrome. Since we run dogfood
workspaces in Docker, chrome requires some compatibility flags to run
properly. If you launch chrome without them, some webpages crash and
fail to load.
The newest release of https://github.com/coder/portabledesktop added an
icon dock. This PR edits the chrome `.desktop` files so when you open
chrome from the dock it runs with the correct flags.
https://github.com/user-attachments/assets/7bf880e1-22a4-4faa-8f7f-394863c6b127
Adds a new `site_config` entry that controls whether the virtual desktop
feature for Coder Agents is enabled. It can be set via a new
`/api/experimental/chats/config/desktop-enabled` endpoint, which will be
used by the frontend.
Implement the backend for the desktop feature for agents.
- Adds a new `/api/experimental/chats/$id/desktop` endpoint to coderd
which exposes a VNC stream from a
[portabledesktop](https://github.com/coder/portabledesktop) process
running inside the workspace
- Adds a new `spawn_computer_use_agent` tool to chatd, which spawns a
subagent that has access to the `computer` tool which lets it interact
with the `portabledesktop` process running inside the workspace
- Adds the plumbing to make the above possible
There's a follow up frontend PR here:
https://github.com/coder/coder/pull/23006
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.
## Problem
`TestE2E_WriteFileTriggersGitWatch` and `TestE2E_SubagentAncestorWatch`
flake intermittently in `test-go-race-pg` with:
```
agentgit_test.go:1271: timed out waiting for server message
```
## Root Cause
In `handleWatch()`, `GetPaths(chatID)` was called **before**
`Subscribe(chatID)` on the PathStore. If `AddPaths()` fired between
those two calls:
1. `GetPaths()` returned empty (paths not added yet).
2. `AddPaths()` stored the paths and called `notifySubscribers()` — but
the subscription channel didn't exist yet, so the notification was a
no-op.
3. `Subscribe()` created the channel, but the notification was already
lost.
4. The handler never scanned, and the mock clock never advanced the 30s
fallback ticker → timeout.
Both failing tests connect the WebSocket with an empty PathStore and
immediately call `AddPaths()` from the test goroutine, making them
vulnerable to this scheduling interleaving.
## Fix
Swap the order: call `Subscribe()` first, then `GetPaths()`. This
guarantees:
| `AddPaths` fires... | `Subscribe` sees it? | `GetPaths` sees it? |
Outcome |
|---|---|---|---|
| Before `Subscribe` | No | **Yes** | Picked up by `GetPaths` |
| Between the two calls | **Yes** (queued) | **Yes** | Redundant but
safe (delta dedupes) |
| After `GetPaths` | **Yes** | No | Goroutine handles it |
No window exists where both miss it.
Verified with 10,000 iterations (`-race -count=5000`) — zero failures.
Fixescoder/internal#1389
go-git has bugs in gitignore logic. With more complex gitignores, some
paths that should be ignored aren't. That caused extra, unexpected files
to appear in the git diff panel.
If the git cli isn't available in a workspace, the /git/watch endpoint
will still allow the frontend to connect, but no git changes will ever
be transmitted.
Replaces the single-purpose PR diff right panel with a tabbed sidebar
that shows both the existing PR diff and real-time git repository
changes from the workspace agent.
There's an accompanying backend PR
[here](https://github.com/coder/coder/pull/22565).
https://github.com/user-attachments/assets/bbd53f1c-d753-4574-a159-6dad5989e5e3
## Backend surface
One endpoint drives this feature:
- **`WS /api/experimental/workspaceagents/{id}/git/watch`** —
bidirectional WebSocket. The client sends `refresh` messages; the agent
responds with `changes` messages containing per-repo branch and unified
diff. The workspace agent also automatically pushes changes as they
occur in the workspace.
Adds real-time git status watching for workspace agents, so the frontend
can subscribe over WebSocket and show
git file changes in near real-time.
1. Subscription is scoped to a **chat** via `GET
/api/experimental/chats/{chat}/git/watch`.
2. The workspace agent automatically determines which paths to watch
based on tool calls made by the chat (and its ancestor chats).
3. Workspace agent polls subscribed repo working trees on a 30s
interval, on tools calls, and on explicit `refresh` from the client.
4. Scans are rate-limited to at most once per second.
5. Edited paths are tracked **in-memory** inside the workspace agent.
There is no database persistence — state is lost on agent restart. This
will be addresses in a future PR.
6. Messages sent over WebSocket include a full-repo snapshot (unified
diff, branch, origin). A new message is emitted only when the snapshot
changes.
This PR was implemented with AI with me closely controlling what it's
doing. The code follows a plan file that was updated continuously during
implementation. Here's the file if you'd like to see it:
[project.md](https://gist.github.com/hugodutka/8722cf80c92f8a56555f7bc595b770e2).
It reflects the current state of the PR.
I noticed that our docs mention the possibility of using the
Tailscale-managed DERP server fleet.
https://github.com/coder/coder/pull/15901 changed the phrasing from
> However, Tailscale has graciously allowed us to use
to
> However, our Wireguard integration through Tailscale has graciously
allowed us to use
This change alters the original meaning of the sentence. AFAIK, the
original meant that we contacted Tailscale directly and asked if it
would be ok for our customers to use the Tailscale-managed DERP server
fleet, and Tailscale graciously agreed. The new phrasing conveys
something different. This PR reverts the phrasing to the original.
---------
Co-authored-by: david-fraley <67079030+david-fraley@users.noreply.github.com>
This PR reverts github.com/mark3labs/mcp-go to 0.32.0, which was the
version used by https://github.com/coder/coder/pull/18670 that
introduced MCP HTTP support in Coder, and ensures dependabot doesn't
upgrade it automatically.
A bug has been introduced in a recent version of mcp-go that causes some
HTTP MCP requests to fail with the error message
```
[erro] coderd.mcp: Failed to handle sampling response: no active session found for session mcp-session-e3cb7333-284f-46bd-a009-d611f1b690f6
```
The bug may be related to this issue:
https://github.com/mark3labs/mcp-go/issues/554.
This PR starts running the full test suite on Windows and macOS in the
nightly gauntlet, since the regular CI only runs agent and cli tests.
The full suite is too slow to be run on every PR.
Previously, we displayed apps in iframes on the task page without
waiting for them to initialize. This would result in 502 errors shown to
the user. This PR makes sure that we only display the app after it
initializes.
### Before
<img width="1920" alt="Screenshot 2025-06-30 at 14 59 07 (2)"
src="https://github.com/user-attachments/assets/63564ac9-abce-4a0c-b58e-b988772fae82"
/>
Use the `/workspaces?q=has-ai-task=true`,
`/templates?q=has-ai-task=true` and `/aitasks/prompts` endpoints to
fetch Task templates and workspaces on the `/tasks` page.
Also:
- remove documentation link placeholders: the documentation is not in
place yet and is not going to be available before the June 24th code
freeze
- load workspaces and templates in parallel
- replace loading spinners with content skeletons
Related to https://github.com/coder/coder/issues/18454 and
https://github.com/coder/internal/issues/660.
Add an endpoint to fetch AI task prompts for multiple workspace builds
at the same time. A prompt is the value of the "AI Prompt" workspace
build parameter. On main, the only way our API allows fetching workspace
build parameters is by using the `/workspacebuilds/$build_id/parameters`
endpoint, requiring a separate API call for every build.
The Tasks dashboard fetches Task workspaces in order to show them in a
list, and then needs to fetch the value of the `AI Prompt` parameter for
every task workspace (using its latest build id), requiring an
additional API call for each list item. This endpoint will allow the
dashboard to make just 2 calls to render the list: one to fetch task
workspaces, the other to fetch prompts.
<img width="1512" alt="Screenshot 2025-06-20 at 11 33 11"
src="https://github.com/user-attachments/assets/92899999-e922-44c5-8325-b4b23a0d2bff"
/>
Related to https://github.com/coder/internal/issues/660.