Skip to content

Bug sweep 2026-10-26: 29 fixes across gates, runners, doctor and skill text - #578

Merged
MendixMau merged 71 commits into
masterfrom
claude/loving-gates-hauc1n
Oct 26, 2026
Merged

MendixMau merged 71 commits into
masterfrom
claude/loving-gates-hauc1n

Conversation

@MendixMau

Copy link
Copy Markdown
Owner

One PR for the nightly bug sweep. There is one commit per issue so it can be reviewed by area. It is batched because a separate PR per fix would serialize ~25 CHANGELOG conflicts at ~30 min CI each.

Every code fix adds a fixture case that fails on master and passes here. Only scoped fixtures were run, per CLAUDE.md. Each commit carries its own CHANGELOG line.

Gates / verify

Runners / monkey / design-audit

Doctor / lint / status

Skill text (on-demand only)

Not in this PR

These are DISCUSS or roadmap items: #518 (can flip PASS→FAIL on 26/55 wireframes), #524 rest, the #564 default-on, #148, #521 (separate PR), and the remaining items of #560 and #502.

Field proof gap

None of the runner changes have been run live against a Mendix app. The #564 patch has the reporter's own field run (60/2 → 62/62).


Generated by Claude Code

claude added 30 commits October 26, 2026 20:35
…t unparsed ones

The page-name sed printed the whole declaration line when it did not
match, so `create page if not exists "M"."P"` went on as the page name
into a false "has no wireframe" STOP. Accept the optional IF NOT EXISTS,
print only on a match, report an unparsed declaration as such, and skip
a placeholder that the same file declares again for real.

Fixes #517

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…scratch copy

gate_selftest copied every sibling of the .mpr, including <App>.mpr.lock,
so mxcli refused the known-bad injection into the throwaway copy whenever
Studio Pro had the project open, and the self-test proved only its
baseline half. Case 11 added to test-doctor-gate-selftest.sh (fails on
master, 32/32 with the fix).

Fixes #577

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…ng it "not transactional"

STOP #22, the detection-gaps register row, iterative-build-loop's gate
invariants and exec.sh's failed-exec message taught the pre-v0.27 behaviour
(earlier statements stay written). On v0.27+ any write-time failure rolls the
whole run back, so a non-idempotent statement placed last now fails every
re-run and drops the edits above it. STOP #22's rule is unchanged; only the
reasoning and the consequence move. Baseline 63,877 -> 63,872 words.

Fixes #539

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Trap 3 said no API creates a first cloud environment, contradicting the
same file's Free App section. POST /api/1/apps {ProjectId} created the Free
App sandbox in a field run; deploys to it stay manual (packages -> 500).

Fixes #516

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
typeSafely() wrote through the native value setter, which skips the
maxlength attribute, so a 5000-char payload reached String(200)
attributes and every capped box gave a crash-class HTTP 560 that no
user can reach. It now cuts to el.maxLength (UTF-16 units, like the
server) and returns the cap, which the round notes in the finding.

Fixture: tests/wave2/test-monkey-oracle.sh (5/5; master: 0/1).

Fixes #544

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…ODULES

Step 0 now says how to verify each marketplace install: module packages via
SHOW MODULES, widget-only packages (Charts) via widgets/<Name>.mpk.

Fixes #510

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
monkey-test.md defines a crash as a page exception, an error page or
dialog, a 5xx, or an ERROR span. oracle() also counted "spinner still
up after 8s", so a slow but successful batch job failed the run. It now
returns those hits as `slow`; round() files them as their own severity
when no crash signal fired, and the exit code still counts crash-class
only. Verdict table and summary line in monkey-test.md updated.

Fixture: tests/wave2/test-monkey-oracle.sh (10/10; without the fix 5/7).

Fixes #562

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…563)

