> Mux working on behalf of Mike.
Debug recording could consume request bodies when a provider SDK
returned the active body from `GetBody`, which left the upstream request
with an empty body after capture.
Reset the request body after debug capture and add coverage for shared
`GetBody` readers so debug logging does not alter the bytes sent
upstream.
Tighten visual rhythm and typography in the agent chat page so tool
calls, reasoning, and assistant text share the same baseline.
## Highlights
- Unify font size to **13px** across user messages, assistant
`Response`, reasoning, and every tool-call label.
- Reuse the `text-content-secondary → hover:text-content-primary`
transition on tool-call rows so labels, chevrons, and lucide icons
brighten together. Icons inside hover-aware headers switch to
`text-current` so they inherit the parent transition; static icons in
non-collapsible cards (`ExecuteTool`, `ProcessOutputTool`) keep the
constant secondary color.
- Collapse padding between adjacent tool/thinking blocks via a shared
`data-tool-call` attribute and adjacent-sibling selectors
(`[&:has(+[data-tool-call])]:pb-0` + `[[data-tool-call]+&]:pt-0`).
First/last items keep their padding against text and reasoning siblings.
- `read_file` now mirrors `write_file`: `Reading <name>…` while running,
`Read <name>` once complete.
- `ask_user_question` flips the inline label from `Asking:` to `Asked:`
once answered.
- Subagent row layout: status icon + label + chevron sit together at the
start of the row, while the `Worked for <duration>` text uses `ml-auto`
to anchor the right edge.
- New Storybook story `WithEveryTool` (under `Pages / AgentsPage /
AgentChatPage`) exercises every tool renderer plus subagent variants and
the generic MCP fallback in a single completed-then-streaming turn.
---
_Authored with help from a Coder Agent._
The MCP manager previously read .mcp.json exactly once at agent startup.
Editing the file had no effect until workspace rebuild or agent restart.
handleListTools now stats config file mtimes on every tool-list request
and triggers a differential reload when any file changed. Unchanged
servers keep their client pointer so in-flight tool calls survive.
Concurrent reload requests coalesce via singleflight.
MCP stdio subprocesses use the agent's execer for resource limits and
receive the same enriched environment as SSH sessions via updateEnv.
On the chatd side, WorkspaceMCPTool.Run detects 404 responses from
CallMCPTool (indicating the server was removed) and drops the chat's
cached tool list so the next turn refetches from the agent.
ValidateToken treated all 403 responses as "token invalid," including
GitHub rate limits. isFailedRefresh included 403 in the status code
fallthrough, destroying tokens on rate-limited refresh attempts.
Split the combined 401/403 check in ValidateToken into a switch on
status code. On 403, inspect X-RateLimit-Remaining and Retry-After
headers; if either indicates a rate limit, return optimistically valid.
Handle 429 the same way. Plain 403 without rate-limit headers preserves
the existing invalid-token behavior.
Add incorrect_client_credentials and invalid_client to isFailedRefresh
error code switch. Remove 403 from the status code fallthrough since no
known provider returns 403 from the token endpoint.
The CODER_AGENT_EXP_* env vars are agent-internal options. When set
in the workspace environment they leak to MCP subprocesses and user
shells.
ReadEnvConfig() captures the values and ClearEnvVars() strips them
before the reinit loop, so config survives agent restarts. NewAPI
and ReadEnvConfig both use applyDefaults() to fill zero fields.
The chatd test passes config via agenttest.WithContextConfigFromEnv().
When `StopAfterTool` fires (e.g., `propose_plan`), the LLM response
containing a `function_call` is stored at OpenAI via `store=true`, but
the tool result is only persisted locally. On the next user message,
`resolveChainMode` sees the tool result in the local DB and concludes
all calls are resolved. Chain mode activates with
`previous_response_id`, but OpenAI rejects because its stored chain has
an unresolved `function_call`.
This adds a `providerMissingToolResults` check to `resolveChainMode`
that detects the `assistant(tool-call) → tool(result) → user` pattern
with no follow-up assistant message. The absence of a follow-up
assistant proves the tool results were never round-tripped to the
provider. When detected, chain mode is blocked and the system falls back
to full history replay, which includes both the tool call and its
result.
Deploying this fix un-bricks existing affected chats with no DB
migration needed.
> Generated by Coder Agents.
> Mux is working on behalf of Mike.
## Summary
- Bump `github.com/coder/anthropic-sdk-go` to the corrected Bedrock
streaming header fix from coder/anthropic-sdk-go#14.
- Match botocore's `InvokeModelWithResponseStream` request shape by
using `X-Amzn-Bedrock-Accept` and omitting the HTTP `Accept` header.
- Update chatd regression coverage for the corrected header shape.
## Context
The previous fix set `Accept: application/vnd.amazon.eventstream`. Real
boto3/botocore streaming requests do not send that header. They send
`X-Amzn-Bedrock-Accept: application/json`, which is the modeled Bedrock
request header for the desired model response MIME type.
## Validation
- `go test ./coderd/x/chatd/chatprovider -run
'TestModelFromConfig_Bedrock(StreamingHeaders|StripsAnthropicHeaders)?$'
-count=1`
- `go mod tidy -diff`
- `git diff --check`
- pre-commit hook during `git commit`
> Mux is working on behalf of Mike.
## Summary
- Bump `github.com/coder/anthropic-sdk-go` to the clean Bedrock
streaming header fix from coder/anthropic-sdk-go#10.
- Add chatd regression coverage that verifies Bedrock streaming requests
use AWS event stream headers and include `X-Amzn-Bedrock-Accept` in the
SigV4 signed headers.
## SDK follow-up
- Reverted the bad coder/anthropic-sdk-go#8 merge with
coder/anthropic-sdk-go#9.
- Re-applied only the intended Bedrock streaming header change in
coder/anthropic-sdk-go#10.
## Validation
- `go test ./coderd/x/chatd/chatprovider -run
'TestModelFromConfig_Bedrock(StreamingHeaders|StripsAnthropicHeaders)?$'
-count=1`
- `go test ./coderd/x/chatd/chatprovider -count=1`
- `go mod tidy -diff`
- `make lint`
- pre-commit hook during `git commit`
## Problem
Anthropic returns HTTP 400 when an assistant message contains a
`web_search_tool_result` block whose `tool_use_id` has no matching
earlier `server_tool_use` block in the same assistant message. A
previous fix (#24706) sanitized provider-executed tool calls without
matching results, but the opposite direction, orphaned or misordered
provider-executed results, could still slip through both the prompt
sanitizer and the persistence path.
## Fix
Tighten Anthropic provider-executed tool history handling while
preserving the useful result payload as normal assistant text when the
provider-tool metadata is unsafe.
1. Extract Anthropic provider-tool sanitization into
`coderd/x/chatd/chatsanitize` so provider-specific repair logic is no
longer spread through `chatprompt` and `chatloop`.
2. `chatsanitize.SanitizeAnthropicProviderToolHistory` removes invalid
provider-executed tool structure for Anthropic prompts: orphans in
either direction, result-before-call, duplicate IDs, invalid JSON
inputs, empty IDs and tool names, unsupported tool names, mismatched
`ProviderExecuted` flags, provider-executed blocks outside assistant
messages, and web-search results without serializable Anthropic result
metadata. Provider-executed result payloads are textified instead of
being discarded when there is text to preserve.
3. `chatsanitize.SanitizeAnthropicProviderToolContent` mirrors the same
rule at the streamed step content level. Persisted history no longer
carries invalid provider-tool blocks forward, but it keeps the result
text for future turns.
4. `chatsanitize.ApplyAnthropicProviderToolGuard` only repairs
structurally invalid Anthropic provider-tool history. It no longer
strips otherwise-valid historical `web_search` blocks just because web
search is disabled for the current request. The fail-closed fallback
also textifies provider results before removing provider-tool metadata.
Tests cover prompt sanitization, validation reason strings, result
payload textification, content-level persistence sanitization, disabled
web-search history preservation, direct pre-request guard behavior, and
the fallback strip path.
> Mux is acting on Mike's behalf.
Persists the agents page archived filter in the URL via
`?archived=archived`,
so deep-linking to archived agents and restoring the filter from history
work
as expected. Unknown values fall back to active. Toggling back to active
removes the param from the URL so the default state has one canonical
form.
Also fixes a regression that surfaced once the filter became
URL-derived:
clicking a chat in the sidebar previously navigated to `/agents/:id`
with no
search params, silently resetting the filter. The sidebar's chat
`NavLink`
now preserves `location.search`.
Coverage:
- `useArchivedFilterParam` is unit-tested with `renderHook`, covering
URL
parsing variants and the `deleteValue` semantics for the default state.
- Cross-route preservation is covered by a Storybook play story
(`PreservesArchivedFilterOnChatNavigation`) that renders the real
`NavLink`
and asserts on a probe child route.
- The pre-existing sidebar callback tests in `AgentsSidebar.test.tsx`
cover
wiring that exists on `main`; they're kept here for completeness rather
than as new feature gates.
_Generated by Coder Agent._
Follow-up to #24650.
Canceling the terminal command confirmation dialog now calls
`window.close()` instead of stripping the `?command=` query parameter
and opening a plain terminal. The terminal always opens in a new tab,
so closing it is the expected UX when the user declines.
> 🤖 Generated by Coder Agents
Depends on #24642
Adds per-owner digest notifications onto the chat auto-archive
subsystem.
Each tick's archived rows are grouped by owner, the top 25 titles per
owner are rendered into a new `Chats Auto-Archived` notification
template, and any remainder surfaces as `and N more`. Each digest is
per-tick, so users with large amounts of purgeable data may get multiple
notifications in sequence (one per user per tick).
The template body branches on `retention_days`: when retention is
disabled (`retention_days=0`), users are told archived chats are kept
indefinitely rather than falsely claiming imminent deletion.
### Changes
- migration `000XXX_chat_auto_archive_notification_template` adds new
notification template
- `dbpurge`: threads `notifications.Enqueuer` through `New`; and
enqueues notification message.
- `cli/server.go`: passes `options.NotificationsEnqueuer` into
`dbpurge.New`.
- `coderd/notifications/events.go`: new `TemplateChatAutoArchiveDigest`
UUID.
- `coderd/inboxnotifications.go`: inbox registration.
- Docs: adds a `Notifications` section to `chat-auto-archive.md`.
> 🤖
> Mux working on behalf of Mike.
## Summary
Clarifies that GitHub PR body prose should rely on soft wrapping instead
of manual fixed-width hard wraps. Updates the examples and key
principles to match that guidance.
## Validation
- `pnpm exec markdownlint-cli2 .claude/docs/PR_STYLE_GUIDE.md`
- `make lint/emdash`
- `make pre-commit-light`
The `--login-type none` option for `coder users create` is deprecated.
This adds deprecation warnings to all docs that reference it and updates
the CI/CD tutorial to recommend the replacement flows.
Refs DEVEX-224
<details>
<summary>Changes</summary>
- `cli/usercreate.go`: Append deprecation notice to `--login-type` flag
description.
- `docs/tutorials/testing-templates.md`: Replace `--login-type none`
example with separate Premium (`--service-account`) and OSS
(`--login-type password`) examples.
- `docs/reference/cli/users_create.md`: Regenerated from CLI source.
- `cli/testdata/coder_users_create_--help.golden`: Updated golden
snapshot.
</details>
> [!NOTE]
> Generated by Coder Agents.
Currently when a user clicks either the Cancel or Allow button on the
authorization page the client app URI is executed but the page does not
land to the main dashboard page, leaving the two buttons open for
multiple clicks from the user. Aside from the potential problems it
might cause by activating the callback URI multiple times, the page also
provides poor UX because users usually expect the authorization tab to
return to the dashboard.
The consent page now executes the OAuth2 callback (auth code on Allow,
`access_denied` on Cancel) and hides the two buttons and updates the
existing description with a user instruction to close the window.
Initial implementation relied on a pop-up window executing the callback
while the main window was redirected to the dashboard main page.
- resolves https://github.com/coder/coder/issues/20323
<!--
If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.
-->
The agent chat thinking disclosure used a smaller label with the caret
on the left, which made collapsed and in-progress thinking look
different from collapsible tool calls.
Align the thinking disclosure with the shared tool-call row treatment by
using the same label size, trailing caret placement, and hover color
while preserving the streaming shimmer. Adds a Storybook story that
renders collapsed thinking next to a tool call.
<details>
<summary>Storybook screenshots</summary>
Captured from Storybook:
-
`pages/AgentsPage/ChatConversation/ConversationTimeline/ThinkingBlockWithToolCall`
-
`pages/AgentsPage/ChatConversation/ConversationTimeline/ThinkingBlockWithToolCall`
hovered
-
`pages/AgentsPage/ChatConversation/StreamingOutput/ThinkingDuringStreamingWithToolCalls`
Screenshots are attached in the Coder task.
</details>
Generated by Coder Agents.
The deprecation notice on the [MCP Tools
Injection](https://coder.com/docs/ai-coder/ai-gateway/mcp) page
currently states the feature "will be removed in a future release,"
which may cause concern for users relying on it today.
This updates the warning to clarify that the feature will remain
functional and will not be removed until its replacement, MCP Gateway,
is released.
> [!NOTE]
> Generated by Coder Agents
---------
Co-authored-by: david-fraley <67079030+david-fraley@users.noreply.github.com>
> Mux is acting on Mike's behalf.
Adds explicit star, edit, and duplicate actions to each Agents model
configuration row, replacing the chevron-only affordance.
Duplicate opens a prefilled create form backed by the existing create
mutation when the provider can manage models. The form copies editable
model fields and provider config while clearing default status so saving
a duplicate does not change the current default model.
Bumps coder/terraform-provider-coder to v2.16.0 to pick up the `coder_secret`
data source that enables expressing a required user secret in a template.
This change passes user secrets from coderd to the Terraform process at
workspace build time so the `data.coder_secret` data source in
terraform-provider-coder can resolve values at plan time.
Secrets traverse two proto hops: `provisionerdserver` fetches them
via`ListUserSecretsWithValues`, attaches them to
`AcquiredJob.WorkspaceBuild.user_secrets` on `provisionerd.proto`;
`runner.go` forwards into `PlanRequest.user_secrets` on
`provisioner.proto`; the Terraform provisioner encodes each as
`CODER_SECRET_ENV_<name>` or `CODER_SECRET_FILE_<hex(path)>` before
invoking `terraform plan`. Only plan requests carry secrets; apply runs
with `nil` because values are baked into plan state.
Fetch is gated on a workspace transitioning to start. stop and delete
transitions never carry secrets, so revoking or deleting a stored secret
cannot make a workspace unstoppable. DB errors on the fetch fail the job
outright rather than silently continuing with an empty secret set.
Note that user secrets will be stored in the workspace_builds table in
provisioner_state with other Terraform state (including other sensitive data).
The terminal page auto-executed commands from the `?command=` query
parameter
on page load without user confirmation. Because session auth uses
`SameSite=Lax`
cookies, an attacker could craft a link (phishing email, Slack DM,
external page)
that executes arbitrary commands in a victim's workspace when clicked.
Adds a `ConfirmDialog` that shows the exact command and requires
explicit user
approval before it is passed to the terminal WebSocket. Canceling
removes the
`command` parameter from the URL and opens a plain terminal.
<details>
<summary>Implementation details</summary>
### Data flow (before)
`TerminalPage.tsx` reads `searchParams.get("command")` and passes it
directly
as `initialCommand` to `WorkspaceTerminal`, which embeds it in the
WebSocket
URL. `proxy.go` forwards it to the agent, which runs `bash -c
"<command>"`
immediately.
### Fix
- Added `commandConfirmed` state and `commandPendingConfirmation` flag
in
`TerminalPage.tsx`.
- The `loading` prop passed to `WorkspaceTerminal` includes
`commandPendingConfirmation`, keeping the terminal in loading state
until
the user confirms or cancels.
- The command is only passed as `initialCommand` after the user clicks
"Run command" in the confirmation dialog.
- Trusted `?app=` commands (resolved from agent apps) bypass the dialog.
- Cancel removes the `?command=` parameter from the URL entirely.
- No backend changes needed; the frontend gates the command before it
reaches the WebSocket.
### Terminal focus after dialog
`WorkspaceTerminal`'s autoFocus effect previously depended on
`[terminal, isVisible, autoFocus]` but not `loading`. It fired while the
Radix dialog's focus trap was active, so `terminal.focus()` was
intercepted. When `loading` became false after confirming the dialog,
the
effect did not re-fire. Fixed by adding `loading` to the effect deps and
skipping focus while `loading` is true.
### Files changed
| File | Change |
|------|--------|
| `site/src/pages/TerminalPage/TerminalPage.tsx` | Confirmation dialog,
`commandPendingConfirmation` in loading prop |
| `site/src/pages/TerminalPage/TerminalCommandConsentDialog.tsx` | New
dialog component |
| `site/src/pages/TerminalPage/TerminalCommandConsentDialog.stories.tsx`
| Storybook story for dialog |
| `site/src/pages/TerminalPage/TerminalPage.stories.tsx` |
`CommandConfirmation` story |
| `site/src/pages/TerminalPage/TerminalPage.test.tsx` | 4 new dialog
tests, `renderTerminalRaw` helper for non-blocking render |
| `site/src/modules/terminal/WorkspaceTerminal.tsx` | Add `loading` to
autoFocus effect deps |
| `site/e2e/helpers.ts` | Dismiss dialog in `openTerminalWindow` helper
|
| `site/e2e/tests/webTerminal.spec.ts` | Wait for
`data-status="connected"` + click terminal for focus |
</details>
> 🤖 Generated by Coder Agents
---------
Co-authored-by: Jakub Domeracki <jakub@coder.com>
`namedWorkspace` in `cli/root.go` parsed workspace identifiers with
`uuid.Parse` first and returned immediately on success, even when no
workspace had that UUID as its actual ID. This caused 404 errors for any
workspace whose name was a valid 32-char hex string (dashless UUID).
- Add `codersdk.ResolveWorkspace`: tries UUID lookup first, falls back
to name lookup on 404. `NameValid` guard skips the fallback for standard
dashed UUIDs (36 chars > 32-char name limit).
- Export `codersdk.SplitWorkspaceIdentifier`, replacing the duplicate
`splitNamedWorkspace` in `cli/root.go` (uses `strings.Cut`).
- Delete `namedWorkspace` from `cli/root.go`; all 28 call sites now use
`client.ResolveWorkspace` directly.
- Delete `namedWorkspace` and `splitNameAndOwner` from
`codersdk/toolsdk/bash.go`; inline `client.ResolveWorkspace`.
- Simplify `GetWorkspace` tool handler to a single `ResolveWorkspace`
call.
- Unit tests via httptest mock cover UUID, name, owner/name, UUID-like
fallback, not-found, server error, transport error, and invalid
identifier paths.
- Integration tests in `cli/show_test.go` and `codersdk/toolsdk` for
workspaces with UUID-like names.
> Generated with Coder Agents
closes CODAGT-142
The Agents right-panel tabs (Git, Terminal, Desktop) reset to the
default tab (Git) every time the user switches between agent sessions.
This happens because `KeyedAgentChatPage` forces a full remount on
`agentId` change, discarding the `useState` that holds the active tab.
Persist the active tab per agent session in `localStorage`, following
the existing `agents.draft-input.<chatID>` pattern. When the user
returns to a session, the last tab they selected is restored. If the
stored tab is temporarily unavailable (e.g. Desktop while the workspace
is stopped), `SidebarTabView`'s existing fallback to the first
available tab applies, and the stored value survives so it can be
honoured once that tab reappears.
Archiving a chat clears its stored tab entry so unarchiving starts
fresh with the default tab.
Relates to #24642
Adds admin UI controls for managing chat auto-archive (days) under
"Lifecycle".
Also adds a "Days" label to the right of the pre-existing unitless
numeric input for consistency.
Exemplary screenshot below. More screens available in Storybook.
<img width="847" height="585" alt="Screenshot 2026-04-24 at 16 48 59"
src="https://github.com/user-attachments/assets/d38de5f8-d379-4b06-b175-ac399f31e578"
/>
Bedrock chat provider requests can inherit Anthropic public API headers
from the process environment, which causes mixed Anthropic and Bedrock
auth headers on signed requests.
Update the Anthropic SDK fork so its Bedrock middleware strips
Anthropic-only headers before signing requests, and keep a chatprovider
regression test for the production request shape.
> Mux is acting on Mike's behalf.
> Worked on by Mux on Mike's behalf.
## Summary
- Disable OpenAI Responses `previous_response_id` chain mode when the
prior assistant response has unresolved local tool calls, so the next
request can include paired tool outputs instead of sending an incomplete
continuation.
- Update the fantasy pin to a Responses replay fix that preserves stored
reasoning references, only replays web search references when paired
with reasoning, and validates local function-call output pairing before
send.
- Add fake OpenAI Responses input validation for the two production 400
shapes and integration coverage for full-history reasoning plus web
search replay.
- Add sanitized diagnostics for the OpenAI Responses continuity errors.
## Tests
- `go test ./providers/openai -run
'TestResponsesToPrompt_(ReasoningWithStore|ReasoningWithWebSearchCombined|WebSearchRequiresReasoningReference|ReasoningWithFunctionCallCombined|WebSearchProviderExecutedToolResults)|TestPrepareParams_(SkipsProviderExecutedToolReferences|ValidatesFunctionCallOutputPairing)|TestValidateResponsesInput_WebSearchReferenceRequiresReasoning'
-count=1`
- `go test ./providers/openai -count=1`
- `GOWORK=off go test ./coderd/x/chatd/chattest -run
TestValidateResponsesAPIInput -count=1`
- `GOWORK=off go test ./coderd/x/chatd -run
'TestOpenAIResponses(NoStaleWebSearchReplay|FullReplayPairsReasoningAndWebSearch|ChainModeSkipsWhenLocalCallPending|ChainModeStillFiresForProviderExecutedOnly)$|TestResolveChainMode_'
-count=1`
- `GOWORK=off go test ./coderd/x/chatd/chatprompt -run
'TestInjectMissingToolResults_' -count=1`
- `GOWORK=off go test ./coderd/x/chatd/chaterror -run
TestClassify_OpenAIResponsesAPIDiagnostics -count=1`
- `GOWORK=off go test ./coderd/x/chatd/... -count=1`
- `git diff --check`
- `git commit` pre-commit hook
`tryAutoPromoteQueuedMessage` in `processChat`'s deferred cleanup could
set a chat back to `pending` without waking the processor. The processor
only noticed on the next 10ms poll, so under load tests like
`TestAutoPromoteQueuedMessageFallsBackForInvalidQueuedModelConfigID`
could time out waiting for the second streaming request (#1500).
Call `p.signalWake()` after the promoted-message publishes when
`promotedMessage != nil`, matching the pattern used by `CreateChat`,
`SendMessage`, `EditMessage`, `PromoteQueued`, and `InterruptChat`. Make
the regression helper `testAutoPromoteQueuedMessageFallback`
deterministic by setting `PendingChatAcquireInterval = time.Hour` and
synchronizing on a `secondRunStarted` channel instead of polling
`requestCount`, so the test fails without the wake instead of relying on
the 10ms ticker.
Closes https://github.com/coder/internal/issues/1500
> Mux is acting on Mike's behalf.
When switching between existing `/agents` chats, unsent file attachments
were kept only in React state and were lost when the chat page
remounted.
This adds chat-scoped draft attachment persistence for the compose
input, keeps edit-mode attachments isolated from compose drafts, warns
when a file cannot be saved to localStorage, and cleans up restored
draft payloads after upload or send.
> Mux is acting on Mike's behalf.
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell.
Fixes the `outsideBox` variant styling for tabs and simplifies the kebab
overflow logic. The overflow calculation now accounts for `column-gap`
between tabs so the menu trigger appears at the correct breakpoint.
- Fix Tailwind hover selector syntax for `outsideBox` variant
(`[&_[data-slot=tabs-trigger]:hover]` instead of
`[&_[data-slot=tabs-trigger]]:hover`)
- Account for `column-gap` in `useKebabMenu` overflow calculation via a
new `getTabGap` helper
- Use content-box width (`getContentBoxWidth`) for the initial overflow
pass so it matches `ResizeObserver`'s `contentRect.width`
- Consolidate `calculateTabValues` into a single-pass loop, removing the
separate `findFirstTabIndex` function
- Drop `FC` wrapper in favor of inline prop destructuring across all tab
components
- Forward `ref` through `TabsList` so consumers can attach refs directly
- Add `useKebabMenu` unit tests covering both all-visible and overflow
scenarios
Anthropic can reject replayed chat histories when a provider-executed
tool call, such as `web_search`, is present without its matching
provider result block.
This sanitizes unpaired Anthropic provider-executed tool calls during
prompt reconstruction, before Anthropic requests, and before persistence
so existing poisoned histories can continue and new malformed turns are
not stored.
Resolves: CODAGT-259
> Mux is acting on Mike's behalf.
## Problem
The CLI does not honor `default` values on template parameters in two
ways:
1. **`--use-parameter-defaults` rejects empty-string defaults.** The
check `parameterValue != ""` means `default = ""` in Terraform falls
through to an interactive prompt. In CI this causes an EOF error.
2. **`--use-parameter-defaults` only exists on `coder create`.** The
`start`, `update`, and `restart` commands never wire it through. SSH
auto-start passes empty `workspaceParameterFlags{}`, so users SSH-ing
into a stopped workspace with new template parameters get stuck in an
interactive prompt they cannot complete.
## Fix
### 1. Fix empty-string default detection and expose flag on all
commands
Replace `parameterValue != ""` with a check based on `!tvp.Required`. A
parameter with `Required==false` always has a valid default in
Terraform, even if that default is `""`. Also respect CLI defaults
provided via `--parameter-default`.
Move `--use-parameter-defaults` from a standalone option on `create`
into the shared `workspaceParameterFlags` struct. This exposes the flag
(and `CODER_WORKSPACE_USE_PARAMETER_DEFAULTS`) on `start`, `update`, and
`restart` via `allOptions()`. Wire it through
`buildWorkspaceStartRequest` so the resolver receives it.
### 2. SSH auto-start always uses defaults
Set `useParameterDefaults: true` on both `startWorkspace` calls in the
SSH auto-start path (initial start and the forbidden/upgrade fallback).
SSH is non-interactive and should never prompt.
Fixes https://linear.app/codercom/issue/DEVEX-180
Fixes https://github.com/coder/coder/issues/22272
<details><summary>Implementation notes</summary>
### Scoping decisions
- **`--yes` does not imply `--use-parameter-defaults`**: Making `--yes`
auto-accept defaults exposes a validation gap in the dynamic parameter
path (client-side validation happens during prompting, and skipping
prompts bypasses it). This is deferred to a follow-up that also
addresses `codersdk.ValidateWorkspaceBuildParameter` integration in the
resolver. Tracked in PLAT-114.
- **Explicit overrides always win**: `--parameter`,
`--rich-parameter-file`, and `--preset` are resolved in stages 1-5 of
the resolver, before `resolveWithInput` runs. No change needed for
precedence.
- **`!tvp.Required` vs `parameterValue != ""`**: The `Required` field is
set by the Terraform provider based on whether a `default` is present.
This is the canonical signal for "has a default," not the string value
itself.
</details>
> Generated with [Coder Agents](https://coder.com/agents)
Bumps the `charm.land/fantasy` replace directive to pick up
<https://github.com/coder/fantasy/pull/29>, which adds `gpt-5.5` and
`gpt-5.5-pro` to `responsesReasoningModelIDs`.
Without this, chatd's `useOpenAIResponsesOptions` returns false for
GPT-5.5, so it falls back to Chat Completions and never attaches
`ResponsesProviderOptions` (losing `store=true` + `previous_response_id`
chaining and other Responses-only features).
## Changes
- `go.mod`: `github.com/coder/fantasy
v0.0.0-20260416152503-959aa39579d2` →
`v0.0.0-20260424191546-5ab464a305f4`
- `go.sum`: updated hashes
Verified `go build ./coderd/x/chatd/...` passes locally.
Created on behalf of @ibetitsmike
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Once a user has touched a field, it is better to leave it alone and display explicit validation errors over silently overwriting their inputs. Same for auto-filled values (whether from query parameters or a previous build).