Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bf724ac
Add repeatable upstream-PR link generator (script + /mxcli-dev:upstre…
claude Jul 29, 2026
253d60d
fix(workflows): version-gate call-microflow storage name at Mendix 11…
claude Jul 29, 2026
6f0750e
fix(settings): reject unconfigured DefaultLanguageCode in ALTER SETTI…
claude Jul 29, 2026
3c9c7fb
fix(grammar): allow V3 as an identifier — remove dead lexer token (FI…
claude Jul 29, 2026
2099bbe
fix(workflows): normalize with(...) param name to bare form (FINDINGS…
claude Jul 29, 2026
53cb961
fix(mdl): parser ergonomics batch — index/role quoting, sort-by, asso…
claude Jul 29, 2026
cdaab9f
fix(workflows,lint,docs): describe param round-trip, SEC005 hint, ski…
claude Jul 29, 2026
9830018
fix(microflows): reject create/change member ref to a non-existent as…
claude Jul 29, 2026
97611a7
feat(check): flag duplicate widget names on a page (FINDINGS #15)
claude Jul 29, 2026
08746d0
feat(check): flag unmapped workflow call-microflow parameters (FINDIN…
claude Jul 29, 2026
68acf0e
feat(check): flag aggregate/unknown functions in create/change attrib…
claude Jul 29, 2026
1b390dc
fix(workflows): match call-microflow outcomes to return type + normal…
claude Jul 29, 2026
48548ca
Merge pull request #53 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Jul 29, 2026
c91f518
fix(pages): reject a page with widgets but no Layout instead of silen…
claude Jul 29, 2026
b6a5043
fix(workflows): parse CallMicroflowActivity storage name on read (11.…
claude Jul 29, 2026
0580ead
Merge pull request #54 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Jul 29, 2026
c663f7a
fix(alter-page): resolve association-source entity for INSERT/REPLACE…
claude Jul 30, 2026
25b02ac
fix(check,describe): clear two microflow-check false positives + rest…
claude Jul 30, 2026
b87e1b9
fix(vscode-mdl): make `make lint` pass under TypeScript 6
claude Jul 30, 2026
a91e732
fix(alter-page): resolve microflow/nanoflow datasource entity for INS…
claude Jul 30, 2026
0b4feb7
fix(settings): overlay server configurations instead of rebuilding th…
claude Jul 30, 2026
4deca96
fix(settings): reject invalid typed values instead of ignoring them (…
claude Jul 30, 2026
f1daae0
docs(show_page): document #56 — describe omits redundant $currentObje…
claude Jul 30, 2026
672c3e3
Merge pull request #55 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Jul 30, 2026
f37e056
Merge pull request #56 from ako/claude/mxcli-issues-analysis-g3qmt6
ako Jul 30, 2026
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
46 changes: 46 additions & 0 deletions .claude/commands/mxcli-dev/upstream-pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# /mxcli-dev:upstream-pr — Link to open a PR into upstream (mendixlabs/mxcli)

Generate a prefilled GitHub **compare** URL that opens a PR merging this fork
(`ako/mxcli`) into the upstream fork (`mendixlabs/mxcli`).

**Why a link instead of opening the PR directly:** `mendixlabs/mxcli` is not in
this session's tooling scope, so the PR can't be created via the GitHub API. The
compare URL prefills the title and body; the user opens it and clicks "Create
pull request".

## Steps

1. Confirm what's actually unmerged upstream. If you have (or can fetch) the
upstream base, build the range explicitly:
```bash
git fetch https://github.com/mendixlabs/mxcli main
git log --no-merges --oneline FETCH_HEAD..HEAD
```
If the fetch is blocked or unnecessary, fall back to summarising the fork's
`main` since the last sync.
2. Draft a concise **title** and a Markdown **body** grouping the changes by
theme (one bullet per finding/fix). Reuse the structure from the last sync PR.
3. Generate the link with the script — pass the body on stdin so multi-line
Markdown encodes cleanly:
```bash
scripts/upstream-pr-link.sh --title "<title>" --body-file - <<'BODY'
<markdown body>
BODY
```
Or let it auto-build the body from commits:
```bash
scripts/upstream-pr-link.sh --commits FETCH_HEAD..HEAD
```
4. Present to the user:
- the prefilled compare URL,
- the **title** and **body** as plain text (fallback if the browser trims a
long prefilled body).
5. Remind the user that `mendixlabs/mxcli` isn't in scope, so this is a link —
offer to `add_repo` and open the PR via API if they'd rather.

## Notes

- Defaults are `ako/mxcli:main → mendixlabs/mxcli:main`. Override with
`--fork`, `--upstream`, `--base`, `--head` for other syncs.
- Do **not** include the model identifier in the title or body.
- This is a link generator only — it does not push, commit, or open anything.
3 changes: 2 additions & 1 deletion .claude/lint-rules/sec_strict_mode.star
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ def check():
return [violation(
message="Strict mode is disabled. This weakens XPath constraint enforcement and is relevant to CVE-2023-23835.",
location=location(module="", document_type="security", document_name="ProjectSecurity"),
suggestion="ALTER PROJECT SECURITY STRICT MODE ON",
# mxcli/MDL cannot toggle strict mode — it is a Studio Pro-only setting.
suggestion="Enable strict mode in Studio Pro: Project Security > Enable 'Check security' and turn on strict-mode XPath validation (not settable via MDL).",
)]
10 changes: 10 additions & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,23 @@ to the symptom table below, so the next similar issue costs fewer reads.

| Symptom | Root cause layer | First file to open | Fix pattern |
|---------|-----------------|-------------------|-------------|
| Any `ALTER SETTINGS` (any section) reports success but the Default configuration's **Custom settings** are gone, **Tracing** is reset, and every **constant override** shows blank in Studio Pro — Integer/Long constants then fail the build | The configuration was re-serialized from `model.ServerConfiguration`, which carries only the modelled fields, so CustomSettings/Tracing/OpenAdminPort/OpenHttpPort were dropped, the list version markers downgraded 3→2, and overrides were written with a flat `Value` instead of the nested `SharedOrPrivateValue` the platform reads | `mdl/settingsoverlay/settingsoverlay.go` (`Configurations`, `ServerConfiguration`, `ConstantValues`) — called by both `mdl/backend/modelsdk/settings_write.go` and `sdk/mpr/writer_settings.go` | Overlay onto the raw document instead of rebuilding: write only the fields the read path populates, take each list's marker from what is stored, and update a constant override in the slot it already occupies (nested if nested, flat if flat). New override → nested. New configuration → clone a sibling's shape, empty its collections, mint a fresh `$ID`. Refuse the write when `RawParts` is empty. Repro `mdl-examples/bug-tests/801-alter-settings-preserves-configuration.mdl`. Issue #801 |
| `ALTER SETTINGS` / `CREATE CONFIGURATION` prints "Updated …" but `DESCRIBE SETTINGS` shows the old value — an Integer property was given a non-numeric value, or a Boolean anything other than `true` | `strconv.Atoi`'s error was discarded (`if v, err := …; err == nil`) so the assignment was skipped while the caller still printed success; the boolean form compared against `"true"`, silently mapping every other spelling to false | `mdl/executor/cmd_settings.go` (`settingsInt`, `settingsBool`) + `mdl/executor/validate_settings.go` (`typedSettingsKeys`, MDL-SET01/MDL-SET02) | Parse through a helper that returns a validation error naming the setting and the offending value, and register the property in `typedSettingsKeys` so `mxcli check` and the LSP flag it before the project is opened for writing. `TestTypedSettingsKeys_MatchExecutor` guards the table against drifting from the executor's switch. Repro `mdl-examples/bug-tests/805-alter-settings-typed-values.fail.mdl`. Issue #805 |
| MCP op reports `MCP error -32000: Request timed out` but the page/document/entity EXISTS in Studio Pro afterwards | Studio Pro's ~30s server-side per-call limit fires while the op still applies — a client false failure, not a server rejection | `mdl/backend/mcp/timeout.go` (`isTimeoutErr`, `timeoutVerifyDelay`, `pedUpdateVerify`, `pedDocumentExists`) | Verify-on-timeout, never blind-retry a non-idempotent op: idempotent root-replace (`pgWritePage`) retries once; creates confirm via `ped_find_document`; entity adds confirm via a shallow `/entities` read. Unverified → error with save-before-re-run guidance (a blind re-run from a fresh session duplicates elements) |
| Retrieve/datasource XPath with a `[%…%]` token (e.g. `[System.owner = '[%CurrentUser%]']` or `[Title = '[%CurrentUser%]']`) fails `mx check` CE0161, but `[Title='abc']` is clean | NOT a token-storage bug — tokens store intact and a type-valid token (`[DueDate < '[%CurrentDateTime%]']`) passes. The failures are semantically-invalid XPath: (1) String/scalar attr compared to a User token = type mismatch; (2) `System.owner`/`changedBy`/… referenced on an entity that doesn't store it (needs `alter entity X add attribute owner: autoowner`) | `mdl/executor/validate.go` (`validateRetrieveConstraints`, `baseSystemMemberRe`) — diagnose with `mxcli bson dump --type microflow` + `mx check`; verify the token alone works | Don't "fix" storage — it's correct. Add a `--references` check: collect retrieve `(entity, constraint)` in `flowRefCollector`, look up the entity via `buildEntityIndex` (`ListDomainModels`), and flag a base-entity `System.<member>` ref (regex excludes `/`-traversed refs) when the entity flag (`HasOwner` etc.) is off, with the `alter entity … add attribute …: auto…` hint. Same-script-created entities aren't in the project index, so the check only fires against existing project entities. **Also fixed**: a bare `[%token%]` *inside* a bracketed constraint (`[DueDate < [%CurrentDateTime%]]`) stored unquoted (the inline path keeps the raw source) → CE0161. `normalizeXPathTokens` (`mdl/visitor/visitor_page_v3.go`) requotes bare tokens; wired into `buildXPathSourceExpression`, the multi-predicate `predicateSources`, `buildXPathString`, and `bracketedXPathFromExpr` (already-quoted tokens untouched). Issue #641 |
| `describe` shows `$var = list operation ...;` | Missing parser case | `sdk/mpr/parser_microflow.go` → `parseListOperation()` | Add `case "microflows$XxxType":` returning the correct struct |
| `describe` shows `$var = action ...;` | Missing formatter case | `mdl/executor/cmd_microflows_format_action.go` → `formatActionStatement()` | Add `case *microflows.XxxAction:` with `fmt.Sprintf` output |
| `describe` shows `$var = list operation %T;` (with type name) | Missing formatter case | `mdl/executor/cmd_microflows_format_action.go` → `formatListOperation()` | Add `case *microflows.XxxOperation:` before the `default` |
| Compile error: `undefined: microflows.XxxOperation` | Missing SDK struct | `sdk/microflows/microflows_actions.go` | Add struct + `func (XxxOperation) isListOperation() {}` marker |
| `TypeCacheUnknownTypeException` in Studio Pro | Wrong `$type` storage name in BSON write | `sdk/mpr/writer_microflow.go` | Check the storage name table in CLAUDE.md; verify against `reference/mendixmodellib/reflection-data/` |
| A `create page` reported success but the built page is EMPTY — every widget gone — and `mx check` fails with CE1613 "The selected layout 'dummyModule.dummyName' no longer exists" | The page had no `Layout:` clause, so `buildPageV3` created no `LayoutCall`; the widget tree is built into the LayoutCall's placeholder arguments, so with no LayoutCall the widgets have nowhere to attach and are silently dropped. `dummyModule.dummyName` is *Mendix's* placeholder for a missing layout, not something mxcli writes | `mdl/executor/cmd_pages_builder_v3.go` (`buildPageV3`, the `if page.LayoutCall != nil` block) | Reject a page that has body widgets (or placeholder blocks) but no LayoutCall — distinguish "no Layout: clause" from "layout not found" in the message. A Mendix page always needs a layout; snippets (buildSnippetV3) are layout-less and unaffected. Repro `mdl-examples/bug-tests/266-page-without-layout-drops-widgets.mdl` |
| `ALTER PAGE INSERT`/`REPLACE` into a list bound `from association` **or** `datasource: microflow/nanoflow` produces a widget whose Attribute binds to the WRONG entity (the outer data view's) or nothing — `mxcli check` ✓ but `mx check` fails **CE1613** ("attribute no longer exists") or **CE0402** ("No value specified"); DESCRIBE masks it by printing only the short attribute name | The ALTER mutator read the enclosing entity from `DataSource.EntityRef.Entity`, only set for a DIRECT ref (database). An `AssociationSource` stores its destination on the last `DomainModels$EntityRefStep` of an `IndirectEntityRef`; a `MicroflowSource`/`NanoflowSource` stores NO entity at all (its entity is the flow's RETURN type, in the flow document). Either way the list reported no entity, so the context stayed at the outer data view (or empty) | `mdl/backend/pagemutator/mutator.go` (`extractEntityFromDataSource`+`lastStepDestinationEntity` for association; `EnclosingDataSourceFlow`+`findNearestDataSourceDoc` for flows) + `mdl/executor/cmd_alter_page.go` (`resolveDataSourceFlowEntity`) | Association: read the `IndirectEntityRef`'s last `EntityRefStep.DestinationEntity`. Microflow/nanoflow: the mutator returns the nearest-enclosing (or own, for INTO) flow QN — a nearer non-flow source shadows an outer flow — and the executor resolves its return entity via `getMicroflowReturnEntityName`/`getNanoflowReturnEntityName`. Guards `TestEnclosingEntity_AssociationSource`, `TestEnclosingDataSourceFlow`; repro `mdl-examples/bug-tests/55-alter-page-insert-assoc-binding.mdl`. FINDINGS #55 |
| `mx check` fails to LOAD the project — `StorageLoadException: ... 'Module.Name' is not a valid AttributeIdentifier` after a `create`/`change` with a `Module.Assoc = …` member, yet `mxcli exec` reported success | A one-qualifier member (`Module.Name`) that isn't a known association was written as an *attribute* ref, but a one-qualifier name can't be a valid attribute (attributes are bare or `Module.Entity.Attribute`) → unloadable .mpr. Usually the association's `create` failed earlier (non-idempotent) leaving it absent | `mdl/executor/cmd_microflows_builder_actions.go` (`resolveMemberChange`, the "Not an association in the authored module" branch) | When the domain model is available and the one-dot member isn't in `dm.Associations`/`dm.CrossAssociations`, `fb.addError` with an actionable "create the association first" message instead of writing an Attribute. Same-script associations are visible via `GetDomainModel`, so no false positive. Repro `mdl-examples/bug-tests/264-create-member-unknown-association.mdl`. FINDINGS #51 |
| Runtime `Failed to load model: ... Class 'Workflows$CallMicroflowTask' could not be found` — the WHOLE app won't boot, yet `mxcli check` ✓ and `mx check` → 0 errors | Mendix 11.9 (WOR-2802) split MicroflowBasedActivity into CallMicroflowActivity + AIAgentTaskActivity, renaming the workflow call-microflow on-disk `$Type` from the pre-11.9 `CallMicroflowTask` to `CallMicroflowActivity`. Writing the old name to an 11.9+ project is fatal at boot only. Evidence: 11.6.3 modeler = only Task; 11.10 modeler = both (Task marked "Removed ... WOR-2802" + a conversion routine); 11.10+ runtime jars = only Activity | `mdl/backend/modelsdk/workflow_write.go` (`applyCallMicroflowStorageName`, `useCallMicroflowActivityName`) + legacy `sdk/mpr/writer_workflow.go` (`renameCallMicroflowTypeBSON`) | Version-gate the emitted `$Type` at 11.9 (same boundary as the `HasOwner`→`HasOwnerAttr` gate): build with the legacy name, rewrite the tree to `CallMicroflowActivity` for `pv.IsAtLeast(11,9)`; register codec TypeDefaults + list-marker under BOTH names; read path already folds both into one semantic type. Repro `mdl-examples/bug-tests/263-workflow-callmicroflow-storage-name.mdl`. FINDINGS #39 |
| `mxcli check` rejects a **valid** microflow: **MDL048** on `retrieve … where [id = '[%CurrentUser%]']` (the standard signed-in-user idiom) — but `mx check` → 0 errors | MDL048 targets constraining `id` against a STORED value (String/Long var or plain literal), which Mendix XPath can't do; it also matched the `'[%CurrentUser%]'` **server token**, which Mendix DOES resolve to a GUID | `mdl/executor/validate_microflow.go` (`checkXPathIdConstraint`) | Skip an operand of the form `'[%…%]'` (a resolved token) before flagging. Case still fires for real stored-id values. Test `TestValidateMicroflow_XPathIdConstraint` (CurrentUser case); repro `mdl-examples/bug-tests/52-53-microflow-check-false-positives.mdl`. FINDINGS #53 |
| `mxcli check` rejects a **valid** microflow: **MDL045** ("`/` is division") on `round($a div $obj/Attr * 100)` — division whose divisor is an association-attribute path — but `mx check` → 0 errors | The MDL grammar parses `div`/`*`/`/` at one precedence level, so `$a div $obj/Attr` mis-nests as `($a div $obj) / Attr`; MDL045 saw the `/ Attr` as division. But `Attr` is a bare member name — Mendix has no `/` division operator and re-parses the raw `$obj/Attr` as a path (serialized output preserves the `/`, so the build is clean) | `mdl/executor/validate_microflow.go` (`exprHasSlashDivision`) | Don't flag a `/` BinaryExpr whose RIGHT operand is a bare `IdentifierExpr` (member navigation); real division has a numeric/paren/variable divisor. Test `TestValidateMicroflow_SlashDivision` (div-by-assoc cases); repro `mdl-examples/bug-tests/52-53-microflow-check-false-positives.mdl`. FINDINGS #52 |
| `describe microflow` prints `-- Empty action` for a `set task outcome` / `open user task` / `notify workflow` statement (default engine); a describe→drop→exec round-trip silently drops it. Legacy engine (`MXCLI_ENGINE=legacy`) describes it fine | The modelsdk read path (`actionFromGen`) had no case for the workflow microflow actions, so they read back as nil → "Empty action". The write path + DESCRIBE formatter already handled them; only the modelsdk read case was missing | `mdl/backend/modelsdk/microflow_read_actions.go` (`actionFromGen`) | Add cases for `genMf.SetTaskOutcomeAction` / `OpenUserTaskAction` / `NotifyWorkflowAction`, mirroring the legacy parsers. Test `TestActionFromGen_WorkflowActions`; repro `mdl-examples/bug-tests/54-describe-set-task-outcome.mdl`. FINDINGS #54 |
| `create association X …` errors "association already exists" on re-run and aborts the script | Correct SQL-shaped semantics (like `CREATE TABLE`) — `create` is not idempotent. The idempotent form is `create or modify association`, but it was undiscoverable from the bare error | `mdl/executor/cmd_associations.go` (the `NewAlreadyExists("association", …)` sites) | Not a code bug in the write path — improve the error to name `create or modify association …` and `drop association …`. Repro `mdl-examples/bug-tests/51-create-or-modify-association.mdl`. FINDINGS #51 |
| CE0066 "Entity access is out of date" | MemberAccess added to wrong entity | `sdk/mpr/writer_domainmodel.go` | MemberAccess must only be on the FROM entity (`ParentPointer`), not the TO entity — see CLAUDE.md association semantics |
| `grant view on page` / `grant execute on microflow\|nanoflow` / `grant access on odata\|published rest service` to a role from **another module** passes `mxcli check`/`exec` but fails the Mendix build with **CE0148 "reselect roles"** — the own-module role works | Document access (page/microflow/nanoflow/service `AllowedModuleRoles`) may only reference the document's **own** module roles; Studio Pro's picker only offers those. The grant path wrote `role.Module + "." + role.Name` verbatim with no same-module check (only `validateModuleRole` = role-exists-in-its-module), so a cross-module reference reached the model. The MOVE path already guarded this (`remapDocumentAccessRoles`) — GRANT didn't | `mdl/executor/cmd_security_defaults.go` (`checkDocumentAccessRolesSameModule`) + the 5 grant handlers in `mdl/executor/cmd_security_write.go` | Pre-check each grant: reject when any `role.Module != docModule` with an actionable message (name the doc's module + suggest the own-module role). Reject (don't silently remap) — a GRANT is explicit, so a wrong role/doc shouldn't be substituted. Wired into page/microflow/nanoflow/OData/published-REST grants. Repro `mdl-examples/bug-tests/ce0148-cross-module-grant.mdl` |
| CE0463 "widget definition changed" | Object property structure doesn't match Type PropertyTypes | `sdk/widgets/templates/` | Re-extract template from Studio Pro; see `sdk/widgets/templates/README.md` |
Expand Down
11 changes: 11 additions & 0 deletions .claude/skills/mendix/generate-domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,17 @@ delete_behavior DELETE_BUT_KEEP_REFERENCES
comment 'Additional documentation';
```

**Idempotency**: plain `create association` is **not** idempotent — re-running it
errors with `association already exists`, which aborts the rest of the script (and
any associations defined *after* it are never created). Write **`create or modify
association`** from the first draft — same clauses, but re-running is a no-op:

```sql
create or modify association Module.Child_Parent
from Module.Child to Module.Parent
type reference;
```

**Association Types**:
- `reference` - One-to-one or many-to-one (foreign key on FROM entity)
- `ReferenceSet` - One-to-many or many-to-many (collection)
Expand Down
7 changes: 7 additions & 0 deletions .claude/skills/mendix/project-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ alter settings configuration 'Default'
DatabaseUrl = 'newhost:5432';
```

`HttpPortNumber`, `ServerPortNumber`, `BcryptCost`, `DefaultTaskParallelism` and
`WorkflowEngineParallelism` are Integer-typed, and `AllowUserMultipleSessions` is
Boolean. An unparseable value is rejected by `mxcli check` (MDL-SET01 / MDL-SET02)
and by the write itself — it is no longer silently ignored. Quoted numbers are
fine: `HttpPortNumber = '8080'` and `HttpPortNumber = 8080` are equivalent.

### Constant Overrides

```sql
Expand Down Expand Up @@ -129,3 +135,4 @@ alter settings configuration 'Default'
- [ ] There is always exactly one ProjectSettings document; it cannot be created or deleted
- [ ] Model setting key names are case-sensitive (e.g., `JavaVersion`, not `javaversion`)
- [ ] Configuration names are case-insensitive (e.g., `'default'` matches `'default'`)
- [ ] Integer / Boolean settings must parse — `mxcli check` reports MDL-SET01 / MDL-SET02 before the write
Loading
Loading