Skip to content

new(lint): ERR001 ErrorSwallowed + LOOP001 ExpensiveActionInLoop (lint backlog Batch 1) - #248

Merged
MendixMau merged 14 commits into
masterfrom
feat/lint-batch1
Oct 12, 2026
Merged

MendixMau merged 14 commits into
masterfrom
feat/lint-batch1

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #238 — #238 is merged (f98eff0); base retargeted to master and master merged in (97badd6). The diff against master is now Batch 1 only.

What changed and why

Batch 1 of the lint backlog: the two activity-based candidates whose fields are confirmed in the mxcli 0.25.0 source.

Rule Catches Not flagged
ERR001 ErrorSwallowed (quality, warning) An activity set to Continue / Custom / CustomWithoutRollBack in a flow where nothing logs (LogMessageAction), raises (ErrorEvent) or calls a handler-named flow (SUB_LogError, ERR_*, *_HandleError) — the "Continue and pray" pattern Rollback, Abort, unset; System/Administration; RULE flows
LOOP001 ExpensiveActionInLoop (performance, warning) Database retrieve, delete, REST, external (OData/web service) or Java call inside a loop body, any depth association retrieve, create/change/commit, sub-flow calls, unknown retrieve source

ERR001 is the complement of mxcli's CONV013/CONV014 (which check that handling is set); LOOP001 is the rest of CONV011's list (commit-in-loop).

Both read error_handling_type / loop_depth / retrieve_source through getattr and emit a _rule blindness finding on an older binary, an empty column, or loops with no indexed bodies. Vocabulary is from the mxcli source (sdk/microflows/error_handling.go: CustomWithoutRollBack with a capital B; catalog builder: retrieve_source ∈ database/association).

Delivery: added to MXTK_LINT_RULES in the install manifest, README rows, CHANGELOG line. The starrun harness gains activities_for(nested=) mirroring the 0.25.0 builtin (default call hides loop bodies, as mxcli does).

Field evidence (2026-10-12, mxcli 0.26.0, scratch copy of a 21-module Mendix 11.12 app — 876 microflows, 55 rules loaded, no load errors, 1.7 s)

Rule Findings Scored by reading each flagged flow
ERR001 8, all on the app's own modules 3 true (on error continue, nothing logged, raised or handled; one silently drops a callee's gate failure). 5 debatable: 4 REST wrappers and 1 button flow return an error payload or empty result as their signal, log nothing, and carry an annotation saying the handling is deliberate, which the rule does not read. 0 false.
LOOP001 4, all on the app's own modules 4 true: every retrieve sits in the loop body and filters on the iterator (one flow does 3 retrieves per row). 0 false.

Neither rule emitted a _rule finding; no findings on marketplace or System modules. Vocabulary (RetrieveAction, RestCallAction, MicroflowCallAction, Continue) confirmed by the messages themselves. Open question for after the second project: should an annotation, or an explicit error-payload return from the failure branch, count as handling? Until then the rule text stands at warning severity (rollout rule in process/lint-backlog.md).

Checklist

  • Size: 9 non-fixture files (2 rules, test script, harness, manifest, README, CI, CHANGELOG) + fixtures
  • Test tier T1: tests/lint-rules/test-batch1-rules.sh — 29 cases under the real Starlark interpreter incl. the 3000-flow perf run; wired into CI
  • Fixtures are hand-written projections of the mxcli source, declared as such in the script header; vocabulary confirmed by the field run above
  • Blind rules say so (_rule findings for missing field, empty column, no loop bodies, no activities)
  • check-portability, check-scripts, leak guard, check-pr-discipline clean on the merged head
  • CHANGELOG lines under Unreleased
  • One field run cited

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw

claude added 9 commits October 8, 2026 07:04
Two Starlark rules delivered through the existing lint-rules install/refresh
path (install-manifest MXTK_LINT_RULES, README row each, delivery fixture
count 4 -> 6).

UX001 flags a button that deletes directly (Forms$DeleteClientAction has no
confirmation setting) or calls a microflow/nanoflow without "Ask confirmation"
when that flow deletes objects (bounded call-graph walk) or is named as an
irreversible step (Approve/Reject/Submit/Archive/...). UX002 flags decisions
left on their auto-generated caption and flows of 8+ top-level activities
with neither an annotation nor a documentation text.

Both need mxcli >= 0.25.0 (widget action_type/has_confirmation, activity
caption/auto_generate_caption) and emit a `_rule` blindness finding on
anything older, so lint-gate.sh never reads them as a clean pass. Vocabulary
was read from the mxcli catalog builder source, not from the bundled API
guide. No mxcli binary was reachable in the authoring session, so the first
field calibration on a scratch copy of a real model is still owed; the README
note says so and names the probe queries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…reter

tests/lint-rules/ runs both rules through go.starlark.net at mxcli's pinned
version via a small Go harness (starrun) that rebuilds the mxcli projection
structs from JSON fixtures and rejects any field the projection does not have.
33 hand-written fixtures cover self-checks, call depth on both sides of the
bound, cycles, the destructive-name pattern, module skips, snippet locations
and the version probe; a generated 3000-flow / 20000-widget model times each
rule (~0.7 s). 35 cases, wired into CI as its own step.

