You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docker build (and therefore docker run / mxcli test) silently converts MPRv2 projects to MPRv1 via mx update-widgets, deleting mprcontents — same root cause as #763, different call site #808
mxcli docker build silently converts an MPRv2 project to the self-contained MPRv1
storage format, the same way mxcli docker check did before #764. It inlines every
unit into the .mpr (adds the Unit.Contents column) and deletes the entire mprcontents/ directory. A command whose job is to build a deployment package should
not rewrite the source project's on-disk storage format.
The cause is the same mx update-widgets step described in #763 — but docker build
has its own, separate call site for it (cmd/mxcli/docker/build.go, distinct from cmd/mxcli/docker/check.go), which PR #764 did not touch. Since docker build is what docker run and mxcli test actually invoke, this is arguably the more commonly hit
path in practice: routine use of mxcli test or mxcli docker run against an MPRv2
project silently converts it to MPRv1 and deletes mprcontents/.
On a project under Git this is damaging in exactly the way #763 described: after the
conversion the working tree no longer matches the mprcontents/*.mxunit files tracked
in the repository. We hit this twice on a real ~700-entity / 1,300-microflow production
project in the same working session, purely from running mxcli test against it.
Environment
Reproduced and fix-verified directly against mendixlabs/mxclimain @ d86fb696
(current tip as of this writing), linux/amd64 (Dev Container) — not against our fork's
own integration branch, so this isn't stale relative to anything that's landed since fix(docker): preserve MPRv2 storage format across docker check #764.
mx/mxbuild 11.12.0 (Mendix Studio Pro-bundled binaries, resolved the same way docker check resolves them)
Project version 11.12.0, MPRv2 storage format (mprcontents/ present, 3,447 unit
files)
Reproduction
Same format oracle as #763 (1 = MPRv1 self-contained, 0 = MPRv2):
sqlite3 App.mpr "SELECT COUNT(*) FROM pragma_table_info('Unit') WHERE name='Contents';"
Starting from a clean MPRv2 project (oracle returns 0, mprcontents/ populated):
mxcli docker build -p App.mpr
# ...
# Checking project for errors...
# Updating widget definitions...
# ...
# BUILD SUCCEEDED
sqlite3 App.mpr "SELECT COUNT(*) FROM pragma_table_info('Unit') WHERE name='Contents';"
# 1 <- now MPRv1; mprcontents/ is gone
mxcli test and mxcli docker run both go through Build() in build.go (mxcli test via its own build step before restarting the runtime), so both are affected
identically — you don't need to call docker build directly to hit this.
Impact
Silent storage-format change from commands whose job is to build/run, not to
restructure the project.
Confirmed by reading cmd/mxcli/docker/build.go: it has its own exec.Command(mxPath, "update-widgets", ...) call (around the "Pre-build check" step,
right before mx check), structurally identical to the one #764 fixed in check.go,
but never wrapped in the snapshot/restore protection that PR added. check.go's fix is
scoped to Check() only; Build() was untouched.
Suggested fix
We already have a working fix, branched directly from and verified against current main @ d86fb696 (not our own fork's diverged integration branch, to keep this
independent of anything else we've got in flight) — happy to share/PR once you've had a
look:
Extracted the existing snapshot/restore logic from fix(docker): preserve MPRv2 storage format across docker check #764 out of check.go into a
shared protectMPRv2AcrossUpdateWidgets helper, so both call sites (and any future
one) go through a single, tested guard instead of each reimplementing the check
inline.
build.go's pre-build check now calls the same shared helper before its update-widgets invocation.
Verified three ways, all on the branch built directly from main @ d86fb696:
go build ./... and go test ./... (unit) pass with no regressions, run fresh
against current main.
go test -tags integration ./cmd/mxcli/docker/... passes, including a new TestBuild_PreservesMPRv2StorageFormat mirroring TestCheck_PreservesMPRv2StorageFormat:
scaffolds a real MPRv2 project via mx create-project, runs a full real Build()
(actual mxbuild PAD package build, not a stub), asserts the project is still MPRv2
with mprcontents/ intact afterward. Fails without the fix (project ends MPRv1, mprcontents/ deleted).
Separately, ran the same fix (same commit, cherry-picked) via docker build against
the real project mentioned above, which had been corrupted this exact way twice
already: .mpr stayed at its original 475KB (previously ballooned to ~196MB) and all
3,447 mprcontents/ files survived.
Workaround
Confirmed in build.go: the update-widgets call is nested entirely inside the if !opts.SkipCheck block, alongside mx check itself. So --skip-check (the flag docker build/docker run already expose) skips update-widgets too and avoids the
conversion:
Trade-off: you lose the pre-build mx check validation and the CE0463
widget-definition-drift suppression that update-widgets provides, so real project
errors would only surface later, from mxbuild itself. mxcli test does not currently
expose a way to pass --skip-check through to the docker build it runs internally, so
this workaround isn't available for that path without a flag added there.
Summary
mxcli docker buildsilently converts an MPRv2 project to the self-contained MPRv1storage format, the same way
mxcli docker checkdid before #764. It inlines everyunit into the
.mpr(adds theUnit.Contentscolumn) and deletes the entiremprcontents/directory. A command whose job is to build a deployment package shouldnot rewrite the source project's on-disk storage format.
The cause is the same
mx update-widgetsstep described in #763 — butdocker buildhas its own, separate call site for it (
cmd/mxcli/docker/build.go, distinct fromcmd/mxcli/docker/check.go), which PR #764 did not touch. Sincedocker buildis whatdocker runandmxcli testactually invoke, this is arguably the more commonly hitpath in practice: routine use of
mxcli testormxcli docker runagainst an MPRv2project silently converts it to MPRv1 and deletes
mprcontents/.On a project under Git this is damaging in exactly the way #763 described: after the
conversion the working tree no longer matches the
mprcontents/*.mxunitfiles trackedin the repository. We hit this twice on a real ~700-entity / 1,300-microflow production
project in the same working session, purely from running
mxcli testagainst it.Environment
mendixlabs/mxclimain@d86fb696(current tip as of this writing), linux/amd64 (Dev Container) — not against our fork's
own integration branch, so this isn't stale relative to anything that's landed since
fix(docker): preserve MPRv2 storage format across docker check #764.
mx/mxbuild11.12.0 (Mendix Studio Pro-bundled binaries, resolved the same waydocker checkresolves them)mprcontents/present, 3,447 unitfiles)
Reproduction
Same format oracle as #763 (1 = MPRv1 self-contained, 0 = MPRv2):
Starting from a clean MPRv2 project (oracle returns 0,
mprcontents/populated):mxcli testandmxcli docker runboth go throughBuild()inbuild.go(mxcli testvia its own build step before restarting the runtime), so both are affectedidentically — you don't need to call
docker builddirectly to hit this.Impact
restructure the project.
same downstream risk of Studio Pro failing to open the project or the repository
history and on-disk format drifting apart if committed.
docker checkpath, sincemxcli test(the documented wayto run MDL regression tests) goes through this same code path.
Relationship to #763 / PR #764
Confirmed by reading
cmd/mxcli/docker/build.go: it has its ownexec.Command(mxPath, "update-widgets", ...)call (around the "Pre-build check" step,right before
mx check), structurally identical to the one #764 fixed incheck.go,but never wrapped in the snapshot/restore protection that PR added.
check.go's fix isscoped to
Check()only;Build()was untouched.Suggested fix
We already have a working fix, branched directly from and verified against current
main@d86fb696(not our own fork's diverged integration branch, to keep thisindependent of anything else we've got in flight) — happy to share/PR once you've had a
look:
check.gointo ashared
protectMPRv2AcrossUpdateWidgetshelper, so both call sites (and any futureone) go through a single, tested guard instead of each reimplementing the check
inline.
build.go's pre-build check now calls the same shared helper before itsupdate-widgetsinvocation.check.gonow calls the shared helper too (behavior/tests unchanged from fix(docker): preserve MPRv2 storage format across docker check #764).Verified three ways, all on the branch built directly from
main@d86fb696:go build ./...andgo test ./...(unit) pass with no regressions, run freshagainst current
main.go test -tags integration ./cmd/mxcli/docker/...passes, including a newTestBuild_PreservesMPRv2StorageFormatmirroringTestCheck_PreservesMPRv2StorageFormat:scaffolds a real MPRv2 project via
mx create-project, runs a full realBuild()(actual mxbuild PAD package build, not a stub), asserts the project is still MPRv2
with
mprcontents/intact afterward. Fails without the fix (project ends MPRv1,mprcontents/deleted).docker buildagainstthe real project mentioned above, which had been corrupted this exact way twice
already:
.mprstayed at its original 475KB (previously ballooned to ~196MB) and all3,447
mprcontents/files survived.Workaround
Confirmed in
build.go: theupdate-widgetscall is nested entirely inside theif !opts.SkipCheckblock, alongsidemx checkitself. So--skip-check(the flagdocker build/docker runalready expose) skipsupdate-widgetstoo and avoids theconversion:
Trade-off: you lose the pre-build
mx checkvalidation and the CE0463widget-definition-drift suppression that
update-widgetsprovides, so real projecterrors would only surface later, from
mxbuilditself.mxcli testdoes not currentlyexpose a way to pass
--skip-checkthrough to thedocker buildit runs internally, sothis workaround isn't available for that path without a flag added there.