land() swallowed navTo's "not found" throw and then only asked whether
any .mx-page was visible, which the previous target's page always is.
A missing target was fuzzed on another page and its findings filed
under the wrong name. land() now returns the reason ('' = landed);
main and the re-land read it, and the summary prints reached r of t
with the unreached targets listed.

Fixture: tests/wave2/test-monkey-oracle.sh (14/14; without the fix 10/12).

Fixes #563

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Header text comes from the popup page Title; the h4 is invisible by default
(fix with a token-scoped rule in main.scss); width is a shared !default
variable set in custom-variables.scss that also widens the sidebar chat.

Fixes #574

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…uest copy new

A page-triggered microflow that saves an object only via
commitInSeparateDatabaseTransaction leaves the request's copy uncommitted;
after browser Back a role without Create gets 'Attempt to illegally add an
object to the state'. Fix: rollback $X refresh. On-demand text only; the
detection-gaps register row is baseline and waits for an A/B.

Part of #565

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…ch the model

The gate self-test counted errors in a $(...) subshell, so MXTK_MXBUILD_WHY
never reached it and an mxbuild that exited 3 with "release version 25 not
supported" was reported as "gate cannot read mxbuild's error file". The
self-test now keeps the errors file and quotes mxbuild's reason, and
mxtk_mxbuild_why appends the compiler's "error:" line from details.

The Build toolchain section now WARNs when the Java major does not match a
Mendix 11 model (11.14+ -> 25, <=11.13 -> 21; Studio Pro's bundled JRE is
never flagged) and names the fix when JAVA_HOME is exported in the shell.
WARN only: no verdict flips. A MXTK_JAVA_HOME override (ask 3) is left out.

Cases 12-13 in test-doctor-gate-selftest.sh (fail on master, 39/39 with the
fix); test-mxbuild-version-match 18/18, test-find-java-version 17/17.

Fixes #576

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…retro traps

Snippet top-level text has no context object (wrap it in a DataView on the
parameter); Nova theme points the File Uploader icons at images nothing
ships. The rest of #282 was already in ui-loop.md, lint UI001 and the
licence-cap skills.

Fixes #282

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
The toolkit's conv010 rule allowlisted MicroflowCallAction but not
NanoflowCallAction, so an ACT_ nanoflow delegating to a SUB_ nanoflow was
flagged as business logic. mxcli's stock copy allows it.

Field proof: mxcli v0.28.0, scratch copy of mendixlabs/mxcli
testdata/pedapp with an ACT_ nanoflow calling a SUB_ nanoflow: 1 CONV010
finding before, 0 after. test-stock-hashes 35/35, test-lint-receipt-revert
55/55, test-exec-lint-autorestore 17/17.

Fixes #512

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
checkA11y() kept only rule, impact, node count and help, so the rung 7
a11y row read `button-name(critical,x1)` with no evidence. It now keeps
the first 5 nodes per rule (axe selector + opening tag), and the new
a11yEvidence() turns them into `selector` evidence entries, lifting the
Mendix mx-name-<widget> class when the element carries one, with the
help text/URL; the page screenshot is attached too. report-render.js
renders a selector entry instead of "NO POINTER". Verdicts unchanged.

Fixture: tests/wave2/test-design-audit-a11y-evidence.sh over a captured
axe-core 4.10.2 run (8/8; master 0/1). nav-map 26/26, css-corpus 7/7.

Fixes #561

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
The build-ready placeholder check greps {{...}} per line, so a
placeholder whose description spans lines (mdl-agent's
PROJECT_LOCAL_SOURCES_OF_TRUTH) was never seen and an untouched stub
read "no unfilled placeholders". Detect it with a multi-line match and
print a named WARN; the verdict is unchanged so no project flips.

Fixes #509

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
named-controls.js proves a control by finding mustFail in a check
name; the outcome check was always "<id> end state", so a control
tagged in outcome.label read NOT PROVEN even when the outcome FAILed.
outcomeCheckName() appends ": <label>" when a label is set (the prefix
is kept, so mustFail "end state" still matches; unlabelled outcomes keep
their name and id). report-normalize.js builds the same name. A no-match
NOT PROVEN now lists the FAIL checks; journey-proof.md says which
fields reach a check name.

