From f6510494f8ad0b7eab4bf618cd7addacb69c754f Mon Sep 17 00:00:00 2001 From: Ahmed Abushagur Date: Mon, 23 Mar 2026 15:47:01 -0700 Subject: [PATCH] fix: skip cloud-init wait in Hetzner Docker mode Hetzner's waitForReady() was missing the useDocker check that GCP already has. Non-minimal agents (openclaw, codex) with --beta docker waited 5 minutes for a cloud-init marker that never appears on Docker CE app images. Adds useDocker to the condition and a source-level regression test verifying both Hetzner and GCP include the check. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../__tests__/docker-cloudinit-skip.test.ts | 30 +++++++++++++++++++ packages/cli/src/hetzner/main.ts | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/__tests__/docker-cloudinit-skip.test.ts diff --git a/packages/cli/src/__tests__/docker-cloudinit-skip.test.ts b/packages/cli/src/__tests__/docker-cloudinit-skip.test.ts new file mode 100644 index 000000000..36b9fb250 --- /dev/null +++ b/packages/cli/src/__tests__/docker-cloudinit-skip.test.ts @@ -0,0 +1,30 @@ +/** + * docker-cloudinit-skip.test.ts — Verify Docker mode skips cloud-init wait. + * + * When --beta docker is active, waitForReady() must skip cloud-init polling + * and only wait for SSH. This test reads the orchestrator source files to + * verify the useDocker check is present in the waitForReady condition. + * + * Without this, non-minimal agents (openclaw, codex) wait 5 minutes for a + * cloud-init marker that never appears when using Docker app images. + */ + +import { describe, expect, it } from "bun:test"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const CLI_SRC = resolve(import.meta.dir, ".."); + +describe("Docker mode skips cloud-init wait", () => { + it("Hetzner waitForReady includes useDocker in skip condition", () => { + const source = readFileSync(resolve(CLI_SRC, "hetzner/main.ts"), "utf-8"); + // The waitForReady condition must include useDocker to skip cloud-init + // when Docker mode is active (matching GCP's implementation) + expect(source).toContain("useDocker || snapshotId || cloud.skipCloudInit"); + }); + + it("GCP waitForReady includes useDocker in skip condition", () => { + const source = readFileSync(resolve(CLI_SRC, "gcp/main.ts"), "utf-8"); + expect(source).toContain("useDocker || cloud.skipCloudInit"); + }); +}); diff --git a/packages/cli/src/hetzner/main.ts b/packages/cli/src/hetzner/main.ts index d65f5f78a..d0fb96b24 100644 --- a/packages/cli/src/hetzner/main.ts +++ b/packages/cli/src/hetzner/main.ts @@ -87,7 +87,7 @@ async function main() { }, getServerName, async waitForReady() { - if (snapshotId || cloud.skipCloudInit) { + if (useDocker || snapshotId || cloud.skipCloudInit) { await waitForSshOnly(); } else { await waitForCloudInit();