mando-cli v0.4.0 compose-runtime bug triage (2026-05-26)
Triage of mando-cli v0.4.0 compose-runtime bug report from Gabi (2026-05-25). The report claimed two critical bugs against the bollard → docker-compose rewrite (commit 6ba0d61, shipped 2026-05-13). Initial user instinct was skepticism. After verification against live source, both bugs are real, and Bug 2’s root cause is one layer deeper than the reporter diagnosed.
For Agents
This note pins (a) two confirmed compose-runtime bugs in v0.4.0 with code refs, (b) a previously-undocumented load-bearing invariant in
render_override— theprofiles: ["{run_tag}"]line is what keeps malformed mocked-service stubs inert under normal use, and (c) the process lesson that smoke-testing the rewrite did not exercise themando-mocked-algosdefault profile against a fresh checkout.
Status
Bugs confirmed, fixes not yet committed. v0.4.0 binary distributed via install.ps1 is broken for both
mando-fullandmando-mocked-algosprofiles.
Reporter
- Gabi, date 2026-05-25
- Report path:
~/Downloads/bug-report-v0.4.0-compose-issues.md - Severity claimed: Critical —
mando upbroken for both default profiles
Bug 1 — build context resolves to runconfig/ not project root
CONFIRMED
Templates
src/runtime/templates/runconfig/build.yml:11andbuild_dev.yml:18usecontext: .. Docker Compose resolves relative build context paths relative to the compose file’s own directory. Since these render to<project>/runconfig/<kind>.builtin.yaml(perRunconfigKind::filename,templates.rs:74-78, written byensure_runconfigsattemplates.rs:244),.resolves to<project>/runconfig/— where no Dockerfile exists.
Affected profiles
All profiles using build or build-dev runconfig: mando-full, mando-mocked-algos, mando-fast-dev.
Error surfaced
target bess-optimization: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory
Proof
$ docker compose -f bess-optimization/runconfig/build.builtin.yaml -f .infra/postgres.builtin.yaml config | grep context
context: /home/gabi/.../bess-optimization/runconfig # WRONG (should be .../bess-optimization)Fix
services:
__MANDO_SERVICE__:
build:
- context: .
+ context: ..
dockerfile: __MANDO_DOCKERFILE__Apply in both src/runtime/templates/runconfig/build.yml and build_dev.yml.
Test gap
build_renders_with_service_and_dockerfile (templates.rs:366) asserts dockerfile and image fields but never asserts context. Add:
assert_eq!(svc["build"]["context"].as_str().unwrap(), "..");Bug 2 — mocked runconfig passes bare service name to compose
CONFIRMED — reporter's diagnosis incomplete
mocked.ymlonly defines__MANDO_SERVICE__-mocks(the one-shot curl loader).up.rs:192(docker_targets.iter().map(|(s, _)| s.clone())) collects bare slugs regardless of runconfig and passes them positionally todocker compose up. But the exact error message “Must specify either image or build” doesn’t come from the missing-service path — it comes fromrender_overrideintemplates.rs:298-325.
Hidden mechanism — the load-bearing profiles: line
render_override writes a <service>: block to .mando/override.builtin.yaml for every entry in docker_targets, mocked or not. The block has env_file, restart, profiles, logging but no image and no build:
services:
bess-optimization: # <-- mocked, but stub still emitted
env_file:
- '/.../bess-optimization/.mando/resolved.env'
restart: unless-stopped
profiles: ["mando-mocked-algos"] # <-- this line keeps it inert
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"Compose normally ignores profile-gated services unless --profile is passed. Since up.rs never passes --profile, the malformed stub stays dormant. But passing the bare slug as a positional arg activates that exact stub from the override, and compose fails validation with the reported error.
Error surfaced
invalid service "bess-optimization". Must specify either image or build
Affected profiles
All profiles with mocked services: mando-mocked-algos (default), mando-fast-dev.
Why Gabi’s proposed fix works
Renaming the positional arg from <service> to <service>-mocks (the helper container, which has a real image: curlimages/curl:8.16.0) sidesteps activation of the malformed override stub. The stub remains dormant because the profile gate is never triggered.
-let svc_names: Vec<String> = docker_targets.iter().map(|(s, _)| s.clone()).collect();
+let svc_names: Vec<String> = docker_targets
+ .iter()
+ .map(|(s, run)| {
+ if run.runconfig == "mocked" {
+ format!("{s}-mocks")
+ } else {
+ s.clone()
+ }
+ })
+ .collect();Cleaner fix — kill the hidden invariant
Gabi’s fix is correct but leaves a load-bearing reliance on profiles: ["{run_tag}"] at templates.rs:317. Anyone who later removes that line — or starts passing --profile flags from a future feature — will resurface the bug. Better:
- Apply Gabi’s
<service>-mocksrename inup.rs:192. - Also skip mocked entries when building
entries: Vec<OverrideEntry>atup.rs:164-180. Mocked sidecars don’t needenv_file/restart/logging— they’re one-shot curl loaders.
With (2), no malformed stub is ever generated for mocked services, and the profiles: line becomes incidental rather than load-bearing.
Naming-convention coupling
The <service>-mocks suffix is duplicated in two places: mocked.yml:9 (the template) and the proposed up.rs:192 fix. If the template ever renames, the up command silently breaks. Reporter suggests extracting a helper:
pub fn mocked_compose_service_name(slug: &str) -> String {
format!("{slug}-mocks")
}Worth adopting — call it from both render-time and runtime sites.
Why the smoke-test round (8de8f64) missed both
mando-cli received two cleanup rounds after the rewrite:
5dd5112triple-review pre-merge fixes (6 real bugs + DRY consolidation)8de8f64smoke-test fixes (curlimages/curl:8tag unpublished,--waiton one-shot sidecars, infra-only profile skippedcompose::run, flyway U-prefix rejected)
The smoke-test commit fixed mocked-related issues, so the mocked path was exercised. But:
- Bug 1 — masked by build caching. Smoke test likely ran against a workspace where prior
docker buildhad populated the image cache, sodocker compose upskipped rebuild and never re-resolved the broken context path. - Bug 2 — not exercised against default profile.
mando-mocked-algosset as the workspace default and run cleanly from scratch was apparently not part of the smoke matrix.
Recommended action
| # | Action | File(s) |
|---|---|---|
| 1 | Apply Bug 1 fix (context: ..) | src/runtime/templates/runconfig/build.yml, build_dev.yml |
| 2 | Add context assertion to existing test | src/runtime/templates.rs (build_renders_with_service_and_dockerfile) |
| 3 | Apply Bug 2 positional-arg rename | src/commands/up.rs:192 |
| 4 | Skip mocked entries in override generation | src/commands/up.rs:164-180 |
| 5 | Extract mocked_compose_service_name(slug) helper | src/runtime/templates.rs |
| 6 | Add integration test: mando up --profile mando-mocked-algos against tmp workspace, assert docker compose config succeeds | tests/ |
| 7 | Concede to Gabi, push fix branch, cut v0.4.1 | git |
Process lesson
Per Agent Context (user pushback pattern): “resist abstract critiques; concede when reporter has runtime trace or reproducer.” Gabi supplied:
- Specific file:line refs
- Verbatim error messages
docker compose configoutput showing the wrong resolved context path- Confirmation that a rebuild with proposed fixes works
That’s the concede signal. Initial user skepticism was wrong here. The lesson is not “trust all bug reports” — it’s that the triple-review pattern (5dd5112) and smoke-test pass (8de8f64) gave false confidence in coverage because neither exercised the default-mocked-profile-against-fresh-checkout path that v0.4.0 ships as the user-facing default.
Cross-links
- mando-cli-v2 — current architecture incl. AppContext + CommandDelegate
- mando-cli-docker-lifecycle — bollard era, now superseded for orchestration (bollard retained only for
inspect_detailper mando-cli-status-readonly-2026-05-06) - mando-cli-mock-down-idempotent-2026-05-06 — canonical 7-step pattern for docker-backed lifecycle commands; informs the override-skip recommendation
- mando-cli-wsl-linux-build — install.sh distribution channel that delivered the broken v0.4.0 binary
- Spec:
/Volumes/bandi/coding/poc/mando-cli/docs/superpowers/specs/2026-05-13-compose-runtime-design.md - Plan:
/Volumes/bandi/coding/poc/mando-cli/docs/superpowers/plans/2026-05-13-compose-runtime.md