From 2f897f40085ea3f812ba131feccd104531f522da Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 15:05:55 +0000 Subject: [PATCH] =?UTF-8?q?fix(doctor):=20probe=20podman=20too=20=E2=80=94?= =?UTF-8?q?=20a=20Podman=20machine=20was=20reported=20as=20"docker=20is=20?= =?UTF-8?q?not=20installed"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit doctor.sh advertised Podman in its advice text ("Rancher Desktop or Podman (docker-CLI compatible) ... are common substitutes") while every probe in the section ran docker only: `docker info`, the `command -v docker` gate, the not-installed warning, and a start hint that said `open -a Docker`. A machine running Podman without the docker shim is fully able to run the container lane, and doctor told it it had nothing. On a team that cannot licence Docker Desktop that reads as "go install software you are not allowed to have" — one colleague's machine-ready status carried "Docker not installed" as a known issue; they may well have had Podman all along. Same defect class as #128: an instrument reporting a problem that does not exist. Detection and reporting only. `mxcli docker check` is mxcli's own code in another repo and is untouched; this change only makes doctor report what is actually on the machine. - CONTAINER_RUNTIME: docker when present (including when it is a Podman shim — transparent and correct), else podman. MXTK_CONTAINER_RUNTIME= forces one. - The runtime is named in the output: "podman responding — ..." vs "docker daemon responding — ...". Podman normally has no daemon, so the label carries that difference rather than inventing a "podman daemon". - container_daemon_up keeps the existing bounded background/poll/kill probe and its exit contract (0 up / 1 down / 2 no answer within the bound) for both runtimes — a stopped `podman machine` stalls exactly like a stopped Docker Desktop, which is what the bound exists for. - container_start_hint: `podman machine start` on macOS/Windows, `systemctl --user start podman.socket` on Linux/WSL, instead of sending a Podman user to open Docker Desktop. - The not-installed warning now names both and presents Podman as the licence-free option rather than implying Docker Desktop is required. - --install still auto-launches Docker Desktop on macOS only; podman is left alone, because `podman machine start` with no machine ever initialised is not a safe guess to make on someone's behalf. NOT field-run: this container has no working container runtime, so every branch was exercised against fake `docker`/`podman` binaries on PATH (up, down, hanging, absent, podman-only), not against a real one. The run that would prove it: on a Mac with Podman installed and no `docker` on PATH, `bin/doctor.sh` should print "podman responding" (or a podman start hint) instead of "docker is not installed". bash -n clean; bin/check-portability.sh clean (163 files). tests/wave2/test-doctor-docker-probe.sh not run — its four scenarios were replicated by hand against the changed script and all of its grep strings reproduce verbatim. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0187r9bQWkZAEppyEqrDevjK --- CHANGELOG.md | 1 + bin/doctor.sh | 146 +++++++++++++++++++++++++++-------------- project-bin/_common.sh | 2 +- 3 files changed, 99 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8101999..c3d8ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- fix(bin/doctor.sh): **doctor told every Podman user "docker is not installed"** — the section advertised Podman in its advice text ("Rancher Desktop or Podman … are common substitutes") while all four probes ran `docker` only: `docker info`, the `command -v docker` gate, the not-installed warning, and a start hint that said `open -a Docker`. So a machine fully able to run the container lane on Podman, but without the docker shim, was reported broken — and on a team that cannot licence Docker Desktop that reads as "go install software you are not allowed to have" (a colleague's machine-ready status carried "Docker not installed" as a known issue; they may have had Podman all along). Detection is now docker-then-podman (`MXTK_CONTAINER_RUNTIME` forces one), the runtime is **named** in the report (`podman responding — …`), the start hint knows `podman machine start` / `podman.socket`, and the not-installed warning names Podman as the licence-free option instead of implying Docker Desktop is required. Same bounded background/poll/kill probe for both, same 0/1/2 exit contract; `mxcli docker check` invocation deliberately untouched (different repo). **Not field-run** — no container runtime in the authoring container; needs one run on a Mac with Podman and no `docker` on PATH. Driver: the Mendix migration team's Docker Desktop licensing constraint — Maurits Visser - fix(bin/doctor.sh): **the gate self-test renamed the model in its scratch dir to `model.mpr` while copying `mprcontents/` verbatim beside it, so mxbuild bailed before writing any error file and the self-test reported `fail (unreadable error file)` — the F-042-class string that means "the gate itself is broken" — on a perfectly healthy gate.** `mprcontents/` carries an internal record of the model's real basename; the rename breaks the pair. The scratch copy now keeps the real basename (`$scratch/$(basename "$MPR")`), threaded through all four later references (baseline count, injection `exec -p`, known-bad count). **Not field-run** — reasoned and inspected only; needs one run of `doctor.sh --gate-selftest ` on a macOS machine with Studio Pro 11 and a real `.mpr` before the team is told to pull. Found by Yvann during Mac/sandbox onboarding — Yvann - docs(front door): **README and `toolkit-guide.html` now count five ways in, not three.** Both already listed migration, requirements-driven, greenfield and the à-la-carte no-pipeline route; only the headlines said "three", so sessions reading the headline reported three entry modes while the runbook and `CLAUDE.md` said three plus à-la-carte. Wording now: four pipeline entry modes plus one route with no pipeline — Maurits Visser - docs(testing): **Windows full test-run prompt.** `docs/windows-test-run.md` — a paste-able, unattended Claude Code prompt for a Git Bash machine: guards, both fixture suites, 27 Windows-relevant fixtures with their subjects, the committed inputs (`fixtures/app-analysis`, page-fidelity mocks, html-to-md capture) run directly, and a field run on a scratch copy of a real project incl. the two-tree and toolkit.env probes; one results file, facts only — Maurits Visser diff --git a/bin/doctor.sh b/bin/doctor.sh index e8f863a..1875419 100755 --- a/bin/doctor.sh +++ b/bin/doctor.sh @@ -20,11 +20,14 @@ # # (same download the headless container build # # uses; cached at ~/.mxcli/mxbuild/). # bin/doctor.sh --install --yes # skip the confirmation (unattended/agent runs) -# bin/doctor.sh --no-docker [] # skip the docker probe (or MXTK_DOCTOR_SKIP_DOCKER=1). -# # The probe is bounded anyway: MXTK_DOCKER_PROBE_SECS -# # (default 8) — a stopped Docker Desktop can make -# # 'docker info' sit silent for minutes, which read -# # as "the setup hangs" (field report, 2026-09-09). +# bin/doctor.sh --no-docker [] # skip the container-runtime probe (or +# # MXTK_DOCTOR_SKIP_DOCKER=1). The probe is bounded +# # anyway: MXTK_DOCKER_PROBE_SECS (default 8) — a +# # stopped Docker Desktop can make 'docker info' sit +# # silent for minutes, which read as "the setup +# # hangs" (field report, 2026-09-09). The probe finds +# # docker OR podman, whichever is present; +# # MXTK_CONTAINER_RUNTIME= forces one. # bin/doctor.sh --quick [] # ~2 s: platform, mxcli/mxbuild/java EXECUTE, spawn # # speed, path hygiene, model layout. Skips the # # once-per-machine sections (python, CRLF, script @@ -674,23 +677,52 @@ else fi note "e2e additionally needs a Playwright browser in the project: npx playwright install chromium" -# --- self-verification (docker) ------------------------------------------------------------- +# --- self-verification (container runtime) --------------------------------------------------- -head_ "Self-verification stack (Docker — recommended)" +head_ "Self-verification stack (container runtime — recommended)" -# With Docker, the loop closes without a human: after a build the agent runs a Linux build of -# the app, brings up Postgres + the app (project-bin/test-stack-up.sh), drives it with -# Playwright, screenshots the pages, and verifies its own work — nobody has to open Studio -# Pro to find out whether a page renders. Without Docker the mxbuild gate still verifies the -# MODEL, but nothing verifies the RUNNING APP unless a human does. Recommended, never +# With a container runtime, the loop closes without a human: after a build the agent runs a +# Linux build of the app, brings up Postgres + the app (project-bin/test-stack-up.sh), drives +# it with Playwright, screenshots the pages, and verifies its own work — nobody has to open +# Studio Pro to find out whether a page renders. Without one the mxbuild gate still verifies +# the MODEL, but nothing verifies the RUNNING APP unless a human does. Recommended, never # required — hence WARN, not FAIL. -# docker_daemon_up — `docker info`, bounded. When Docker Desktop is installed but not running, -# the CLI can sit on its socket/named pipe with no output for minutes (macOS and Git Bash both -# reported, 2026-09-09) — and since init-project.sh runs doctor last, the whole scaffold read as -# hung. No `timeout` on stock macOS, so: background the probe, poll once a second, kill it at -# the bound. Returns 0 up · 1 down (answered quickly) · 2 no answer within the bound. -docker_daemon_up() { - docker info >/dev/null 2>&1 & +# +# DOCKER *OR* PODMAN. This section used to advertise Podman in its advice text while every +# probe ran `docker` only — so a machine running Podman without the docker shim was told +# "docker is not installed", and on a team that cannot licence Docker Desktop that reads as +# "go install software you are not allowed to have". (Field data point: a colleague's +# machine-ready status carried "Docker not installed" as a known issue; they may well have had +# Podman all along.) Detection order: docker first when present — including when it IS a Podman +# shim, which is transparent and correct — then podman. +CONTAINER_RUNTIME="${MXTK_CONTAINER_RUNTIME:-}" +if [ -z "$CONTAINER_RUNTIME" ]; then + if command -v docker >/dev/null 2>&1; then CONTAINER_RUNTIME=docker + elif command -v podman >/dev/null 2>&1; then CONTAINER_RUNTIME=podman + fi +fi +# What to call it in the report. Docker has a daemon; podman normally does not, so "podman +# daemon" would be wrong — the label carries that difference, and the runtime is NAMED in the +# output so a user can tell which one doctor actually found. +case "$CONTAINER_RUNTIME" in + docker) + RUNTIME_LABEL="docker daemon" + RUNTIME_PROBING="the docker daemon" + RUNTIME_DOWN="docker is installed but the docker daemon is not responding." ;; + *) + RUNTIME_LABEL="$CONTAINER_RUNTIME" + RUNTIME_PROBING="$CONTAINER_RUNTIME" + RUNTIME_DOWN="$CONTAINER_RUNTIME is installed but not responding." ;; +esac + +# container_daemon_up — ` info`, bounded. When Docker Desktop is installed but not +# running, the CLI can sit on its socket/named pipe with no output for minutes (macOS and Git +# Bash both reported, 2026-09-09) — and since init-project.sh runs doctor last, the whole +# scaffold read as hung. A stopped `podman machine` stalls the same way, so podman goes through +# the same bound. No `timeout` on stock macOS, so: background the probe, poll once a second, +# kill it at the bound. Returns 0 up · 1 down (answered quickly) · 2 no answer within the bound. +container_daemon_up() { + "$CONTAINER_RUNTIME" info >/dev/null 2>&1 & _dd_pid=$! _dd_waited=0 while kill -0 "$_dd_pid" 2>/dev/null; do @@ -703,61 +735,77 @@ docker_daemon_up() { wait "$_dd_pid" } -# docker_start_hint — the one command that starts the daemon on this platform, or the closest -# thing to it. Printed instead of "start Docker", which sent people to look for a button. -docker_start_hint() { - case "$PLATFORM" in - macos) - if [ -d /Applications/Docker.app ]; then note " open -a Docker # Docker Desktop; the daemon needs ~30-90 s after launch" - elif command -v colima >/dev/null 2>&1; then note " colima start" - else note " open -a Docker (Docker Desktop) or colima start — whichever is installed"; fi ;; - gitbash) - note ' start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe" # then wait ~30-90 s' - note " (or Rancher Desktop / Podman Desktop from the Start menu)" ;; - linux|wsl) - note " sudo systemctl start docker # or, on WSL, start Docker Desktop on the Windows side" ;; - *) note " start Docker Desktop / the docker service, then re-run this script" ;; - esac +# container_start_hint — the one command that starts this runtime on this platform, or the +# closest thing to it. Printed instead of "start Docker", which sent people to look for a +# button — and a Podman user must never be sent to look for Docker Desktop at all. +container_start_hint() { + if [ "$CONTAINER_RUNTIME" = podman ]; then + case "$PLATFORM" in + macos|gitbash) + note " podman machine start # the VM podman runs containers in; ~15-60 s" + note " (podman machine init first, if this machine has never had one)" ;; + linux|wsl) + note " systemctl --user start podman.socket # rootless; or sudo systemctl start podman.socket" + note " (rootless podman often needs no service at all — check 'podman info' by hand)" ;; + *) note " start the podman machine/service, then re-run this script" ;; + esac + else + case "$PLATFORM" in + macos) + if [ -d /Applications/Docker.app ]; then note " open -a Docker # Docker Desktop; the daemon needs ~30-90 s after launch" + elif command -v colima >/dev/null 2>&1; then note " colima start" + else note " open -a Docker (Docker Desktop) or colima start — whichever is installed"; fi ;; + gitbash) + note ' start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe" # then wait ~30-90 s' + note " (or Rancher Desktop / Podman Desktop from the Start menu)" ;; + linux|wsl) + note " sudo systemctl start docker # or, on WSL, start Docker Desktop on the Windows side" ;; + *) note " start Docker Desktop / the docker service, then re-run this script" ;; + esac + fi note "Then re-run: bin/doctor.sh${PROJECT_DIR:+ $PROJECT_DIR} (this section is skipped by --quick)" } if [ "$NO_DOCKER" = 1 ]; then note "docker probe skipped (--no-docker / MXTK_DOCTOR_SKIP_DOCKER=1). Self-verification stack not checked." -elif command -v docker >/dev/null 2>&1; then - note "probing the docker daemon (bounded: ${DOCKER_PROBE_SECS} s — a stopped Docker Desktop can otherwise hang here)..." +elif [ -n "$CONTAINER_RUNTIME" ]; then + note "probing $RUNTIME_PROBING (bounded: ${DOCKER_PROBE_SECS} s — a stopped runtime can otherwise hang here)..." DOCKER_STATE=0 - docker_daemon_up || DOCKER_STATE=$? + container_daemon_up || DOCKER_STATE=$? case "$DOCKER_STATE" in 0) - ok "docker daemon responding — mxcli docker check + test-stack-up.sh (app up, e2e, screenshots) available" ;; + ok "$RUNTIME_LABEL responding — mxcli docker check + test-stack-up.sh (app up, e2e, screenshots) available" ;; 2) - warn "docker is installed but 'docker info' gave no answer within ${DOCKER_PROBE_SECS} s — treated as not running." + warn "$CONTAINER_RUNTIME is installed but '$CONTAINER_RUNTIME info' gave no answer within ${DOCKER_PROBE_SECS} s — treated as not running." note "That silent wait is what makes a setup look hung. Raise the bound with" - note "MXTK_DOCKER_PROBE_SECS=30 if the daemon is merely slow to answer here. To start it:" - docker_start_hint ;; + note "MXTK_DOCKER_PROBE_SECS=30 if it is merely slow to answer here. To start it:" + container_start_hint ;; *) - warn "docker is installed but the daemon is not responding." + warn "$RUNTIME_DOWN" note "Until it runs: no app container, no throwaway Postgres, no agent-driven e2e/screenshots." note "To start it:" - docker_start_hint ;; + container_start_hint ;; esac # --install is the "do it for me" mode: on macOS with Docker Desktop present, launch it. - # Never elsewhere — Windows launch paths vary and Linux needs sudo. - if [ "$DOCKER_STATE" != 0 ] && [ "$INSTALL" = 1 ] && [ "$PLATFORM" = macos ] && [ -d /Applications/Docker.app ]; then + # Never elsewhere — Windows launch paths vary and Linux needs sudo. Podman is left alone: + # `podman machine start` on a machine that has never run `podman machine init` is not a safe + # guess to make on someone's behalf. + if [ "$DOCKER_STATE" != 0 ] && [ "$INSTALL" = 1 ] && [ "$CONTAINER_RUNTIME" = docker ] && [ "$PLATFORM" = macos ] && [ -d /Applications/Docker.app ]; then if open -a Docker 2>/dev/null; then note "--install: launched Docker Desktop (open -a Docker). Give it ~30-90 s, then re-run doctor." fi fi else - warn "docker is not installed — recommended for new builders." + warn "no container runtime found — neither docker nor podman. One is recommended for new builders." note "It is what lets the agent verify its own build: 'mxcli docker check' (deep model+build" note "verification) and project-bin/test-stack-up.sh (Postgres + the app up, Playwright e2e," note "page screenshots) both need it. Without it, only mxbuild verifies the model and a human" note "must open Studio Pro to see whether anything actually renders." - note "No-Docker fallback for just running the app: 'mxcli run --local' against a native" + note "No-container fallback for just running the app: 'mxcli run --local' against a native" note "PostgreSQL (host:PORT)." - note "Corporate machines: Docker Desktop needs a paid licence at larger companies — Rancher" - note "Desktop or Podman (docker-CLI compatible) and colima (macOS) are common substitutes." + note "Docker Desktop needs a paid licence at larger companies, and is NOT required: Podman is" + note "the licence-free option (docker-CLI compatible; doctor probes it directly, so no docker" + note "shim is needed). Rancher Desktop and colima (macOS) are the other common substitutes." fi fi diff --git a/project-bin/_common.sh b/project-bin/_common.sh index 43fdcb4..bd8b61c 100755 --- a/project-bin/_common.sh +++ b/project-bin/_common.sh @@ -445,7 +445,7 @@ find_java_exe() { # not be able to wait on it. # # Bounded by : no `timeout(1)` is assumed (macOS ships none) — the -# same background/poll/kill pattern as bin/doctor.sh's docker_daemon_up. This is a +# same background/poll/kill pattern as bin/doctor.sh's container_daemon_up. This is a # genuine behaviour addition versus the exec.sh code it replaces, which had no bound # at all; the default callers use is generous (300s) specifically so a real build is # never cut short by it, only a truly stuck one.