* Core ensureReady logic - called once (protected by ensureReadyPromise). * * Flow: * 1. Check status via `coder list` - short-circuit for "running" or "not_found" * 2. If "stopping"/"canceling": poll until it clears (coder ssh can't autostart during these) * 3. Run `coder ssh --wait=
(
workspaceName: string,
options?: EnsureReadyOptions
)
| 268 | * - starting/pending: waits for build completion + startup scripts |
| 269 | */ |
| 270 | private async doEnsureReady( |
| 271 | workspaceName: string, |
| 272 | options?: EnsureReadyOptions |
| 273 | ): Promise<EnsureReadyResult> { |
| 274 | const statusSink = options?.statusSink; |
| 275 | const signal = options?.signal; |
| 276 | const startTime = Date.now(); |
| 277 | |
| 278 | const emitStatus = (phase: RuntimeStatusEvent["phase"], detail?: string) => { |
| 279 | statusSink?.({ phase, runtimeType: "ssh", detail }); |
| 280 | }; |
| 281 | |
| 282 | // Helper: check if we've exceeded overall timeout |
| 283 | const isTimedOut = () => Date.now() - startTime > CODER_ENSURE_READY_TIMEOUT_MS; |
| 284 | const remainingMs = () => Math.max(0, CODER_ENSURE_READY_TIMEOUT_MS - (Date.now() - startTime)); |
| 285 | |
| 286 | // Step 1: Check current status for short-circuits |
| 287 | emitStatus("checking"); |
| 288 | |
| 289 | if (signal?.aborted) { |
| 290 | emitStatus("error"); |
| 291 | return { ready: false, error: "Aborted", errorType: "runtime_start_failed" }; |
| 292 | } |
| 293 | |
| 294 | let statusResult = await this.coderService.getWorkspaceStatus(workspaceName, { |
| 295 | timeoutMs: Math.min(remainingMs(), 10_000), |
| 296 | signal, |
| 297 | }); |
| 298 | |
| 299 | // Short-circuit: already running |
| 300 | if (statusResult.kind === "ok" && statusResult.status === "running") { |
| 301 | return this.markReadyIfRepoPresent(workspaceName, options, emitStatus); |
| 302 | } |
| 303 | |
| 304 | // Short-circuit: workspace doesn't exist |
| 305 | if (statusResult.kind === "not_found") { |
| 306 | emitStatus("error"); |
| 307 | return this.coderWorkspaceNotFound(workspaceName); |
| 308 | } |
| 309 | |
| 310 | // For status check errors (timeout, auth issues), proceed optimistically |
| 311 | // and let SSH fail naturally to avoid blocking the happy path |
| 312 | if (statusResult.kind === "error") { |
| 313 | if (signal?.aborted) { |
| 314 | emitStatus("error"); |
| 315 | return { ready: false, error: "Aborted", errorType: "runtime_start_failed" }; |
| 316 | } |
| 317 | log.debug("Coder workspace status unknown, proceeding optimistically", { |
| 318 | workspaceName, |
| 319 | error: statusResult.error, |
| 320 | }); |
| 321 | } |
| 322 | |
| 323 | // Step 2: Wait for "stopping"/"canceling" to clear (coder ssh can't autostart during these) |
| 324 | try { |
| 325 | statusResult = await this.waitForStoppingOrCancelingToClear(workspaceName, statusResult, { |
| 326 | abortSignal: signal, |
| 327 | timeoutMs: () => Math.min(remainingMs(), 10_000), |
no test coverage detected