@Summary Get workspace agent reinitialization @ID get-workspace-agent-reinitialization @Security CoderSessionToken @Produce json @Tags Agents @Param wait query bool false "Opt in to durable reinit checks" @Success 200 {object} agentsdk.ReinitializationEvent @Failure 409 {object} codersdk.Response @R
(rw http.ResponseWriter, r *http.Request)
| 1479 | // @Failure 409 {object} codersdk.Response |
| 1480 | // @Router /api/v2/workspaceagents/me/reinit [get] |
| 1481 | func (api *API) workspaceAgentReinit(rw http.ResponseWriter, r *http.Request) { |
| 1482 | // Allow us to interrupt watch via cancel. |
| 1483 | ctx, cancel := context.WithCancel(r.Context()) |
| 1484 | defer cancel() |
| 1485 | r = r.WithContext(ctx) // Rewire context for SSE cancellation. |
| 1486 | |
| 1487 | workspaceAgent := httpmw.WorkspaceAgent(r) |
| 1488 | log := api.Logger.Named("workspace_agent_reinit_watcher").With( |
| 1489 | slog.F("workspace_agent_id", workspaceAgent.ID), |
| 1490 | ) |
| 1491 | |
| 1492 | workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) |
| 1493 | if err != nil { |
| 1494 | log.Error(ctx, "failed to retrieve workspace from agent token", slog.Error(err)) |
| 1495 | httpapi.InternalServerError(rw, xerrors.New("failed to determine workspace from agent token")) |
| 1496 | return |
| 1497 | } |
| 1498 | log = log.With(slog.F("workspace_id", workspace.ID)) |
| 1499 | |
| 1500 | log.Info(ctx, "agent waiting for reinit instruction") |
| 1501 | |
| 1502 | // Subscribe to claim events BEFORE any durable checks to avoid a |
| 1503 | // TOCTOU race: without this, a claim could fire between the |
| 1504 | // IsPrebuild() check and the subscribe call, and we'd miss the |
| 1505 | // pubsub event entirely. By subscribing first, any event that |
| 1506 | // fires during the checks below is buffered in the channel. |
| 1507 | pubsubCh, cancelSub, err := prebuilds.NewPubsubWorkspaceClaimListener(api.Pubsub, log).ListenForWorkspaceClaims(ctx, workspace.ID) |
| 1508 | if err != nil { |
| 1509 | log.Error(ctx, "subscribe to prebuild claimed channel", slog.Error(err)) |
| 1510 | httpapi.InternalServerError(rw, xerrors.New("failed to subscribe to prebuild claimed channel")) |
| 1511 | return |
| 1512 | } |
| 1513 | defer cancelSub() |
| 1514 | |
| 1515 | reinitEvents := pubsubCh |
| 1516 | |
| 1517 | // Only perform the durable claim check when the agent opts in via |
| 1518 | // the "wait" query parameter. Older agents don't send the |
| 1519 | // "wait" query parameter and lack the duplicate-reinit guard, so |
| 1520 | // they would enter an infinite reinit loop if we pre-seeded the |
| 1521 | // channel on every connection. |
| 1522 | waitParam, _ := strconv.ParseBool(r.URL.Query().Get("wait")) |
| 1523 | if waitParam && !workspace.IsPrebuild() { |
| 1524 | firstBuild, err := api.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, |
| 1525 | database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ |
| 1526 | WorkspaceID: workspace.ID, |
| 1527 | BuildNumber: 1, |
| 1528 | }) |
| 1529 | if err != nil { |
| 1530 | log.Error(ctx, "failed to get first workspace build", slog.Error(err)) |
| 1531 | httpapi.InternalServerError(rw, xerrors.New("failed to get first workspace build")) |
| 1532 | return |
| 1533 | } |
| 1534 | if firstBuild.InitiatorID != database.PrebuildsSystemUserID { |
| 1535 | // Not a claimed prebuild — this is a regular workspace. |
| 1536 | // Return 409 so the agent stops reconnecting to this |
| 1537 | // endpoint. |
| 1538 | httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ |
nothing calls this directly
no test coverage detected