The suite caught two defects in UX002 before they shipped: StartEvent/EndEvent
objects returned by activities_for() inflated the activity count, and the
"needs mxcli >= 0.25.0" probe only fired on flows that had a decision. Both
fixed here; UX001's depth comment now states what the bound means.

Fixtures encode the projection as read from the mxcli source, not captured
output: they prove the rule logic, not the catalog vocabulary. Field
calibration on a real model is still owed and the README still says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
check-portability.sh (in CI) rejects a hard-coded `python3`: on Windows a
name on PATH may be the Store alias stub, not an interpreter. The suite now
sources bin/lib/portable.sh, calls require_py and runs "$PY", like every
other script here. 35/35 cases still pass; check-portability clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…atch 1)

First batch from process/lint-backlog.md, as the rollout there prescribes:
two rules, warning severity, Starlark unit fixtures, draft until a real
project reports a count.

ERR001: an activity set to Continue / Custom / CustomWithoutRollBack in a
flow where nothing logs (LogMessageAction), raises (ErrorEvent) or calls a
handler-named flow (SUB_LogError, ERR_*). The complement of mxcli's
CONV013/CONV014, which only check that handling is set.

LOOP001: a database retrieve, delete, REST, external or Java call inside a
loop body at any depth. CONV011 covers commit-in-loop; this is the rest.
Association retrieves, sub-flow calls and unknown retrieve sources are
left alone.

Vocabulary from the mxcli source, not guessed: error_handling_type values
per sdk/microflows/error_handling.go (CustomWithoutRollBack, capital B),
loop_depth / retrieve_source per the 0.25.0 catalog builder. Both rules
read the fields through getattr and emit a `_rule` blindness finding on an
older binary, an empty column, or loops with no indexed bodies.

tests/lint-rules/test-batch1-rules.sh: 29 cases under the real Starlark
interpreter (incl. the 3000-flow perf fixture), wired into CI. The starrun
harness gains activities_for(nested=) mirroring the 0.25.0 builtin, so the
default call hides loop bodies exactly as mxcli does.

Field evidence: none yet. No mxcli binary is reachable from the authoring
session; the first count on a real project is owed before this leaves
draft, per the backlog's own rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
test-stock-hashes.sh T2 demanded a STOCK-HASHES.txt entry per version for
every file in MXTK_LINT_RULES. mxcli init seeds only three of those; the
toolkit-authored ux001/ux002 rules can never have a stock hash, so the
fixture went red on every version (3 FAIL) the moment they joined the list.

install-manifest.sh gains MXTK_LINT_RULES_STOCK (the three init-seeded
rules). The fixture's coverage, listed-name and live-capture checks walk
that set, and a new check asserts it is a subset of MXTK_LINT_RULES.
Replayed T2's awk against the real list: all three versions cover all
three stock rules; a listed non-stock name still fails.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
# Conflicts:
#	CHANGELOG.md
#	bin/lib/install-manifest.sh
…e manifest

Case 1 asserted a literal 4 files in .claude/lint-rules/ after a fresh
install. Two toolkit-authored rules joined MXTK_LINT_RULES, six landed,
and the fixture reported them as a NOINSTALL leak. The expected count is
now the size of MXTK_LINT_RULES + MXTK_LINT_RULES_CONFIGURABLE, with an
explicit check that no MXTK_LINT_RULES_NOINSTALL file was installed.
CI's own output on 40f2c00 listed exactly the six manifest rules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Case 10 ("caller forgot the manifest") still compared against a literal 6
after case 1 was switched to the manifest-derived count in 53c4a36. On this
branch two more managed rules land, so the literal went red in CI while the
install itself was correct.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…ulary defects

First run of the pair with mxcli 0.26.0 against a scratch copy of a 21-module
Mendix 11.12 app (876 microflows, 124 pages): UX001 fired 0 times and UX002's
decision leg 0 times across 728 decisions. A catalog probe explained both:

- the stored microflow-button action type is Forms$MicroflowAction, not the
  Forms$MicroflowClientAction read from the source, so UX001 legs 2-3 could
  never fire for a microflow button. FLOW_CALLS now lists both spellings.
- auto_generate_caption is 0 on every ExclusiveSplit; a bare decision carries
  its condition expression as its caption. UX002 now flags a caption that is
  empty or equals condition_expression (39 of 39 custom decisions on that
  model; marketplace decisions carry real questions).

Fixtures carry the probed action-type name; new case q pins the
caption==condition shape. tests/lint-rules/test-ux-rules.sh: 36/36.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
mxcli 0.26.0 against a scratch copy of a 21-module Mendix 11.12 app
(876 microflows): ERR001 8 findings (3 true, 5 debatable), LOOP001 4
(4 true), all on the app's own modules, no load errors. No false
positives; rule text unchanged, warning severity kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau
MendixMau marked this pull request as ready for review October 12, 2026 03:13
@MendixMau
MendixMau changed the base branch from feat/ux-lint-rules to master October 12, 2026 03:37
# Conflicts:
#	.github/workflows/checks.yml
#	CHANGELOG.md
#	bin/lib/install-manifest.sh
#	lint-rules/README.md
#	tests/lint-rules/starrun/main.go
#	tests/test-lint-delivery.sh
@MendixMau
MendixMau merged commit a2f849e into master Oct 12, 2026
1 check passed
@MendixMau
MendixMau deleted the feat/lint-batch1 branch October 15, 2026 10:45
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