handleConnected handles the Connected state and startup script logic. This is a terminal state, returns nil on success or error on failure. nolint:revive // Control flag is acceptable for internal method.
(ctx context.Context, agent codersdk.WorkspaceAgent, showStartupLogs bool, fetchedAgent chan fetchAgentResult)
| 190 | // |
| 191 | //nolint:revive // Control flag is acceptable for internal method. |
| 192 | func (aw *agentWaiter) handleConnected(ctx context.Context, agent codersdk.WorkspaceAgent, showStartupLogs bool, fetchedAgent chan fetchAgentResult) error { |
| 193 | if !showStartupLogs && agent.LifecycleState == codersdk.WorkspaceAgentLifecycleReady { |
| 194 | // The workspace is ready, there's nothing to do but connect. |
| 195 | return nil |
| 196 | } |
| 197 | |
| 198 | // Determine if we should follow/stream logs (blocking mode). |
| 199 | follow := aw.opts.Wait && agent.LifecycleState.Starting() |
| 200 | |
| 201 | stage := "Running workspace agent startup scripts" |
| 202 | if !follow { |
| 203 | stage += " (non-blocking)" |
| 204 | } |
| 205 | aw.sw.Start(stage) |
| 206 | |
| 207 | if follow { |
| 208 | aw.sw.Log(time.Time{}, codersdk.LogLevelInfo, "==> ℹ︎ To connect immediately, reconnect with --wait=no or CODER_SSH_WAIT=no, see --help for more information.") |
| 209 | } |
| 210 | |
| 211 | // In non-blocking mode (Wait=false), we don't stream logs. This prevents |
| 212 | // dumping a wall of logs on users who explicitly pass --wait=no. The stage |
| 213 | // indicator is still shown, just not the log content. See issue #13580. |
| 214 | if aw.opts.Wait { |
| 215 | var err error |
| 216 | agent, err = aw.streamLogs(ctx, agent, follow, fetchedAgent) |
| 217 | if err != nil { |
| 218 | return err |
| 219 | } |
| 220 | |
| 221 | // If we were following, wait until startup completes. |
| 222 | if follow { |
| 223 | agent, err = aw.pollWhile(ctx, agent, func(agent codersdk.WorkspaceAgent) bool { |
| 224 | return agent.LifecycleState.Starting() |
| 225 | }) |
| 226 | if err != nil { |
| 227 | return err |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | // Handle final lifecycle state. |
| 233 | switch agent.LifecycleState { |
| 234 | case codersdk.WorkspaceAgentLifecycleReady: |
| 235 | aw.sw.Complete(stage, safeDuration(aw.sw, agent.ReadyAt, agent.StartedAt)) |
| 236 | case codersdk.WorkspaceAgentLifecycleStartTimeout: |
| 237 | // Backwards compatibility: Avoid printing warning if |
| 238 | // coderd is old and doesn't set ReadyAt for timeouts. |
| 239 | if agent.ReadyAt == nil { |
| 240 | aw.sw.Fail(stage, 0) |
| 241 | } else { |
| 242 | aw.sw.Fail(stage, safeDuration(aw.sw, agent.ReadyAt, agent.StartedAt)) |
| 243 | } |
| 244 | aw.sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script timed out and your workspace may be incomplete.") |
| 245 | case codersdk.WorkspaceAgentLifecycleStartError: |
| 246 | aw.sw.Fail(stage, safeDuration(aw.sw, agent.ReadyAt, agent.StartedAt)) |
| 247 | aw.sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script exited with an error and your workspace may be incomplete.") |
| 248 | aw.sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, fmt.Sprintf("%s/admin/templates/troubleshooting#startup-script-exited-with-an-error", aw.opts.DocsURL))) |
| 249 | default: |
no test coverage detected