Fixtures: test-named-controls.sh 28/28 (original: 22/26);
test-journey-{control-exit,id,liveness,persona,preflight},
test-report-normalize-two-tree, journey-rung4-scope.test.js,
report-normalize --selftest all pass.

Fixes #538

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
The page-scope rung had a fixed 300 s budget and the watchdog killed
only the bash PID it started, so page-scope.sh's Python child survived,
and wrote page-scope.json after the design audit had read the previous
one. Add VM_TMO_PAGESCOPE (default 900), kill the instrument's
descendants on timeout, and write page-scope.json via rename.

Fixes #541

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
#560)

--positive-control walks its mutants on one data set. For a journey
whose action consumes its seed state, later mutants stopped at "seeds
resolved = INVALID" and their control rows said FAIL "BROKEN
PRECONDITION WENT GREEN". judgeMutant() now returns INVALID "mutant not
run: <row>" when nothing FAILed but a row was INVALID; "went green" is
kept for an all-green walk. A journey that declares `consumes` gets a
WARN before its mutants. Exit code and proven count unchanged.

Not done (proposed): the issue's per-mutant reset hook and --only-rung.

Fixture: test-journey-control-exit.sh 12/12 (original 7/9); journey
id/liveness/persona/preflight and named-controls fixtures pass.

Refs #560

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…gle file

