fix: infer workspace claim time from build history for /agents delete dialog - #25057
Conversation
Docs preview📖 View docs preview for |
abac1b2 to
c486b1d
Compare
johnstcn
left a comment
There was a problem hiding this comment.
I looked at the up migration and started thinking about how to best backfill this data:
- Look at build number 1 and check if it was a start transition initiated by the prebuilds user. If not,
claimed_at = NULL. - Otherwise, check build number 2, validate that it was not initiated by the prebuilds user, and use
build_2.created_atasclaimed_at.
However, if that's the case, can we not just compute that in the UI?
… history Replaces the previous draft of this PR (a backend workspaces.claimed_at column plus migration plus SDK plumbing) with a frontend-only heuristic per Cian's review. The /agents archive-and-delete molly-guard previously compared workspace.created_at against chat.created_at to decide whether to require typing the workspace name. ClaimPrebuiltWorkspace never updates workspace.created_at, so claimed prebuilds always looked pre-existing and the dialog misfired. Build history already records the truth: build #1's initiator is the prebuilds system user iff the workspace was a prebuild, and build #2 is the claim. Compute that in the resolver and compare its created_at against the chat. From-scratch workspaces fall through to workspace.created_at as before. The prebuilds system user UUID is hardcoded on the frontend; it lives in coderd/database/constants.go on the backend and has not changed since the prebuild feature shipped. If it ever moves, both sides have to move together. 🤖 Generated with the help of Coder Agents.
c486b1d to
c1b07bf
Compare
|
@johnstcn good call, you're right that the column is unnecessary. Pivoted the PR to the frontend-only approach you sketched: derive the acquisition time from build history ( The one wart is hardcoding the prebuilds system user UUID on the frontend. Open to alternatives if you want a backend-exposed signal instead, but it's been stable since the prebuild feature shipped. Force-pushed away the column-based draft; new diff is at Comment authored by Coder Agents on behalf of @kylecarbs. |
DanielleMaywood
left a comment
There was a problem hiding this comment.
The change seems reasonable enough
| // The hard-coded UUID of the Coder prebuilds system user. Prebuilt | ||
| // workspaces are owned by this user until claim. Build #1 of a | ||
| // claimed workspace is permanently attributed to this user as the | ||
| // initiator, which is how we recognize prebuild claims after the | ||
| // fact. | ||
| // | ||
| // This UUID is stable and lives in coderd/database/constants.go on | ||
| // the backend. If it ever changes, both sides must move in lockstep. | ||
| const PREBUILDS_SYSTEM_USER_ID = "c42fdf75-3097-471c-8c33-fb52454d81c0"; |
There was a problem hiding this comment.
I know we're unlikely to change the prebuilds system user id but it kinda sucks we've got this second source of truth (We have another constant in the backend).
There was a problem hiding this comment.
Done in c2d2f5c. Added PrebuildsSystemUserID as a string constant in codersdk/prebuilds.go, switched coderd/database/constants.go to parse that constant via uuid.MustParse(codersdk.PrebuildsSystemUserID) so the two cannot drift, and the frontend now imports the typegen'd value from typesGenerated.ts.
Comment authored by Coder Agents on behalf of @kylecarbs.
| const build2 = builds.find((b) => b.build_number === 2); | ||
| return build2 ? build2.created_at : null; |
There was a problem hiding this comment.
I can't entirely remember how prebuilds work but is it possible for a prebuild to have two builds?
There was a problem hiding this comment.
A number of prebuilds queries filter for workspace_builds using WHERE build_number = 1 so I think the assumption is safe.
| workspaceAcquiredAt, | ||
| } from "./agentWorkspaceUtils"; | ||
|
|
||
| const PREBUILDS_USER = "c42fdf75-3097-471c-8c33-fb52454d81c0"; |
There was a problem hiding this comment.
nit: just use PREBUILDS_SYSTEM_USER_ID?
There was a problem hiding this comment.
Done. Test now imports PrebuildsSystemUserID from typesGenerated.ts instead of redeclaring the literal.
Comment authored by Coder Agents on behalf of @kylecarbs.
| // The hard-coded UUID of the Coder prebuilds system user. Prebuilt | ||
| // workspaces are owned by this user until claim. Build #1 of a | ||
| // claimed workspace is permanently attributed to this user as the | ||
| // initiator, which is how we recognize prebuild claims after the | ||
| // fact. | ||
| // | ||
| // This UUID is stable and lives in coderd/database/constants.go on | ||
| // the backend. If it ever changes, both sides must move in lockstep. | ||
| const PREBUILDS_SYSTEM_USER_ID = "c42fdf75-3097-471c-8c33-fb52454d81c0"; |
| const build2 = builds.find((b) => b.build_number === 2); | ||
| return build2 ? build2.created_at : null; |
There was a problem hiding this comment.
A number of prebuilds queries filter for workspace_builds using WHERE build_number = 1 so I think the assumption is safe.
Per Cian's review, expose the prebuilds system user UUID through codersdk so it gets typegen'd into typesGenerated.ts. Single source of truth: codersdk.PrebuildsSystemUserID is the string constant, and coderd/database.PrebuildsSystemUserID parses it at package init via uuid.MustParse(codersdk.PrebuildsSystemUserID), so any drift fails fast at load time. The frontend agentWorkspaceUtils helper and its test now both import PrebuildsSystemUserID from typesGenerated instead of duplicating the literal. 🤖 Generated with the help of Coder Agents.
Closes CODAGT-317.
Problem
The
/agentsarchive-and-delete molly-guard (typing the workspace name) was firing for chats that had clearly created their own workspace. The heuristic inresolveArchiveAndDeleteActiondecides whether confirmation is needed by comparing the workspace'screated_atagainst the chat'screated_at:That assumption breaks for prebuilt workspaces.
ClaimPrebuiltWorkspacerewritesowner_id,name,updated_at,last_used_at, etc., but never touchescreated_at, which still reflects when the prebuild was provisioned by the reconciler, often hours before the chat exists. Result: every prebuild-claimed workspace looks pre-existing, so the molly-guard fires.Concrete example from a real chat:
chat.created_at2026-05-07T15:12:23Zworkspace.created_at(provision)2026-05-07T14:22:24Zlatest_build.created_at(claim)2026-05-07T15:19:09Z14:22:24 < 15:12:23soisWorkspaceAutoCreatedreturned false even though the chat issued the claim.Fix (frontend-only)
Derive the moment a workspace was acquired from existing build history rather than relying on
workspace.created_at:build_2.created_at(the claim build) as the acquisition time.workspace.created_at(unchanged behavior).null(force confirmation; safe degradation for a destructive flow).The resolver fetches the build list via the existing
getWorkspaceBuildsendpoint when the dialog might fire. No new column, no migration, no schema change. Works retroactively for all existing claimed prebuilds; no backfill needed.The prebuilds system user UUID is exposed via
codersdk.PrebuildsSystemUserIDand typegen'd totypesGenerated.ts.coderd/database.PrebuildsSystemUserIDparses that constant viauuid.MustParseso the two cannot drift; if the codersdk literal ever changes, package init fails fast.History
The first draft of this PR added a
workspaces.claimed_atcolumn populated byClaimPrebuiltWorkspace. After review feedback from @johnstcn pointing out that the same fact is already implicit in build history, I pivoted to the frontend-only approach. Subsequent review notes consolidated the prebuilds system user UUID into a single typegen'd constant.Why not the other open PRs
chatKeycache fallback) only fixes a different cache-miss path; it explicitly notes it does not addresscreated_at < chat.created_at.chats.workspace_auto_createdboolean) puts the truth on the wrong side of the schema: "this workspace was claimed at time T" is a property of the workspace, not the chat. The MCP plumbing it adds is also unnecessary now that the same answer is available from build history.Test plan
pnpm vitest run --project=unit src/pages/AgentsPage/utils/agentWorkspaceUtils.test.ts— 40/40 pass; new cases cover prebuild claim before/after chat, unclaimed prebuild, missing-build-history fallback, and the fetch-skip when the chat is not in cache.pnpm lint:types,pnpm check,make pre-commit.Disclosure
Opened on behalf of @kylecarbs by Coder Agents.