Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (`<unbound>`, 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
Expand Down
78 changes: 71 additions & 7 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
# # (same download the headless container build
# # uses; cached at ~/.mxcli/mxbuild/).
# bin/doctor.sh --install --yes <dir> # skip the confirmation (unattended/agent runs)
# bin/doctor.sh --no-docker [<project-dir>] # 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 [<project-dir>] # ~2 s: platform, mxcli/mxbuild/java EXECUTE, spawn
# # speed, path hygiene, model layout. Skips the
# # once-per-machine sections (python, CRLF, script
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."
Expand Down
59 changes: 59 additions & 0 deletions tests/wave2/test-doctor-docker-probe.sh
Original file line number Diff line number Diff line change
@@ -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 <dir> <body>
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 ]
Loading