Skip to content

fix(new): resolve the exact Mendix version, and verify what was created - #69

Merged
ako merged 3 commits into
mainfrom
claude/new-version-exact-mxbuild
Aug 1, 2026
Merged

fix(new): resolve the exact Mendix version, and verify what was created#69
ako merged 3 commits into
mainfrom
claude/new-version-exact-mxbuild

Conversation

@ako

@ako ako commented Aug 1, 2026

Copy link
Copy Markdown
Owner

The bug

mxcli new --version 11.12.2 printed:

Step 1/4: Resolving MxBuild 11.12.2...

…and then produced a Mendix 11.6.6 project, with no warning that the requested version had been ignored. mxcli setup mxbuild --version 11.12.2 downloads that version fine, so the CDN was never the problem — the resolver simply never asked.

ResolveMxForNewProject delegated to ResolveMxForVersion, whose last resort is AnyCachedMxPath(): any cached mx, of any version. That fallback is defensible when the project already exists and its version is a preference. It is wrong for new, where the requested version is not a preference but the definition of the outputmx create-project stamps the new project with the version of the binary that ran it, and every later step (init, mxbuild, runtime, run --local) then follows the wrong version.

Two changes, because either alone leaves a silent path

Resolution is now exact. localMxForVersion looks only for the requested version: an exact-version Studio Pro install, an exact-version binary in the OS-specific install locations, then the exact-version download cache. PATH is deliberately excluded — an mx on PATH carries no version guarantee. Anything else downloads. Windows and macOS still prefer an exact Studio Pro install, so they do not fetch a Linux CDN binary that cannot execute.

new now checks its postcondition rather than trusting the resolution: it reopens the created .mpr, compares ProductVersion against --version, and exits with the mismatch, the binary that caused it, and the command that fixes it. A resolution bug is invisible without this — which is exactly how the original went unnoticed.

Verification

Reproducing the reported state (only 11.6.6 cached, 11.12.2 requested):

result
before silently created a Mendix 11.6.6 project
after downloads 11.12.2, creates a Mendix 11.12.2 project

And reverting only the resolver, to prove the postcondition is not decorative:

Error: requested Mendix 11.12.2 but the created project is 11.6.6.
  mx create-project stamps the project with the version of the binary that ran it (.../11.6.6/modeler/mx).
  Run 'mxcli setup mxbuild --version 11.12.2' and try again.

Unit tests cover the resolution decision without touching the network: a cached 11.6.6 must not satisfy a request for 11.12.2, an exact match must still be reused rather than re-downloaded, and an empty version matches nothing. Full suite green.

How it was found

While building a scratch project to verify mendixlabs#812 in a browser. It cost a full project rebuild before the wrong version was spotted — the created project simply reported Mendix 11.6.6 in a later command's banner, which is the only place the substitution ever surfaced.

The generalisable lesson is recorded in the symptom table: when a flag names the version or identity of the artifact being produced, a "close enough" local substitute is never valid, and the produced artifact should be verified against the request rather than the resolution trusted.

🤖 Generated with Claude Code

https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA


Generated by Claude Code

claude added 2 commits August 1, 2026 03:52
`mxcli new --version 11.12.2` printed "Step 1/4: Resolving MxBuild 11.12.2..."
and then produced a Mendix 11.6.6 project, with no warning that the requested
version had been ignored. `mxcli setup mxbuild --version 11.12.2` downloads that
version fine, so the CDN was never the problem — the resolver simply never asked.

ResolveMxForNewProject delegated to ResolveMxForVersion, whose last resort is
AnyCachedMxPath(): any cached mx, of any version. That fallback is defensible
when the project already exists and its version is a preference. It is wrong for
`new`, where the requested version is not a preference but the definition of the
output — `mx create-project` stamps the new project with the version of the
binary that ran it, and every later step (init, mxbuild, runtime, run --local)
then follows the wrong version.

Two changes, because either alone leaves a silent path.

Resolution is now exact. localMxForVersion looks only for the requested version:
an exact-version Studio Pro install, an exact-version binary in the OS-specific
install locations, then the exact-version download cache. PATH is deliberately
excluded — an `mx` on PATH carries no version guarantee. Anything else
downloads. Windows and macOS still prefer an exact Studio Pro install, so they
do not fetch a Linux CDN binary that cannot execute.

And `new` now checks its postcondition rather than trusting the resolution: it
reopens the created .mpr, compares ProductVersion against --version, and exits
with the mismatch, the binary that caused it, and the setup command that fixes
it. A resolution bug is invisible without this — which is exactly how the
original went unnoticed.

Verified by reproducing the reported state (only 11.6.6 cached, 11.12.2
requested):

  before  ->  silently created a Mendix 11.6.6 project
  after   ->  downloads 11.12.2, creates a Mendix 11.12.2 project

and by reverting only the resolver, which now fails loudly instead of silently:

  Error: requested Mendix 11.12.2 but the created project is 11.6.6.
    mx create-project stamps the project with the version of the binary that ran it (.../11.6.6/modeler/mx).
    Run 'mxcli setup mxbuild --version 11.12.2' and try again.

Unit tests cover the resolution decision without touching the network: a cached
11.6.6 must not satisfy a request for 11.12.2, an exact match must still be
reused rather than re-downloaded, and an empty version matches nothing.

Found while building a project to verify mendixlabs#812 in a browser; it cost a full
project rebuild before the wrong version was spotted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
Same recurring conflict as #68: .claude/skills/fix-issue.md, where every fix
appends a symptom row at the identical anchor. Both sides kept.

check.go auto-merged cleanly — #64's runUpdateWidgets and this branch's
localMxForVersion touch different functions. Parser regenerated against #66's
grammar; full suite green.
ako pushed a commit that referenced this pull request Aug 1, 2026
Every bug fix touches .claude/skills/fix-issue.md, and new rows went in directly
under the table header. Two branches fixing unrelated bugs therefore inserted at
the same line, which git cannot merge — a conflict by construction rather than by
bad luck. It cost five separate resolution rounds in one week (#59-#62 three
times, then #68 and #69), and each round risks silently dropping a row: one
earlier round was "resolved" by deleting a PR's rows entirely and restoring them
in a follow-up.

Appending puts each branch's insert at a different offset, which git merges
without help. The table is unordered — it is looked up by matching a symptom, not
read top to bottom — so position carries no meaning and appending costs nothing.

Recorded in both places a contributor might look: the skill's How to Use, with
the reasoning so it is not "fixed" back, and the PR checklist item in CLAUDE.md.

Existing rows are deliberately left where they are. Reordering them would
conflict with every open branch at once, which is the problem this change exists
to avoid.

Folded into this branch rather than opened separately: it edits the same How to
Use block, so a sixth branch would have manufactured exactly the conflict it is
meant to prevent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
#68 landed, putting its symptom row at the top of the table — the same line this
branch inserts at, so the conflict recurred immediately after the previous
resolution. That is the pattern, not bad luck.

Resolved by keeping main's rows and moving this branch's row to the END of the
table, which is the convention #70 introduces. Applying it here rather than
waiting: this branch is the exact case it exists for.
@ako
ako merged commit b546544 into main Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants