Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased

- fix(test-scratch, app-restart): **both refuse to start on a model whose after-startup is still an mxcli test injection (`MxTest.RegisterEndpoint` / `MxTest.TestRunner`), and print the repair** ([#244](https://github.com/MendixMau/mxcli-project-toolkit/issues/244), BUG-157). A `mxcli test` or `run --test-endpoint` stopped by a signal skips its restore, and the next injection chains `RegisterEndpoint` to itself: StackOverflowError, app down. It happened twice in one day on one machine, and again on a second project. The setting is read from the settings unit's bytes, so no mxcli is needed. Every unit holding the BSON element is checked, not just the first one that mentions the name. A unit without the bytes `MxTest.` is skipped, so an 8 MB v1 model costs one grep. The reader runs with pipefail off, so a large v1 `.mpr` cannot switch the guard off. `MXTK_SKIP_MXTEST_GUARD=1` skips the check and says so. The fixture uses a settings unit captured from mxcli's own test model. `test-scratch.sh` exits 3 before copying. `app-restart.sh` exits 3 before stopping, except for a live `--test-endpoint` app, which holds the injection legitimately; if stopping that app leaves the setting behind, it exits 5 (new): stopped, not started. Projects pick this up on sync; a clean model sees no change. — MendixMau, second case AlvinnYeo
- fix(app-restart): **the recorded pid is verified to still be an `mxcli run` before anything is stopped** (review of #304). With `--cmd` / `MXTK_APP_START_CMD`, the identity check read the override, not the process. A record left by a SIGKILL whose pid the OS had reused sent TERM/KILL to that unrelated process and its children. The pid's own command line (`/proc`, `ps`; on Windows the image name `mxcli.exe`) must now name mxcli and `run`. Otherwise the app counts as not running and the process is not touched. — MendixMau
- fix(test-scratch): **taking over a stale lock is serialised with exactly exec.sh's protocol, so test-scratch and exec.sh never both hold the shared `.exec.lock`** (review of #304). The old takeover moved the lock aside with `mv -f`. It now uses the same O_EXCL `<lock>.takeover` file and rules as exec.sh in #324: stale when its pid is dead or after 10s (an empty one after over a minute, `find -mmin +1`: `+0` made a fresh one stale at once), re-read under it, remove only the exact stale line, put back a wrongly moved takeover file with `ln`. It removes only its own takeover file. The port lock uses the same protocol. A new cross-script fixture, `test-shared-lock-takeover.sh`, runs 5 exec.sh + 5 test-scratch contenders on a dead holder's lock: two holders at once on the old code, never on the new. — MendixMau
- learn(lint-that-actually-runs): **lint a copy of the model only after copying `.claude/lint-rules` (and `lint-config.yaml`) beside it, and compare rule counts before diffing** ([#240](https://github.com/MendixMau/mxcli-project-toolkit/issues/240), BUG-158). A snapshot in `/tmp` gave 51 findings from 8 rules, against 721 from 29 for the project: no notice, exit 0. mxcli's source also shows `lint-config.yaml` is read only from beside the `.mpr`, so an in-repo snapshot loses it as well. — MendixMau
- learn(testing-shape, test-result-audit): **three test-strength rules, each with a denominator: refusals probed on the server, every undeclared 4xx a failure, one clean-database final run** ([#172](https://github.com/MendixMau/mxcli-project-toolkit/issues/172)). A field comparison of two builds against one frozen spec found the toolkit build checked permission refusals only in the client (a hidden menu item), logged unexpected 400/422 responses as information, and reached 20/20 by stitching three runs. `testing-shape.md` §7 gains three report lines (`N of N restricted pairs probed server-side`, `undeclared 4xx/5xx: 0 of M requests — each fails its step`, `one clean-DB run <id>: N of N scenarios incl. timers, not stitched`); `test-result-audit.md` gains check 2e, which carries the reasons and the field evidence and asks for those numbers. Both baseline skills stay inside the 80,000-word baseline budget (79,997): the reasons live in the on-demand audit skill, and two redundant sentences in `testing-shape.md` §4's verdict paragraphs were folded (the empty-set corollary is stated in the Trace row, `harness-architecture.md` and `report-schema.md`). The backing inbox note is trimmed to its unpromoted "Keep" items. — MendixMau
- learn(learned-detection-gaps, module-review): **two UI defects every model gate passes: a lazy-loaded combo box filter hides its preset, and a theme that pins row heights by position lets the next row cover an inserted widget** ([#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299)). Both passed check, mxbuild, lint and the scripted checks on a 127-module production app and were found only in the running app. Two register rows with the first oracle and the pre-build check (pluggable properties that change *when* data loads; grep the theme for fixed `height` on positional row classes before an insert), and two LOOK lines in `module-review.md` §4c with denominators (`N of N filters show their preset on first load`, `N of N widgets inserted into an existing container fully visible`). — jopterhorst
- fix(app-restart, test-stack-up): **a port held by another process before `run --local` is named and reported as "instrument blocked" (exit 4), never killed** ([#262](https://github.com/MendixMau/mxcli-project-toolkit/issues/262)). Studio Pro had started its own runtime on the app/admin ports a restart needed; `mxcli run --local` then failed with "Authentication failed" (the admin API answering was Studio Pro's), the next journey ran against Studio Pro's runtime, and four more runs were NOT RUN. `app-restart.sh` now checks the app port and the admin port (record's `adminPort`, else app + 10) before stopping anything — a holder outside the recorded process tree exits 4 with its name, pid and parent — and again after the stop, where anything still holding a port is by definition not ours. `test-stack-up.sh` runs the same check wherever it would tell you to start `run --local` (`--no-docker`, no Docker daemon, `--check` with a leftover record), on the record's ports, `RUN_LOCAL_APP_PORT`/`RUN_LOCAL_ADMIN_PORT`, or mxcli's defaults 8080/8090. Holder lookup: lsof, then ss, then netstat + tasklist on Windows; unnamed holders still block when nothing of ours is running. Fixtures: `test-app-restart.sh` +2 cases (14 assertions, all fail on the old script), `test-stack-up-local.sh` T7 (4 of 6 fail on the old script). — AlvinnYeo
Expand Down
72 changes: 72 additions & 0 deletions bug-logs/mxcli-bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6851,3 +6851,75 @@ passes `mxcli check`, and `mx check` then fails with CE1613.

**Workaround:** never treat `describe` output as a source to replay without an `mx check`; qualify
the caption parameter (as BUG-23 / BUG-142 describe) before re-executing.

## BUG-157: `mxcli test` / `run --test-endpoint` stopped by a signal leave AfterStartupMicroflow on `MxTest.RegisterEndpoint`; the next injection chains it to itself (StackOverflowError, the app never starts)

**Severity:** High. An app goes down with no model change by the user, and the cause (a settings unit) is not where anyone looks.
**mxcli version:** v0.25.0 (field). Same code in v0.26.0 source (`cmd/mxcli/testrunner`, `12ce281`). **Mendix version:** 11.15.0 and 11.14.0 Beta
**Discovered:** 2026-10, three apps built side by side (#244): two hits in one day, one app down. A second field case on another project, 2026-10-13, reported by AlvinnYeo (open PR #309, inbox note)
**Reproducible:** yes. Probe from the second report: start `mxcli run --local --test-endpoint` on a scratch copy, `kill -TERM` it, then `describe settings`. After-startup is still `MxTest.RegisterEndpoint` on v0.25.0.
**Not filed upstream** (draft prepared, issue #244)

**How it happens.** `mxcli test` and `run --test-endpoint` read the current after-startup
microflow, generate `MxTest.RegisterEndpoint` (plain `mxcli test`: `MxTest.TestRunner`) that calls
it after registering the endpoint, and point after-startup at the generated flow. Only a clean exit
runs the restore (`ALTER SETTINGS RUNTIME (AfterStartupMicroflow: '<original>')`). A run stopped by
a signal (an agent's background-task stop, a kill) skips it. The setting stays on the injected flow
in the on-disk model, where every other mxcli process sees it. The next injection then reads
`MxTest.RegisterEndpoint` as "the original" and generates a flow that calls itself:

```
ERROR - Core: An exception occurred while running the after-startup-action.
com.mendix.modules.microflowengine.MicroflowException: java.lang.StackOverflowError
at MxTest.RegisterEndpoint (SubMicroflow : 'RegisterEndpoint')
at MxTest.RegisterEndpoint (SubMicroflow : 'RegisterEndpoint')
ERROR - M2EE: Starting Mendix Runtime failed.
```

A partial clean-up makes it worse. If the `MxTest` module is removed but the setting is not, the
after-startup names a flow that does not exist: CE1613 at build, which `exec.sh`'s delta gate then
carries as a "pre-existing" error on every later script.

**Toolkit guard (#244).** `project-bin/test-scratch.sh` and `project-bin/app-restart.sh` read the
setting from the model bytes before starting. The setting is the BSON string `AfterStartupMicroflow`
in the project-settings unit (`mprcontents/4e/70/4e70063d-….mxunit`, or inside a v1 `.mpr`). This
needs no mxcli. When the value is `MxTest.RegisterEndpoint` or `MxTest.TestRunner`, both scripts
refuse and print the repair. `app-restart.sh` lets a live `--test-endpoint` app through, because it
holds the injection legitimately, and checks again after the stop. `MXTK_SKIP_MXTEST_GUARD=1` skips the check out loud, so a false refusal never blocks.

**Repair** (any one of):
- `git checkout -- mprcontents/4e/70/<settings unit>`, then `git status`: the run may also have left an `MxTest` module.
- Through `./bin/exec.sh` (an `ALTER SETTINGS`, so say so in chat; it is the statement mxcli's own cleanup runs, and it was field-proven on v0.25.0):
`mdl 1;` `alter settings runtime (AfterStartupMicroflow: 'MyModule.ASU_Startup');` (`''` for none).
- Studio Pro: App Settings → Runtime → After startup.

**Avoid it:** stop `run --test-endpoint` with Ctrl+C in its own terminal. Never kill it or stop it
as a background task. Afterwards `git status` on the model must show the Settings unit unchanged.

## BUG-158: `mxcli lint` on a copy of the model outside the repository silently drops the project's Starlark rules (8 of 29 rules ran, no notice, exit 0)

**Severity:** Medium. The result is wrong, not refused: a before/after diff over it is off by hundreds.
**mxcli version:** v0.25.0 (field). Discovery code unchanged in v0.26.0 source (`mdl/linter/lintrules_discovery.go`, `12ce281`)
**Discovered:** 2026-10, attributing a lint ratchet rise to one script by linting the pre-exec snapshot copied to `/tmp` (#240)
**Reproducible:** yes. Copy the `.mpr` + `mprcontents/` to a folder outside the repository and lint it.
**Not filed upstream** (draft prepared, issue #240)

`mxcli lint -p /tmp/pre/App.mpr --format json` gave **51 findings across 8 rules**. The same model
in the project folder gave **721 across 29**. The 21 missing rules are the Starlark rules in
`.claude/lint-rules/`. Discovery walks up from the `.mpr`'s folder and stops at a `.git`, so a
copy outside the repository finds none. Only the built-in rules ran (MPR\*, CONV013, SEC003,
MDL-FLOW01), with no notice and exit 0. The "no `.claude/lint-rules/` directory was found" line
exists in the source, but it prints only when `--rules` names an unknown id.

Two related findings from reading the v0.26.0 source (not field-run):
- `lint-config.yaml` is read only from beside the `.mpr` (`FindConfigFile`), never from above it.
A snapshot inside the repository finds the rules by walking up but loses the config's module
excludes and rule overrides.
- With no `.git` above it, the walk continues to `/`. A stray `~/.claude/lint-rules` would then
supply rules to the copy.

**Workaround (worked in the field):** `cp -R .claude/lint-rules <copy>/.claude/` (plus
`.claude/lint-config.yaml`) before linting. The copy then gave 721 findings across 29 rules,
matching the project. Recipe and the rule-set comparison to run before any diff:
`skills/lint-that-actually-runs.md` → "Linting a copy of the model". `lint-gate.sh`'s collapse
guard catches this inside the gate. Nothing catches it when lint is run directly.
Loading
Loading