diff --git a/CHANGELOG.md b/CHANGELOG.md index e30a231..baa5dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Kinds: `new` · `fix` · `learn` (a skill/learning) · `process` (rules, templat Credit the person or project that surfaced the change — the credit line is the thank-you. ## 2026-09-09 +- fix(bin/doctor.sh): the docker probe is bounded (`MXTK_DOCKER_PROBE_SECS`, default 8 s), announces itself before waiting, and can be skipped (`--no-docker` / `MXTK_DOCTOR_SKIP_DOCKER=1`). With Docker Desktop installed but stopped, `docker info` sat silent for minutes at the end of `init-project.sh` and the whole scaffold read as hung. A daemon that is down now gets the one start command for this platform (`open -a Docker`, the Windows `start` line, `systemctl start docker`) plus the re-run line; `--install` on macOS launches Docker Desktop itself. Fixture `tests/wave2/test-doctor-docker-probe.sh` (13 assertions: hanging, down, up, skipped) — a new builder's onboarding feedback, via Maurits Visser - new(bug-logs): upstream draft for BUG-114 — `pending-github-issues/bug114-alter-page-replace-rescopes-to-outer-context.md`. One issue for the two symptoms of the same `mutator.go` walker defect: `ALTER PAGE REPLACE`/`INSERT` under a selection-driven data view re-scopes the binding to the outer entity (CE1613, invisible to `DESCRIBE PAGE`), and under a flow-sourced gallery template drops it (``, CE0402 — the former BUG-118 draft, now reproduction 2, not filed separately). Cites the exact functions (`findEntityContextInWidgets` → `widgetOwnEntity` → `EntityRef`-only readers) and that `Forms$ListenTargetSource` is written with no `EntityRef` — the toolkit merge queue, from the dashboard-publishing and language-learning conversions ## 2026-09-08 diff --git a/bin/doctor.sh b/bin/doctor.sh index 4250363..39eab29 100755 --- a/bin/doctor.sh +++ b/bin/doctor.sh @@ -20,6 +20,11 @@ # # (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 --quick [] # ~2 s: platform, mxcli/mxbuild/java EXECUTE, spawn # # speed, path hygiene, model layout. Skips the # # once-per-machine sections (python, CRLF, script @@ -47,11 +52,14 @@ PROJECT_DIR="" INSTALL=0 ASSUME_YES=0 QUICK=0 +NO_DOCKER="${MXTK_DOCTOR_SKIP_DOCKER:-0}" +DOCKER_PROBE_SECS="${MXTK_DOCKER_PROBE_SECS:-8}" for _arg in "$@"; do case "$_arg" in --install) INSTALL=1 ;; --yes|-y) ASSUME_YES=1 ;; --quick) QUICK=1 ;; + --no-docker) NO_DOCKER=1 ;; *) PROJECT_DIR="$_arg" ;; esac done @@ -616,13 +624,69 @@ head_ "Self-verification stack (Docker — recommended)" # 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 # required — hence WARN, not FAIL. -if command -v docker >/dev/null 2>&1; then - if docker info >/dev/null 2>&1; then - ok "docker daemon responding — mxcli docker check + test-stack-up.sh (app up, e2e, screenshots) available" - else - warn "docker is installed but the daemon is not responding." - note "Start Docker Desktop (or colima / Rancher Desktop). Until it runs: no app container," - note "no throwaway Postgres, no agent-driven e2e/screenshots." +# 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 & + _dd_pid=$! + _dd_waited=0 + while kill -0 "$_dd_pid" 2>/dev/null; do + if [ "$_dd_waited" -ge "$DOCKER_PROBE_SECS" ]; then + kill "$_dd_pid" 2>/dev/null; wait "$_dd_pid" 2>/dev/null + return 2 + fi + sleep 1; _dd_waited=$((_dd_waited + 1)) + done + 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 + 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)..." + DOCKER_STATE=0 + docker_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" ;; + 2) + warn "docker is installed but 'docker 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 ;; + *) + warn "docker is installed but the daemon is not responding." + note "Until it runs: no app container, no throwaway Postgres, no agent-driven e2e/screenshots." + note "To start it:" + docker_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 + 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." diff --git a/tests/wave2/test-doctor-docker-probe.sh b/tests/wave2/test-doctor-docker-probe.sh new file mode 100755 index 0000000..5843dfb --- /dev/null +++ b/tests/wave2/test-doctor-docker-probe.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# test-doctor-docker-probe.sh — doctor.sh's docker probe must never hang, and must say what +# it is doing. Field report 2026-09-09: with Docker Desktop installed but stopped, `docker info` +# sat silent for minutes at the end of init-project.sh and the whole scaffold read as hung. +# +# usage: bash tests/wave2/test-doctor-docker-probe.sh bin/doctor.sh +# +# Three fake `docker` binaries on PATH: one that hangs, one that answers "down" at once, one +# that answers "up". Plus --no-docker / MXTK_DOCTOR_SKIP_DOCKER=1 skipping the section. +set -u +DOCTOR="${1:?usage: $0 bin/doctor.sh}" +DOCTOR="$(cd "$(dirname "$DOCTOR")" && pwd)/$(basename "$DOCTOR")" +T="$(mktemp -d "${TMPDIR:-/tmp}/mxtk-doctor-docker.XXXXXX")" +trap 'rm -rf "$T"' EXIT +PASS=0; FAIL=0 +ok() { PASS=$((PASS + 1)); echo " ok $*"; } +fail() { FAIL=$((FAIL + 1)); echo " FAIL $*"; } +assert_contains() { if grep -q -- "$2" "$1"; then ok "$3"; else fail "$3 — expected: $2"; fi; } +assert_missing() { if grep -q -- "$2" "$1"; then fail "$3 — unexpected: $2"; else ok "$3"; fi; } + +mkfake() { # mkfake + mkdir -p "$1"; printf '#!/usr/bin/env bash\n%s\n' "$2" > "$1/docker"; chmod +x "$1/docker" +} +mkfake "$T/hang" 'sleep 120' +mkfake "$T/down" 'echo "error during connect: Docker Desktop is not running" >&2; exit 1' +mkfake "$T/up" 'echo "Server Version: 27.0.0"; exit 0' + +echo "doctor docker probe — $DOCTOR" + +# 1. hanging daemon: bounded, and says so +S0=$(date +%s) +PATH="$T/hang:$PATH" MXTK_DOCKER_PROBE_SECS=2 bash "$DOCTOR" > "$T/hang.out" 2>&1 +S1=$(date +%s) +if [ $((S1 - S0)) -lt 40 ]; then ok "hanging docker: doctor finished in $((S1 - S0)) s (bound 2 s)"; else fail "hanging docker: doctor took $((S1 - S0)) s — the bound is not biting"; fi +assert_contains "$T/hang.out" "probing the docker daemon (bounded: 2 s" "hanging docker: announces the probe and its bound before waiting" +assert_contains "$T/hang.out" "gave no answer within 2 s" "hanging docker: reports the timeout as 'not running'" +assert_contains "$T/hang.out" "MXTK_DOCKER_PROBE_SECS=30" "hanging docker: tells how to raise the bound" +assert_contains "$T/hang.out" "Then re-run: bin/doctor.sh" "hanging docker: says how to re-check after starting it" +assert_missing "$T/hang.out" "docker daemon responding" "hanging docker: not reported as up" + +# 2. daemon down, answering at once +PATH="$T/down:$PATH" bash "$DOCTOR" > "$T/down.out" 2>&1 +assert_contains "$T/down.out" "daemon is not responding" "down docker: reported as not responding" +assert_contains "$T/down.out" "To start it:" "down docker: carries a start command" +assert_missing "$T/down.out" "gave no answer within" "down docker: a fast 'down' is not called a timeout" + +# 3. daemon up +PATH="$T/up:$PATH" bash "$DOCTOR" > "$T/up.out" 2>&1 +assert_contains "$T/up.out" "docker daemon responding" "up docker: reported as responding" + +# 4. skipping the section entirely +PATH="$T/hang:$PATH" bash "$DOCTOR" --no-docker > "$T/skip.out" 2>&1 +assert_contains "$T/skip.out" "docker probe skipped" "--no-docker: section skipped (hanging fake never called)" +S0=$(date +%s); PATH="$T/hang:$PATH" MXTK_DOCTOR_SKIP_DOCKER=1 bash "$DOCTOR" > "$T/skip2.out" 2>&1; S1=$(date +%s) +assert_contains "$T/skip2.out" "docker probe skipped" "MXTK_DOCTOR_SKIP_DOCKER=1: section skipped" +if [ $((S1 - S0)) -lt 40 ]; then ok "MXTK_DOCTOR_SKIP_DOCKER=1: no wait ($((S1 - S0)) s)"; else fail "MXTK_DOCTOR_SKIP_DOCKER=1 still waited $((S1 - S0)) s"; fi + +echo; echo "$PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ]