Sync ako/mxcli: local test loop, OData publishing, JAR dependency resolution, and 20 fixes - #859
Merged
Conversation
`mxcli lint` on a freshly initialised app reported ~106 issues, of which ~102 came from the System module — Mendix's own platform module, whose entities you cannot document, give access rules, or rename members of. Every one of those findings is un-actionable, and they buried the handful about the developer's own code. Measured on a real project here: 170 issues, 102 of them System, across QUAL002 (50), SEC001 (38), CONV001 (8), DESIGN001 (4), MPR003 and SEC006. `mxcli lint` already had -e/--exclude and a lint-config.yaml with excludeModules, so this was solvable — but only by a user who already knew to look. The default is what people see. `mxcli init` now writes .claude/lint-config.yaml with System excluded, which drops that project from 170 findings to 68. The file is written only when the project has no lint config in any location FindConfigFile searches, so re-running init never discards edits. It is written outside the per-tool branches because `mxcli lint` reads it regardless of which AI tool was selected. Also warns when --modules names a config-excluded module. LintContext .IsExcluded checks the exclude set before the include set, so `lint -m System` against the new default would otherwise report zero findings with no explanation — a trap this change would have introduced. Addresses issuetracker finding #9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The Mendix runtime omits a column from a row's JSON object when its value is null. parseOQLFeedback took the column list from the first row only, so a column that happened to be null in row 1 was dropped from the entire result — `mxcli oql` rendered a narrower table than the query asked for, with no error and no empty column to hint at the loss. The column set is now the union of every row's keys. New keys are inserted directly after the last key already known rather than appended, so a column absent from earlier rows keeps its SELECT position: merging [A, C] with [A, B, C] gives [A, B, C], not [A, C, B]. Rows are re-scanned for key order only when they carry a key not seen yet, so the uniform case still costs one length check. Verified by stubbing the union back to first-row-only and watching the reported symptom return. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Referencing a loop's variable after `end loop;` passed `mxcli check` and then failed the build with [error] [CE0108] "Variable 'item' is defined but not in scope at this location." Both flavours reproduce against mxbuild 11.12.1: the iterator itself, and anything the loop body introduces (a retrieve, a `$X = create …`, a call output). MDL053 maps each loop-scoped name to the loop whose own body introduces it (a nested loop keeps its own names), then walks the flow tracking which loops enclose the current position and flags any reference from outside the owner. A name claimed by two loops is deliberately not reported — that is the MDL052/CE0111 duplicate-name case, and without the guard the existing MDL052 negative example started failing for the wrong reason. MDL052 is the sibling rule: names are unique across the whole microflow, but visibility stops at the loop body. The write-microflows skill now states both and shows the carry-out idiom. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The loop-variable-scope check (MDL053) was filed against sudoku finding #40, which is actually an app bug in that project ("roughly one dealt board in twenty is not solvable by forced logic") and explicitly marked "not an mxcli bug". The check is a real, mxbuild-verified check-parity gap, so it stays — but it is not that finding, and the repro file and symptom row said otherwise. The OQL column fix does match sudoku #39, but only its first half; the second (ORDER BY on a DateTime attribute being ignored) is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`mxcli test` got all the way through parsing, runner generation, model
injection, after-startup wiring and the full mxbuild build with no
container, then died on `docker up` — so microflow tests were unavailable
in exactly the environments mxcli targets, where /usr/bin/docker exists
with no daemon behind it.
Only the last step needed a container. --local boots the same standalone
runtime `mxcli run --local` uses (new docker.StartLocalApp: cache mxbuild
+ runtime, ensure the database, mxbuild --serve build, boot) and reads the
runner's output from the runtime log. The Docker path moves to
runDockerAndCapture; both share parse, inject, parse-results and cleanup.
Local runs use their own ports (8081/8091) and a <project>_test database,
so a warm `run --local` loop can keep serving the same project and test
data never lands in the database the developer is looking at.
Two things only running it could have taught:
- The runner reports through an after-startup microflow, so its LOG
output happens during the start action, before the runtime attaches
its log subscriber. Registering the subscriber early is not possible —
the runtime answers LoggingException pre-start. The JVM console tee,
live from spawn, is what carries it; confirmed by A/B running with the
early attach removed, and it is documented where it matters.
- A failing test makes the runner return false, which fails the
after-startup action, which makes `start` return an error. The first
version reported that as a broken run and printed a stack trace
instead of the test report.
Verified end-to-end in a container with no Docker daemon: two tests
passing, then one passing and one failing with exit 1, project restored
both times.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Sudoku findings: OQL column union, Docker-free `mxcli test --local`, loop-scope check
The prompt hardcoded "App" as the application name, so every repo
bootstrapped from it ended up with App.mpr regardless of what was being
built — and the name is awkward to change afterwards: it is the .mpr file
name, the Studio Pro app name, and the path baked into the SessionStart
hook.
A Step 0 now asks for the app name, what the app is for, what it keeps
track of, who logs in, the theme and the Mendix version — all in one
message with defaults, so "defaults" is a valid answer and the agent is
told not to block twice. The answers become the brief, written to
README.md and committed, so a session resuming after an idle reap knows
what it is building. The provisioning steps take <AppName> throughout,
and a closing step has the agent propose the model from the brief and
wait rather than inventing one.
Two corrections while rewriting, both verified rather than assumed:
- "Create the app at the repo root: mxcli new App" cannot work. mxcli
new refuses a directory that is not empty and .git alone trips it,
and with no --output-dir it creates ./App/, leaving every later
`-p App.mpr` wrong by one directory. The prompt now creates in a
subfolder and moves the contents up, which is what the SessionStart
hook's relative path needs.
- The blank template's module is MyFirstModule, not the app name, so
the closing step asks for a module name explicitly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The prompt assumed one app at the repo root, which is most of what people
want but silently wrong for a solution — a backend that owns the data and
publishes OData, plus a frontend that consumes it.
The interview now opens by asking the shape, and a new section carries the
deltas that a second app actually needs. They are deltas, not a rewrite:
per-app subfolders instead of moving to the root, explicit ports for the
second app (8180/8190/6643 — 8081/8091/6544 belong to `mxcli test
--local`), and `--hub-solution` so previews group. Databases need no
action, since the name is derived from the .mpr file name.
Two things the agent could not have inferred, both verified:
- `mxcli init` dedupes the SessionStart hook on the command, not on the
project, so a second app never gets its own entry. Claude Code reads
the root `.claude/settings.json`, which has to be written by hand,
one line per app.
- `CREATE ODATA CLIENT` fetches the $metadata at creation time and
caches it (warning, not failing, when unreachable). So the producer
must be published and running before the consumer is wired, and
ServiceUrl belongs in a constant since it will not stay localhost.
Default Mendix version 11.6.3 -> 11.13.0, with a note on what "newest
supported" means: both mxbuild-<v> and mendix-<v> must be on the CDN, and
a solution's apps should share a version. Confirmed 11.13.0 serves both
tarballs and 11.14.0 serves neither.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Separating a solution's apps by port alone does not separate their sessions: cookies are keyed on host name and ignore the port, so localhost:8080 and localhost:8180 share one jar and a login to one can replace the other's XASSESSIONID. Distinct hostnames fix it and need no mxcli change, which is worth stating explicitly because the obvious alternative — one loopback IP per app on a shared port — is not possible today: the runtime binds 127.0.0.1 and there is no listen-address flag. Verified against a booted app on 11.12.1: a foreign Host header is served 200 (via /etc/hosts, nip.io and localtest.me alike), and the client uses relative URLs, so no ApplicationRootUrl is needed for this. /etc/hosts is recommended over public wildcard DNS for locked-down containers, where localtest.me resolves to ::1. Also notes the one thing the hostname alone does not cover: ApplicationRootUrl is only set by --hub, so absolute-URL cases (OIDC/SAML redirect URIs, deep links) still advertise the listen address. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The project's own configuration (App Settings -> Configurations in Studio Pro, `alter settings configuration '…' ApplicationRootUrl = …` in MDL) is where a custom host name belongs — versioned with the app, per configuration, no flag to repeat. `run --local` ignored it: the boot payload's ApplicationRootUrl came only from a --hub registration, and since update_configuration replaces rather than merges, nothing else could supply it. It is now read at boot and used when no hub URL was assigned (a hub assignment wins, being the URL actually serving the app). Without it, serving a solution's apps under their own host names still works — the runtime accepts any Host and the client uses relative URLs — but the absolute URLs Mendix generates for OIDC/SAML redirects and deep links kept naming the listen address. The trap: a blank Mendix app already ships ApplicationRootUrl = http://localhost:8080/, so "is set" does not mean "was chosen". Honouring every value would change behaviour for every existing project and advertise the wrong port under --app-port. Only a non-loopback host is passed through, and a port disagreeing with --app-port warns. Verified end-to-end on 11.12.1: with backend.local configured, the boot prints the configuration it came from and the app answers 200 on both the host name and the listen address. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…tity A widget bound to an attribute the context entity inherits rather than declares passed `mxcli check --references` and `mxcli lint`, and then the real MxBuild rejected the project: [error] [CE1613] "The selected attribute 'TaskBoard.Person.FullName' no longer exists." Mendix stores the reference against the entity that DECLARES the attribute; mxcli qualified it with the entity in context, leaving a dangling reference. The message reads as a deletion — it never existed there. Two independent resolvers had the bug and both are fixed: the direct binding (resolveAttributePath) and the final attribute of an association path (resolveAssociationAttributePath). The reporter's own table showed both failing, and the association case still failed after the first patch — a probe of the direct case alone would have shipped half a fix. Unknown names keep their previous context qualification rather than being re-pointed, and a cyclic generalization chain terminates. Attribute resolution now consults the domain models, so the lookup bails out when there is no backend and nothing cached — several unit tests build a pageBuilder with neither. A/B on Mendix 11.12.1: pre-fix binary gives CE1613 for the inherited column and 0 errors for the own column; fixed binary gives 0 errors for both shapes, direct and over an association. Repro: mdl-examples/bug-tests/todo-12-inherited-attribute-on-page.mdl Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The generated hook was `test -x ./mxcli && ./mxcli run --local --setup --ensure-db -p App.mpr || true`, and .gitignore excludes that binary (~85 MB) on purpose. In an ephemeral container the two combine badly: the container is reclaimed, the repo re-cloned without the binary, the guard fails, and the hook silently no-ops through `|| true`. The next session has no mxcli, no MxBuild cache and no database — exactly the state the hook exists to prevent — with nothing said about it. `mxcli init` now writes a committed `.claude/bootstrap-mxcli.sh` that resolves OS/arch, fetches the binary when it is missing (MXCLI_TAG pins a version, default nightly) and then runs the setup; the hook is reduced to `sh .claude/bootstrap-mxcli.sh || true`. A hook line cannot reasonably do OS/arch detection, but a committed script can — and the binary stays out of git. That changes the hook command, which is what dedupe matched on, so addSessionStartHook now recognises any known marker and rewrites the entry in place. An existing project migrates on the next `mxcli init` instead of ending up with two hooks that both run. Also adds /theme-cache/ to the generated .gitignore: MxBuild regenerates the compiled theme on every build, so tracking it means a fresh clone goes dirty the first time anyone builds. Verified by reproducing the reap: moved ./mxcli out of a project, ran the hook command verbatim, and watched it re-download the binary (88 MB, new mtime) and finish with "Setup complete … database ready". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A heredoc is the natural way to drive MDL from an agent or a shell script, and `-` is how every other Unix tool spells stdin. It was taken literally as a filename: Error reading file: open -: no such file or directory so every ad-hoc script needed writing to a temp file first. One helper now backs both commands, so `check` gained the same spelling rather than only the reported one; it reports the source as <stdin> instead of a bare dash. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`mxcli syntax` is the reference an agent reads before writing MDL, and
nothing checked it against the parser. Two entries had drifted, each
costing a build round-trip to discover:
- TEXTBOX/TEXTAREA/COMBOBOX/DATEPICKER/CHECKBOX were documented with
`Binds:`, which the parser rejects outright ("'Binds:' is no longer
supported, use 'Attribute:' instead").
- `DataSource: MICROFLOW Module.MF()` — a zero-argument microflow
DATASOURCE takes no parentheses, unlike RETRIEVE/CALL where they are
normal. The parser errors at the `)`.
Both verified against the parser before and after.
A table-driven test now fails if either spelling reappears in any topic's
Syntax or Example field. It is a spelling guard rather than a parse: the
snippets are fragments (a DATAVIEW body, a property line) that do not
stand alone as statements. Proven by reintroducing `Binds:` and watching
the test name the topic and field.
A third claim in the same report did not reproduce — a CONTAINER with
`OnClick: SHOW_PAGE M.Page(Param: $currentObject)` parses fine on current
main — so it was left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Bootstrap prompt: interview first, multi-app solutions, and honour the configured app host name
Measured on Mendix 11.13.0. Registering one Java custom request handler at boot gives the test runner an entry point it can invoke repeatedly, instead of triggering tests from the after-startup microflow (a boot hook, so every re-run needs a full runtime restart). Core.getMicroflowNames() lets the handler resolve microflows by name at request time, so the Java is written once and never regenerated as tests change. Measured: unchanged suite re-run 30.55s -> 0.084s; edit-then-re-run 30.55s -> 4.29s (almost entirely the existing --watch rebuild). The load-bearing finding is that the handler survives reload_model: the runtime JVM PID is unchanged, after-startup does not re-run, and the already-registered handler resolves the new model's microflows. Verified in one reload with an edited test and a test created after boot. Also records what is not settled: the endpoint is unauthenticated and executes arbitrary microflows under a system context, and there is no Core.removeRequestHandler. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
A decision written with uppercase keywords — IF $Task/Status != M.Status.Done AND $Task/CompletedOn != empty THEN passed `mxcli check` and then failed the build with CE0117, quoting the expression back with `AND` still uppercase. Mendix requires its word operators in lowercase. A rebuilt BinaryExpr already gets strings.ToLower on the operator. A condition kept as an ast.SourceExpr — original text plus the parsed tree — returned the raw source and skipped it. The `=` form of the same condition parses to a BinaryExpr and the `!=` form to a SourceExpr, which is why this looked like "`!=` cannot be an operand of `AND`" rather than a casing problem: every failing probe was uppercase and the control was not. Preserved source now has and/or/not/div/mod lowercased on the way to the model, with everything else byte-identical: the scanner tracks single-quoted literals (including '' escapes) and skips any word preceded by `.`, `/` or `$`, so 'AND', M.Enum.And, $Task/Mod and $Android are untouched. Reproduced and fixed against mxbuild 11.12.1: stored `AND` gives CE0117, stored `and` gives 0 errors. Repro: mdl-examples/bug-tests/todo-14b-uppercase-and-operator.mdl Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`ADD EVENT HANDLER` errored when the handler already existed and `DROP EVENT HANDLER` errored when it did not, so a script containing either could not be re-run — and the obvious workaround, a defensive drop-then-add, fails on whichever half does not match the current state. Attributes got idempotency guards in findings #10; event handlers, which have no other re-run route, did not. Both clauses now accept the existing ifNotExists/ifExists grammar rules, so the guard reads the same everywhere, and the errors name the flag so the fix is discoverable from the failure. Verified by running one script twice: first run skips the drop (absent) then adds; second run skips both, exits 0, and the project still builds with 0 errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`CREATE DEMO USER` reported success and `SHOW PROJECT SECURITY` reported "Demo Users Enabled: true", while the running app had zero accounts, no login page and no row-level enforcement. A blank mxcli template ships with Security Level Off, and with it off the runtime creates no accounts at all — so the demo users sit in the model and never appear. The model said yes and the app said nothing; nothing connected the two. The create path has both facts in hand, so it now says so and names the one statement that fixes it. A warning rather than a refusal: authoring demo users before raising the level is legitimate ordering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Mendix passes no object to a before-create handler — the object does not exist yet — so a microflow with parameters wired there builds as [CE7247] "Microflow should not have parameters" at Event handler of entity 'TaskBoard.Task' `mxcli check` had no way to see it, and the model was written before anyone found out. The guard sits in buildEventHandlers, which CREATE ENTITY's inline handlers and ALTER ENTITY ADD EVENT HANDLER both go through, so one check covers both paths. It refuses before the write, and the message carries the build code and the way out: AFTER CREATE does receive the object. A microflow created earlier in the same script is not readable back yet, so an unreadable microflow is skipped rather than refused — mxbuild still catches the real case, and failing on the read would break legitimate scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
"Contrast is low and not everything uses the dark theme" — and switching
theme did not help, because all three themes rendered the same defects.
The login page is the bulk of it. It is served from theme/web/login.html,
which loads the SAME compiled theme CSS, so it always was themeable —
nothing themed it. Two Atlas rules did the damage:
- `.loginpage-image` layers a brand-tinted gradient over Atlas's stock
photograph (url("./resources/work-do-more.jpeg")), so a dark app opens
on a large, bright, unrelated picture. It is now a gradient built from
the theme's own tokens, with no photo.
- The submit button is `.btn-success`, so it followed the SUCCESS colour
rather than the brand — a green button two screens from a teal one on
console. It now points at the brand.
The project's own theme/web/logo.png is deliberately left alone: it is the
app's asset to replace, and hiding it would strip a real logo from apps
that have one.
Separately, --link-color was mapped straight to the brand. Link text needs
4.5:1 as body text, while a brand used as a button fill only needs 3:1
plus contrast against its own ink — so darkening the brand everywhere
would have been the wrong lever. Link text now has its own --mxt-link
token defaulting to the brand, and console's light variant sets it to
#0f766e (5.47 / 5.10 / 4.92 against surface / ground / surface-alt, versus
3.74 on white for the brand teal it replaces).
Verified at the compiled-CSS layer: the overriding .loginpage-image is
last in the cascade and carries no photo, and the button and link
declarations resolve. NOT verified in a browser — raising the scratch
project's security level far enough to serve a login page surfaced
pre-existing model errors there that block deploy.
The reported #999 empty-state label is not reproduced: every #999 in the
compiled CSS is a Bootstrap default (popover arrows, modal border, print
styles), so that one needs the reporting app to pin down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The local runtime is unlicensed, and an unlicensed runtime caps concurrent sessions. Past the cap it refuses the sign-in and the login page reports it as a plain "Sign in failed" — indistinguishable from a wrong password. The reason is written only to .mxcli/runtime.log: Maximum number of sessions exceeded! (You are currently using a trial license) mxcli held both halves and joined neither. --screenshot-user filled the form, waited, and saved whatever session it had, so a rejected sign-in produced screenshots of the login page with no error at all. Mendix answers a rejected sign-in by re-rendering the same form, so the username field still being present after the click is the signal that login did not complete. On that signal the login helper now reports the page's own alert text and reads the tail of the runtime log, naming the session cap and the way out when it finds it. Verified against two served pages: a form that stays put exits 2 carrying the alert text, one that navigates away exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The template ships the generated action stubs in a slightly older shape and MxBuild rewrites every one of them on the first build — banner, big.js import, `async function X()` becoming `export async function X()`. They are tracked files, so a fresh clone of a project created by `mxcli new` goes dirty the first time anyone builds it, with ~50 changes nobody wrote. `mx check` does not do this; only a build does, which is why it first shows up long after the initial commit. Run that build while the project is still being created, so the settled form is what gets committed. Best-effort by contract: no JDK, no mxbuild beside the resolved mx, or a failed build is a warning telling the user what the first build will rewrite — never a failed creation. `--skip-build` opts out. A/B on 11.12.1, both git-init'd and then built: --skip-build leaves 50 modified tracked files, the default leaves 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A page whose button targets a page created further down the same script passed `mxcli check` and then failed partway through `exec` — and `exec` is not transactional, so the statements before the failure were already written to the .mpr. `check --references` had an ordered pass for this, but it needs -p, and plain `check` is what gets run. The ordering is knowable from the script alone when the target is created by a plain CREATE: that CREATE would fail if the page already existed, so the script itself asserts the page does not exist yet and the earlier reference cannot resolve against the project either. CREATE OR MODIFY / OR REPLACE assert nothing of the kind, so those stay with --references, which can look at the project. The message also says what the hint could not: a cycle has no valid ordering — create one page without the linking widget and add it with ALTER PAGE … INSERT. Checked against every mdl-examples script for false positives: none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…n CSS A sidebar item reading "All task" is Atlas's closed sidebar — an icon rail at --navsidebar-width-closed: 48px, set in Atlas's own _theme-default.scss. Reproduced against a real compiled theme in a browser: the <a> for "All tasks" is 57px inside a 48px rail, matching the 56-in-48 measured on a live app. No mxcli theme sets a navigation width, which is why all three themes and both variants render it identically. An ellipsis rule was written and then reverted rather than shipped: it only helps where Atlas also sets white-space: nowrap. Where it does not, the label wraps to two readable lines and the rule turns "All / tasks" into "All / t…" — a worse truncation than the one being fixed. The answer is an icon on the nav item, or an open sidebar, and neither is a theme's call to make for every app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
'mxcli test --local' triggered tests from the project's after-startup microflow. That is a boot hook, so a re-run needs a full runtime restart, a failing test manifests as a failed boot, and results have to be scraped out of the runtime log. Local runs now go through the request handler spiked in 2952c2a: startup registers an HTTP endpoint and runs nothing, each test is its own MxTest.Test_<id> microflow invoked by name, and the verdict comes back in the response. A throwing test therefore fails only itself, and because each test has its own variable scope the suffix-renaming the monolithic runner needed is gone. The endpoint executes microflows under a system context, so it is gated four ways, each verified against a live 11.13.0 runtime: - no MXCLI_TEST_TOKEN in the environment => the handler is not registered at all, so a project that kept the MxTest module through a failed cleanup exposes nothing when deployed elsewhere - missing/wrong X-MxTest-Token => 401, compared in constant time - non-loopback caller => 403 - an mf outside MxTest.Test_* => 403 The token is minted per run and reaches the runtime through its environment (new LocalRuntimeOptions.Env), never written into the project. Probing the live runtime also turned up an unfiltered /list returning every microflow in the app; it is now clamped to the test namespace. Docker keeps the after-startup mechanism, which needs no secret and no loopback assumption; --legacy-runner selects it for a local run too. Cleanup additionally removes the generated javasource/mxtest tree, which DROP JAVA ACTION leaves behind. Each gate test was verified to fail against a stubbed guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
`alter page … set Editable = [expr]` (and `set Visible`) produced a project
Studio Pro refused to open:
StorageLoadException: Conditional editability settings has an invalid
value '' for property Attribute
`Attribute` on Forms$Conditional{Visibility,Editability}Settings is a BY_NAME
AttributeIdentifier, so its unset value is the empty string, not null. The
CREATE path already encodes it that way — codec.RegisterTypeDefaults with
EmptyStringFields: {"Attribute"} in mdl/backend/modelsdk/widget_write.go,
whose comment records this exact StorageLoadException from mendixlabs#627. The ALTER
path builds the node by hand in setWidgetConditionalSettingMut and never got
the same treatment, so a widget authored through ALTER was unloadable while
the identical widget authored through CREATE was fine.
Neither `mxcli check` nor `mx check` inspects the stored value, so both
reported success on the broken project.
SourceVariable stays nil: it is a BY_ID reference, where null is the absent
value. "null is wrong here" is per-field, not a blanket rule.
Verified by dumping both encodings (`mxcli bson dump --type page`): a
CREATE-authored and an ALTER-authored widget now produce identical key sets
and values for both settings types.
Fixes mendixlabs#851
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
…ssions `visible: [trim($currentObject/Slug) != '']` and `[length(...) > 0]` silently dropped the entire property. `xpathFunctionName` enumerated only IDENTIFIER, HYPHENATED_ID, NOT, TRUE, FALSE and CONTAINS, so a call to a function whose name is also an MDL lexer keyword never matched xpathFunctionCall. The enclosing `[...]` then failed to parse as an xpathConstraint and matched the generic property-value alternative instead, so the visitor set `Visible` to an array rather than `VisibleIf`, and the builder — which reads only a bool or a string from that slot — never fired. Nothing reported it: `mxcli check` passed, `mx check` passed, and a dropped Visible defaults to "always visible", so the only symptom was a widget that should have been hidden appearing in the running app. toUpperCase/isMatch (plain IDENTIFIERs) and contains (enumerated) were unaffected, which is what made the failure look arbitrary. Define the rule as `xpathWord | NOT` instead. xpathWord is the negated token set already used for name parts, so it self-maintains as the lexer gains keywords — an enumerated list would reacquire this bug with the next function name promoted to a token. This cannot swallow a path: xpathFunctionCall requires a following LPAREN and no xpathStepValue may be followed by one, so bare `empty` still parses as a word, which is what keeps `[Name = empty]` working. NOT is spelled out because xpathWord excludes it. The grammar deliberately does not enumerate a valid function set, because xpathConstraint serves two contexts with different ones: `Visible:`/`Editable:` is a Mendix client expression (trim/length/toUpperCase/find), while a datasource `where` is real XPath (contains/starts-with/ends-with/string-length/not, where `length()` means list length, the aggregates are Java-API-only, and empty/NULL are keywords rather than calls). One rule cannot encode both, so mxbuild adjudicates — it reports a wrong-context call as CE0117 against the real version's rules, which no table here could track. Also add MDL-WIDGET19 as the general guard, per the issue's request that encoding failures be loud: a `Visible`/`Editable` value that is neither routed to VisibleIf/EditableIf nor a recognized static form is the residue signature of any conditional the visitor could not build. It now fails `check` instead of disappearing on write. Verified by reverting the grammar fix and confirming the rule fires on the reported MDL. Verification, on a blank Mendix 11.6.6 app via `mxcli run --local` + Playwright (the repro script carries the Bug852.Verify page for this; Slug is three spaces so trim() changes the outcome): pre-fix all 5 markers render — TRIM_HIDDEN and LEN_HIDDEN should not post-fix only the 3 that should be visible render `mx check` reports 0 errors both ways, so this could only be confirmed in a browser. XPath regression checked too: `[Name = empty]`, `[Name = NULL]`, not(), contains(), starts-with() and string-length() all still parse and check clean. Behaviour change worth noting: `count(…)` and `empty(…)` are also keyword tokens and now parse, but they are not client-expression functions, so mxbuild now reports CE0117 where it previously saw nothing — the property having been dropped before reaching it. Loud beats silent, but anyone who had written one will newly see an error. Fixes mendixlabs#852 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
`download file $Doc;` was accepted by the grammar, the visitor, the flow builder and `mxcli check`, and `mxcli exec` reported "Created microflow" — but microflowActionToGen had no *microflows.DownloadFileAction case, so the action hit `default: return nil` and the enclosing ActionActivity was serialized with no Action at all. DESCRIBE then rendered "-- Empty action" and `mx check` failed with CE0008 "No action defined." The statement passed every stage that reports something and disappeared at the one that reports nothing. This is the same silent-drop mechanism as the microflowObjectToGen default branch in mendixlabs#791. Add the case, setting FileDocumentVariableName, ShowFileInBrowser and ErrorHandlingType (Rollback default). The storage key is ShowFileInBrowser, not ShowInBrowser; the gen setter binds the right one (legacy's parseDownloadFileAction reads the wrong key — a latent legacy bug the existing reader test documents). Tested at the round trip rather than the reader. A reader-only test cannot catch this class: it starts from BSON the writer never had to produce, which is why TestActionFromGen_DownloadFile stayed green the whole time. The new test asserts the round-tripped ActionActivity has a non-nil Action — the CE0008 shape itself — and covers both the plain and `show in browser` forms. Fixes mendixlabs#850 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
'mxcli test -p app.mpr --local' failed with MxBuild's "the project file path should be an absolute path" and a page of Windows sample requests, which tells a user who typed a relative -p nothing about what to do. 'mxcli run' already handled this (findings #17), but the fix lived in cmd_run.go rather than in the code that talks to MxBuild, so the next caller — StartLocalApp, via the test runner — re-hit the same error. Absolutizing in ServeServer.Build covers every caller, present and future. LocalAppOptions.applyDefaults and testrunner.Run resolve the path too, so DeployDir, the runtime log path, and the paths named in error messages are not derived from a relative value. Tested by pointing a ServeServer at an httptest fake and asserting on the request body actually sent — something the CLI-layer fix could not be tested for, which is part of why it did not generalise. Both new tests were verified to fail against the reverted fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
mxcli-todo findings: nine fixes from bootstrapping an app (check-parity, first-build cleanliness, theming, diagnostics)
'mxcli test --local' booted the app once per invocation, so every re-run
paid the ~30s cold boot even for a one-character edit. --watch keeps the
runtime and the mxbuild serve server up and re-runs the suite on every
change, to a test file or to the project's model.
Measured on an 11.13.0 app: first run ~30s, then ~2.0s from editing a test
to a verdict on screen and ~2.1s from editing the microflow under test.
Every re-run in the session applied via reload, not restart.
Two hazards this loop has that the run --local dev loop does not:
- The runner writes to the project it is watching, so injecting the test
microflows moves the very mtime being polled. Baselines are taken after
the injection and rebuild settle; getting this wrong is an infinite
rebuild loop, verified absent by idling a session.
- The injected set changes during the session, so cleanup drops what is
currently injected rather than what was injected at boot — otherwise a
test added mid-session is left behind in the user's project. A deleted
test's microflow is dropped explicitly, since CREATE OR REPLACE says
nothing about removal and a lingering flow would keep reporting a stale
pass.
Ctrl-C stops the runtime, restores the project, and says so. --watch is
rejected with --legacy-runner, without --local, and with --skip-build:
those paths can only re-run by restarting.
Verified live across a session that edited, deleted and added tests and
changed the microflow under test, then confirmed the project was fully
restored. Each new test was checked to fail against a stubbed guard.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
Fix silent drops of conditional expressions with keyword-named functions
'mxcli test --local --watch' keeps a runtime warm within a session, but a session still opens with a ~30s cold boot. --attach removes even that by running against an app already up: 2.83s for the first attached run and 2.30s for a repeat, with the dev app still serving throughout. Attaching cannot be unilateral, and the reason shapes the design. The endpoint's handler is registered by the after-startup microflow, which runs only at boot, and its token comes from the runtime's environment — neither can be added to an app that is already running. So the dev loop opts in with 'mxcli run --local --test-endpoint', which is also where the decision belongs: hosting the endpoint means the developer's own app carries it, and tests write to the database they are looking at (said on every attach, not just in the docs). What a second process can drive is the dev loop's serve and admin APIs, both plain loopback HTTP. So an attach applies its own injections deterministically rather than waiting on someone else's watcher, and does not require --watch. The host publishes .mxcli/test-endpoint.json (0600, written-then-renamed) with the ports, token, admin password and its PID; the PID check turns a handshake left behind by a SIGKILLed dev loop into one clear message instead of a confusing connection error later. The project's own after-startup microflow is chained rather than displaced, so the app still boots normally, and the endpoint is removed on exit. Ownership is strict: an attach adds and removes only its own test microflows. The endpoint, the after-startup setting and the MxTest module belong to the hosting loop. A change needing a runtime restart is refused rather than half-applied. Running it live caught a bug review had not: the M2EE admin API and the test endpoint are different secrets, and passing the endpoint token to the admin API failed with "Authentication failed" after the test microflows had already been injected. The handshake now carries the admin password separately, taken from the resolved value so an override still works. Help, syntax topics, skills and site docs updated throughout; the running-tests page also no longer claims Docker is required. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
Brings the branch up to date with main (PRs #107, #108 and the commits behind them) so PR #109 merges cleanly and CI runs against the current base. No conflicts. The fix-issue.md symptom table merged via the union driver as intended — both this branch's rows and main's survive, with no duplicates. Full suite green on the merged tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
Add test endpoint for warm test re-runs without restart
A published OData service created purely from MDL passed `mxcli check` and then failed the build on two counts, neither of which the author could see coming. CE0729 "The service name should not be empty": `Name` (the document) and `ServiceName` (the name in the OData metadata document) are different properties, and CREATE set only the first. The consumed path has defaulted the same field to the document name for CE0339 all along. CE7375 "Attribute ID ... must be published and be the key when associations are exposed as an associated object id", firing even with no associations exposed: PublishAssociations defaults to false, which is object-id mode, and Mendix only allows that when the system ID is published as the key — while MDL's `expose (Attr (KEY))` publishes an ordinary attribute. The finding framed the second as a non-persistable-entity problem. It is not: measured on 11.12.1, the identical service over a PERSISTENT entity with a unique key builds 0 errors with true and CE7375 with false. The default broke every published service; non-persistable is only where no workaround exists, because publishing the ID of a non-persistable entity is forbidden. So default an unspecified PublishAssociations to true. That is not a preference, it is the only value that can build from the MDL people write. An explicit false is still honoured (tracked separately from "absent" so it survives), warned about when a published entity is non-persistable, and `create or modify` no longer flips a stored value the script never mentioned. Nothing in-repo caught either default: doctype-tests/10-odata-examples.mdl sets both properties explicitly. Both bug-test scripts now build 0 errors on 11.12.1 with no workarounds — no ServiceName, no follow-up ALTER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The grammar accepts any `name: value` pair in an OData property list and the visitor's switch had no default, so `ReadMicroflow:` or `ServiceNam:` parsed cleanly, checked cleanly, executed "successfully" — and left the model without the property. The ALTER path has always answered "unknown OData service property"; CREATE, PUBLISH ENTITY, the OData client and the external entity had nothing. The name is lost in the visitor, so the visitor is where it has to be recorded: each switch grows a default that appends to UnknownProperties, and a check-time validator reports them as MDL-ODATA01 before anything is written. The message guesses the intended property (prefix/substring, then one edit) rather than only listing the known ones — with a list alone the reader is still diffing two spellings by eye. One correction to the report: `Pagesize:` is not among the casualties. The visitor lowercases before matching, so casing is never a typo. A test pins that, so the rule cannot start flagging it later. No false positives across every script in mdl-examples. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The three OData query options were written as literal true in the BSON writer, with no MDL above them. Countable is not a cosmetic default: it forces every read-microflow-backed resource to declare a System.ODataResponse parameter and compute a count, and over a full CSV scan that count is the expensive part of the request. They are now publish-entity properties. Tri-state is load-bearing — they default to true, so "unset" and "false" cannot share a representation or every existing script would quietly turn them off. Unset stays nil, the writer resolves nil to true, and the reader maps a stored true back to nil so DESCRIBE prints what the author wrote rather than three defaults on every resource. Verified on 11.12.1: `Countable: No` with a read microflow that takes no $Response parameter builds 0 errors — the combination that could not be expressed before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Three slips in one emit block, each of which broke the DESCRIBE-roundtrip property the review checklist asks for: - `ReadMode: CallMicroflow:Module.Read` is the backend's storage spelling and matches no MDL value. It now prints `microflow Module.Read`, the form the author wrote. - `expose (...)` takes a bare member name; the stored name is fully qualified, so `Module.Entity.Attr` was emitted and did not parse. `IsPartOfKey` becomes `KEY` for the same reason — both parse, but only one is documented. - `as '<name>'` printed the entity TYPE's exposed name where the entity SET's belongs. That one is invisible until the two differ (Studio Pro exposes the type singular and the set plural), and then a describe -> exec cycle silently renames the set the $metadata serves. Proved by round trip rather than by eye: describe -> check parses, then drop the service, exec the describe output, describe again — byte-identical, and mxbuild reports 0 errors on the rebuilt model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ptions `ReadMode: microflow Module.MF` has worked through the whole pipeline — grammar, visitor, AST, BSON — and was documented nowhere, so the only way to find it was to read the Go source. Without it, "publish persistent entities" is the only shape the docs describe, which sends anyone with data outside Mendix down a materialise-into-the-database path they do not need. The syntax topic now carries ReadMode/InsertMode/UpdateMode/DeleteMode in their microflow form, ServiceName and PublishAssociations with their defaults, and the three query options. The skill gains a worked non-persistable example — read microflow, no copy of the data, no refresh job — with the two things that bite first: the $Response parameter Countable requires, and why PublishAssociations must stay at its default there. The skill's existing example also carried `PublishAssociations: No`, which cannot build: object-id mode requires the system ID as the published key, and that example publishes an ordinary attribute. Removed, with the reason. The new example was executed against a real .mpr and builds 0 errors on 11.12.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The skill's table listed `Redshift` and `SQLServer`. Neither is in Studio Pro's
connector picker on any version available here — read out of the shipped bundle
at modeler/ide-client/database-connector-editor/, identical on 11.10.0, 11.12.1
and (per the report) 11.13.0: MSSQL, MySQL, Oracle, PostgreSQL, Snowflake, BYOD.
mxcli writes the type string through unchanged and mxbuild does not validate it
either — `type 'Redshift'` builds 0 errors — so a wrong value hides behind a
green build and only shows up as a connection that does not connect. `check` now
says so. A warning rather than an error: the set is version-specific and mxcli
cannot prove a string wrong on a version it has not seen.
The table was also missing the entry that matters most. `BYOD` ("Other") forces
connection-string configuration and skips the driver-presence check, which makes
any JDBC driver Mendix ships no picker entry for usable by dropping the JAR in
userlib/. That is what the finding needed for DuckDB, and it read as impossible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Run from a solution root — one folder per app, no .mpr at the root — `mxcli init` reported success and wrote tooling pointing at `project.mpr`, a file that does not exist. The hardcoded default made a missing project look like a found one. It now looks one level down and lets the candidate count decide: none, warn that the generated paths are placeholders; exactly one, say which project it is and initialise that directory; more than one, refuse, list them, and print the command naming a specific app. A solution repo should not get one app's tooling by coin flip. One level only. A Mendix app keeps its .mpr at its own root, so walking deeper would start finding deployment copies and backups. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`alter settings configuration 'Default' …` is the write form; the read form was a parse error. `describe settings configuration '<name>'` now parses and prints that one configuration as re-executable MDL, naming the configurations that do exist when the name is wrong. Where MDL has `alter X <selector>`, reaching for `describe X <selector>` and getting a parse error teaches the wrong lesson. `show settings configurations` also gains ApplicationRootUrl. It decides the host the app answers on, so "did my root URL land?" is the obvious question to ask that command, and the summary could not answer it — `describe settings | grep` was the only way. An empty DatabaseUrl is skipped too, rather than rendering as a bare comma that reads like a bug in the reader. Both dumps go through one emit helper, so the whole-settings output and the single-configuration output cannot drift apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`make build` needs an `antlr4` launcher, and the error when it is missing said only "install ANTLR4" — while the version is load-bearing. CI pins antlr4-tools 0.2.2 with generator 4.13.2 against the 4.13.1 runtime in go.mod, and a generator/runtime mismatch is a classic ANTLR failure mode that surfaces as a generated parser that will not compile. That pin lived only in the workflow YAML, where nobody hits it. The pin now lives in mdl/grammar/Makefile, the error message quotes it, and `make -C mdl/grammar bootstrap` does the pip install. `generate` also notes when ANTLR4_TOOLS_ANTLR_VERSION is unset, since the default is whatever antlr4-tools last downloaded. The README build-from-source recipe gains the bootstrap step and says what the build actually needs — network and a JVM, not only Go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Step 6 was documented as "downloads the correct mxcli binary", and on Linux it does not download: it hard-links the mxcli you ran into the project, sharing the inode. That is the right behaviour for a from-source build — the app folder gets your binary, not the nightly — but the help text described the opposite, which is what led one report to delete the linked binary before moving files around. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Published OData services could not build from MDL — plus init/settings/database-type fixes (formula1 findings)
…y skipping `ALTER MODULE X ADD JAR DEPENDENCY (…)` wrote the coordinate, `list jar dependencies` reported it, the build went green — and the runtime threw "No JDBC driver found in app for URL". Nothing between the author and the runtime said the jar was never fetched. The write was never wrong. Declaring and resolving are separate steps: `mx sync-java-dependencies <project.mpr>` downloads into vendorlib/, Studio Pro runs it when you edit Module Settings, and nothing ran it headless. Measured on 11.12.1: a full `mxbuild --target=deploy` emits a build.gradle with no dependencies block and downloads nothing; the sync command then fetches the jar. (The open question in the report was whether mxbuild skips Maven resolution or mxcli writes somewhere MxBuild cannot read — neither.) Wired at three levels so the gap cannot stay silent: the executor says so the moment it writes a coordinate that is not in vendorlib/, `mxcli sync-java-deps` does the resolution on demand (`--check` exits non-zero as a build gate), and `run --local` vendors anything missing before boot, so the warm loop works from a fresh clone. Resolution needs network, so each call site is best-effort and names the command to retry. Falling back to another version's mx is now stated rather than silent — a version mismatch otherwise surfaces as an mpr-format complaint that reads like a corrupt project. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`$Total = 5;` failed with "no viable alternative at input '$Total=5'" while `DECLARE $Total Integer = 0;` worked — and so did every other assignment in MDL: `$X = HEAD($List)`, `$X = create M.E (…)`, `$X = execute database query …`. Assignment existed only as a prefix on specific activity statements, plus a `SET $Var = expression` statement, so a plain value needed a keyword that nothing in the error or the surrounding syntax suggested. Rather than improve the error, make the form people reach for work. SET is now optional; both spellings produce the same MfSetStmt and the same ChangeVariableAction, and the keyword form is untouched. Checked for regressions with a control binary rather than by reading the grammar: stash the .g4, regenerate, build bin/mxcli-control, and sweep every script in mdl-examples with both. Thirteen fail — the same thirteen, all pre-existing. ANTLR's adaptive prediction picks the activity-prefixed alternatives on its own; no reordering was needed. Executed against a real .mpr, mxbuild reports 0 errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`mxcli test tests/ -p app/App.mpr` failed with "no such file or directory" for a tests/ sitting right next to the .mpr. Resolving against the process CWD is defensible on its own, but mxcli otherwise encourages naming the project rather than standing in its directory, so the two conventions collide and the failure reads as a missing directory. The fallback applies only when the CWD-relative path does not exist. A tests/ in both places still resolves to the one the user is standing in — silently preferring the project's copy would run the wrong suite — and a path that exists in neither is passed through unchanged, so the error names what was typed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A `dynamic` SQL override still requires a value for every parameter the query
*definition* declares, including ones the replacement SQL never mentions — the
parameter list belongs to the definition, not to the string. And a `{param}`
placeholder can be concatenated into a path, which is what keeps an absolute
path out of the model: bind the data directory as a constant and build the file
name around it.
Both come from a DuckDB read path verified end to end on Mendix 11.13, which
also settles the open question about `type 'BYOD'` — the runtime accepts it, not
just the editor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
JAR dependencies never reached the classpath; `$Total = 5;` now parses (formula1 findings §11–§13)
Making SET optional put `$X = <expr>` in front of every `VARIABLE EQUALS <function-call>` statement in the grammar. ANTLR picks the lowest-numbered alternative that matches, so `$Sum = sum($List.Price)` stopped reaching aggregateListStatement and fell through to the generic SET conversion, which joined the list and the attribute into one name. mxbuild rejected the result on both engines: [CE0109] "Undefined variable 'ProductList.Price'." [CE0015] "Aggregate function must specify a valid attribute." Move setStatement last so it only claims what no dedicated rule parses, and make the SET conversion agree with the canonical one — it also dropped the per-item expression of `sum($List, $currentObject/Price * 0.21)`, which buildListAggregateAsFunction never appended in the first place. That half was wrong before the bare form ever reached it, so `set $X = sum($List.Price)` was broken too. Proved by reverting both halves: the new tests fail with the reported symptom, and the same script executed against a blank 11.12.1 project reports 8 errors on a control binary and 0 with the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Aggregates broke when SET became optional (upstream CI regression)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs
ako/mxcli:mainintomendixlabs/mxcli:main— 44 commits since #857.Most of this came out of two rounds of external findings against real Mendix apps; every fix was verified against mxbuild (
mx check→ 0 errors) on Mendix 11.12.1, and the runtime work in a browser.Warm local test loop
mxcli test --localruns microflow tests with no Docker daemon, on mxcli's own runtime and its own<project>_testdatabase.MXCLI_TEST_TOKEN; loopback-only; clamped toMxTest.Test_*.reload_model, which makes--watchpossible: ~30s first run, then ~2s from an edit to a verdict.--attachruns against an app already up underrun --local --test-endpoint, skipping the boot entirely.Published OData services
Countable/SkipSupported/TopSupported.DESCRIBEof a published service is re-executable: entity-set exposed name,KEYinstead ofIsPartOfKey, byte-identical on a describe → drop → recreate → describe round-trip.Managed Java dependencies
ALTER MODULE … ADD JAR DEPENDENCYwrote the coordinate but nothing downloaded the jar — mxbuild does not resolve them,mx sync-java-dependenciesdoes. That step is now wired in: newmxcli sync-java-deps [--check], run automatically byrun --localbefore the database check, and warned about at execute time.MDL language
SETis now optional:$Total = 5;parses, matching every other assignment form.IF NOT EXISTS/IF EXISTSon event handlers;-reads MDL from stdin.New checks
BEFORE CREATEis refused; demo users that cannot materialise are warned about.Fixes
run --local: relative project paths resolved where MxBuild is called; the app's configured Application root URL honoured; a refused sign-in says why instead of "Sign in failed".mxcli new: runs the first build so a fresh clone does not go dirty.mxcli init: no longer invents a project when the directory has none; the SessionStart hook survives an idle reap; seeds a lint config excluding the System module.Attributewritten as""not null;DownloadFileActionon the modelsdk engine.DESCRIBE SETTINGS CONFIGURATION '<name>'reads one configuration and shows its root URL.