A module keeping one journey per file under journeys/<Module>/ got
FAULT "no journeys/<Module>.journey.json" while its journeys sat one
level down. When the single file is absent, pass every
journeys/<Module>/*.journey.json to one runner call (and to the positive
control). The single file keeps precedence when both exist.

Part of #542 (per-persona runs not included)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…#536)

Playwright's locator.isVisible() ignores `timeout` and returns at once.
#547 fixed the landing guard; the click / fill / combobox actions and
helpers.gotoTarget()'s ready wait still used it, so an element that was
attached but still appearing threw "not visible" in 0 ms. New
helpers.visibleWithin(loc, ms) waits via waitFor({state:'visible'});
those four sites use it. Instant probes elsewhere are unchanged.

Fixture: tests/wave2/test-e2e-visible-wait.sh 4/4 (stand-in locator +
the issue's Chromium reproduction; original 0/3). Journey, monkey,
named-controls, navto-missing-item and oql-rows fixtures pass.

Refs #536

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
… are git-ignored

A project .gitignore with /.claude/ kept lint-baseline.json,
lint-acceptances.tsv and project-local lint rules out of git; a fresh clone
then took a new baseline and lost the accepted-rise record, and no check
said so. status.sh (brief and full screen) now adds a WATCH entry naming
each ignored path and the .gitignore line, decided by check-ignore -q so a
'!' negation counts as tracked. No gate verdict changes.

New fixture test-status-lint-unversioned.sh: 4/4 (T1 and T4 fail on
master). test-status-bare-var 3/3, test-status-existing-app 27/27,
test-status-session-cwd 10/10.

Fixes #523

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…t names

A per-module coverage-ledger.md was measured against every BRD in the
project, so BRDs only other modules claim read fully UNCLAIMED. When
every pointer in it carries a BRD prefix, measure only the BRDs the
prefixes name and list the rest as another module's, as the directory
form already does. Slash-pointer and project-wide ledgers are unchanged.

Part of #543 (option 1; cross-ledger UNCLAIMED is option 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Ports the reporter's ~36-line patch. `{ "do": "back" }` calls goBack
(networkidle timeout is a WARN, not a failure) and throws when Back
left the app (backLeftApp: login page, about:blank, another origin).
`"ui": { "no5xx": true }` collects every response >= 500 from the
step's first action to its landing guard and records one row before
any bail-out (action failed / not reached); seal() removes the
listener. Opt-in, so existing journeys are unchanged.
validate-journeys.js accepts `back` and checks no5xx is a boolean;
journey-examples.md documents both.

Field evidence (reporter's project, Mendix 11.14): Back journey
60 PASS / 2 FAIL on the defective build, 62/62 after the fix.

Fixture: tests/wave2/test-journey-back-no5xx.sh 10/10 (original 0/4);
validate-journeys --selftest 25/25; journey fixtures, selftest and
rung4-scope pass.

Not done: making no5xx the default and a positive-control mutant for
it (the issue's asks 2-3) — they change results for existing journeys.

Fixes #564

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…to edit by hand

The check tested settings.json per line, so a single-line file holding the
installed (CLAUDE_PROJECT_DIR) form and a relative form passed. Each
"command" string is now split onto its own line first. A hand-written
variant, which install-claude-permissions.sh --hooks-only never rewrites,
now gets an "edit it by hand" note next to the --hooks-only fix.

L8-L9 in test-look-hook-cwd.sh (fail on master, 9/9 with the fix);
test-look-ledger-hook-repair 13/13, test-doctor-gate-selftest 39/39.

Part of #502 (the doctor item; the other follow-ups stay open).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…statuses

conformance-check.sh kept only ledger rows whose pointer starts with
"/", so the single-file multi-BRD ledger brd-to-build-plan.md writes
(F002/modules/0) exited 2 as "an index, not a ledger", and its
`not built` and `blocked (..)` statuses read UNKNOWN-STATUS. Accept a
BRD prefix and map both statuses to not-built.

Part of #515 (review-module.sh's BRD-path lookup not included)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Logs written within one clock tick tie on mtime, and `ls -t` breaks the
tie by name ascending, so the prune could keep the oldest log; this made
test-exec-gate-log case C flake under load. Sort by the time-stamped
file name instead, and make case C deterministic.

Fixes #505

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
BRDs that carry identifiers as domainEntities[].name / pages[].name gave
facts-lock nothing to harvest, so a respelled entity across BRDs went
unnoticed. Read `name` inside identifier-bearing arrays into a separate
set and report disagreeing spellings as WARN lines; never frozen, exit
codes unchanged.

Fixes #519

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
From the reporter's comment on #522: `back` takes no required fields;
`timeout`, if present, is a positive number. `back` and ui.no5xx were
accepted in the #564 commit. offline/online and notMeasurableWhen are
NOT accepted: the toolkit runner does not implement them (#521), so the
"unknown action — the runner throws" message is still true here.

validate-journeys --selftest 26/26; test-validate-journeys-spans 15/15;
test-journey-back-no5xx 10/10.

Refs #522

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
The leak guard (check-no-client-data.sh) blocks CJK characters in
tracked files; test-monkey-oracle.sh (#544) carried the payload
literally. Same string, as monkey.js's own PAYLOADS writes it.

test-monkey-oracle.sh 14/14; leak guard clean (1185 files).

Refs #544

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…-changing git pull/checkout

The restatement guard's ledger only saw exec.sh writes, so a pull that
brought newer model work in left it saying "05 wrote last" and a replay
of 05 over that work stayed quiet. install-project-hooks.sh now installs
post-merge + post-checkout hooks that append a RESTORE row (as
restore-mpr.sh does) when the operation changed the .mpr or
mprcontents/. Squash merges and file checkouts give the hook no range,
so they always mark: the cost is one capped DESCRIBE round.

Fixture test-restatement-git-hooks.sh: real git, both layouts, chained
foreign hook, idempotent re-install; 26/26, and the #504 repro fails on
master. test-model-stamp 35/35, test-mx-metadata-note 40/40,
test-restatement-guard 58/58.

Not done: restore-mpr.sh taking the exec lock (optional ask) — draft
PR #379 moves the lock into _common.sh; do it on top of that.

Fixes #504

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
This was referenced Oct 26, 2026
MendixMau pushed a commit that referenced this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants