Skip to content

fix: preserve custom error handler continuations#366

Merged
ako merged 16 commits intomendixlabs:mainfrom
hjotha:submit/custom-error-handler-routing
May 2, 2026
Merged

fix: preserve custom error handler continuations#366
ako merged 16 commits intomendixlabs:mainfrom
hjotha:submit/custom-error-handler-routing

Conversation

@hjotha
Copy link
Copy Markdown
Contributor

@hjotha hjotha commented Apr 27, 2026

Closes #349.
Part of #332.

Summary

  • keeps custom error-handler paths pending until the next safe continuation is known
  • rejoins non-terminal handler bodies through a merge instead of fabricating detached EndEvents
  • preserves empty custom-handler error flows and prevents output-producing error paths from continuing into statements that read a missing output variable

Validation

  • make build
  • make lint-go
  • make test

Copy link
Copy Markdown
Collaborator

@ako ako left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #366 — fix: preserve custom error handler continuations

Reviewed against the CLAUDE.md checklist.


Blockers

1. statementVarRefs and statementsVarRefs duplicated across open PRs — compile error when merged

PR #337 adds statementVarRefs and statementsVarRefs to cmd_microflows_builder_graph.go. This PR adds the same two functions to cmd_microflows_builder_flows.go. Both files are in package executor, both PRs target main. Merging them in either order produces:

./cmd_microflows_builder_flows.go: statementVarRefs redeclared in this block
./cmd_microflows_builder_flows.go: statementsVarRefs redeclared in this block

The two implementations are almost identical but differ in two places:

  • PR #337 is missing the RestCallStmt body field refs (s.Body.Template, s.Body.TemplateParams, s.Body.SourceVariable) that PR #366 adds — so the PR #366 version is the more complete one.
  • PR #337 has an if s.Item != "" nil-guard in the AddToListStmt case; PR #366 does not.

Fix options:

  • Move the canonical (merged) implementation to a new shared file cmd_microflows_builder_varutil.go before either PR merges, and remove the definitions from both builder_graph.go and builder_flows.go.
  • Or rebase PR #366 on PR #337's branch so that builder_graph.go already has the functions and builder_flows.go doesn't need to redeclare them.

Also worth noting: PR #337 adds referencedVariableSet(stmts) map[string]bool and this PR adds statementsReferenceVar(stmts, varName) bool. Both wrap statementsVarRefs to answer "does this variable appear here?" with different return shapes. After both PRs land, the package will have two overlapping utilities. Consolidating to one is not strictly a blocker, but flagging it for cleanup.


Moderate Issues

2. Active-handler state split between fields and slice — invariant not documented

flowBuilder gains 7 new fields (emptyErrorHandlerFrom, errorHandlerTailFrom, errorHandlerSource, errorHandlerSkipVar, errorHandlerTailIsSource, errorHandlerReturnValue, pendingErrorHandlers). The design is: the active handler lives in the flat fields; completed handlers are queued in the slice via queueActivePendingErrorHandler(). activePendingErrorHandler() and setActivePendingErrorHandler() shuttle between these two representations.

This invariant is non-obvious. A future reader adding a new call site may write directly to a field instead of going through the accessor methods and silently break the queuing. A short comment on the struct fields stating the invariant would pay off.

3. findExistingRejoinMerge scans all objects

The function iterates the entire fb.objects slice looking for an ExclusiveMerge whose destination flow matches. For typical Studio Pro microflows this is fine in practice, but a note that this is an O(N) scan (and the N is bounded by normal microflow sizes) would make the intent clearer.


Minor Issues

4. AnchorLeftAnchorTop change in newErrorHandlerFlow — rationale not documented

The change from AnchorLeft to AnchorTop as the destination connection index for error handler flows is presumably because error handlers enter the destination node from above rather than from the side. This is a visual Studio Pro layout change. A one-line comment explaining why would help future readers understand whether this is load-bearing or cosmetic.

5. hasReturnValue bool is redundant

fb.returnType is already stored, and fb.hasReturnValue is always assigned as returns != nil && returns.Type.Kind != ast.TypeVoid. Callers can compute this from fb.returnType without the extra field. Not a correctness issue, but it adds a third way to answer the same question alongside fb.returnValue != "".


Positive Notes

  • The deduplication of the 10 copy-pasted error-handler blocks in builder_actions.go and builder_calls.go into finishCustomErrorHandler is a clear improvement.
  • 5 focused unit tests in cmd_microflows_builder_terminal_test.go with the flowPathExists BFS helper cover the key routing scenarios well.
  • Bug-test file 349-custom-error-handler-routing.mdl has thorough header documentation and correctly sets up the non-terminal handler scenario.
  • The pendingErrorHandlerState struct cleanly separates the data from the builder fields, making queueActivePendingErrorHandler easy to follow once the invariant is understood.

Summary: One blocker — the statementVarRefs/statementsVarRefs duplicate with PR #337 needs to be resolved before this can merge. The fix is a one-time extraction to a shared file. All other findings are moderate/minor documentation and style issues.

@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • Bug fix focus: The PR cleanly addresses issue Preserve custom error-handler routing during microflow roundtrip #349 without scope creep
  • Comprehensive testing:
    • Added mdl-examples/bug-tests/349-custom-error-handler-routing.mdl reproduces the bug and validates the fix
    • Added extensive unit tests in cmd_microflows_builder_terminal_test.go covering:
      • Non-terminal custom handler rejoining continuation
      • Consecutive custom handlers each rejoining continuation
      • Empty output handler terminating before output-dependent tail
      • Output handler terminating before declare referencing output
      • Empty no-output handler rejoining at next action
  • Logical refactor: Replaced scattered error handler handling with centralized finishCustomErrorHandler() logic
  • Proper layering: Fix resides entirely in executor layer (no grammar/AST/visitor changes needed)
  • Clear documentation: Bug test file includes symptom description, root cause, fix explanation, and validation steps
  • Deterministic testing: No time.sleep usage in tests
  • Consistent patterns: Uses existing microflow builder patterns and types

Recommendation

Approve the PR. It correctly fixes bug #349 with appropriate test coverage, follows project conventions, and introduces no regressions. The changes are focused, well-tested, and solve the specific problem described in the issue.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • Targeted fix: The PR addresses a specific bug (Preserve custom error-handler routing during microflow roundtrip #349) in custom error handler routing without unrelated changes.
  • Comprehensive testing: Adds both a doctype test (mdl-examples/bug-tests/349-custom-error-handler-routing.mdl) and extensive unit tests in cmd_microflows_builder_terminal_test.go covering non-terminal handlers, consecutive handlers, empty handlers, and output-dependent continuation prevention.
  • Centralized logic: Replaces duplicated error handler building across action types with a unified finishCustomErrorHandler method and pending state management.
  • Clear documentation: The test file includes detailed symptom descriptions, root cause analysis, and validation instructions.
  • Consistent application: Changes applied uniformly to all microflow action types (create, commit, delete, retrieve, calls, etc.).
  • No syntax changes: As a bug fix to existing behavior, no MDL grammar/AST/visitor modifications were needed, avoiding unnecessary pipeline changes.
  • Robust state management: Implements queuing of pending error handlers across branch boundaries with proper synchronization via helper methods.

Recommendation

Approve the PR. This is a well-scoped bug fix with adequate test coverage that resolves the reported issue without violating any project conventions or introducing risks. The changes follow existing patterns and maintain code quality standards.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 29, 2026
Symptom: the combined open-PR branch still produced an MPR check failure for a
custom-error-handler path even though the described MDL round-tripped exactly.
The handler rejoined before a DECLARE whose initial value referenced the failed
action output, so Studio Pro reported the output variable as out of scope.

Root cause: the combined branch was missing the already-submitted custom error
handler fix that treats DECLARE initial values as output-variable references.
That fix exists in PR mendixlabs#366 but had been lost during combined-branch conflict
resolution.

Fix: restore DECLARE initial-value reference tracking and the synthetic graph
regression from the custom-error-handler PR.

Tests: ran the targeted executor regression for output-dependent DECLARE routing.
hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 30, 2026
Moves statementVarRefs / statementsVarRefs / referencedVariableSet /
expressionStringVarRefs out of cmd_microflows_builder_graph.go into
cmd_microflows_builder_varutil.go. Sibling builder passes (PR mendixlabs#366's
pending error-handler queue, future planners) need the same walker
without redeclaring it in their own file. Pure refactor — no
behavior change. Also marks the variable-alias-collision proposal as
Implemented now that the feature is shipping.

Addresses ako's review on PR mendixlabs#337.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hjotha hjotha force-pushed the submit/custom-error-handler-routing branch from d66c0ca to 3bf45d4 Compare April 30, 2026 08:00
@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

  • The PR addresses bug Preserve custom error-handler routing during microflow roundtrip #349 with a focused fix for custom error handler routing in microflows
  • Adds a comprehensive test script (mdl-examples/bug-tests/349-custom-error-handler-routing.mdl) that reproduces the issue and validates the fix
  • Includes extensive unit tests in cmd_microflows_builder_terminal_test.go covering multiple scenarios:
    • Non-terminal handlers rejoining continuations
    • Consecutive handlers each properly rejoining
    • Empty handlers terminating before output-dependent statements
    • Handler state propagation across control flow branches
  • Refactors error handler handling consistently across all activity types (create, commit, delete, retrieve, calls, etc.) and workflows
  • Preserves empty custom handler flows and prevents invalid continuations that would read missing output variables
  • Uses proper queuing mechanism for pending error handlers across branch boundaries
  • Maintains existing behavior for terminal handlers (those with explicit RETURN/RAISE)
  • Changes are isolated to the microflow builder executor layer without violating backend abstraction
  • No changes to MDL syntax, grammar, AST, or visitor layers (appropriate for a bug fix in execution logic)
  • Follows existing code patterns and style

Recommendation

Approve the PR. The fix correctly resolves the reported issue with appropriate test coverage and maintains code quality standards. No changes are needed to the PR.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 30, 2026
Moves statementVarRefs / statementsVarRefs / referencedVariableSet /
expressionStringVarRefs out of cmd_microflows_builder_graph.go into
cmd_microflows_builder_varutil.go. Sibling builder passes (PR mendixlabs#366's
pending error-handler queue, future planners) need the same walker
without redeclaring it in their own file. Pure refactor — no
behavior change. Also marks the variable-alias-collision proposal as
Implemented now that the feature is shipping.

Addresses ako's review on PR mendixlabs#337.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented Apr 30, 2026

Review follow-up status on the current head:

  • Avoided the statementVarRefs/statementsVarRefs duplicate by keeping the custom-error-handler walker private to this PR (errorHandlerStatementVarRefs) instead of defining the same package-level helpers as fix: reject duplicate microflow output variables #337.
  • Added comments documenting the active-vs-queued pending-error-handler invariant.
  • Added comments for the bounded O(N) merge scan and the AnchorTop destination choice for error-handler flows.

Local validation after the update was green:

make build
make test
make lint-go

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented Apr 30, 2026

Review follow-up status:

  • Removed the duplicate statementVarRefs/statementsVarRefs implementation; fix: reject duplicate microflow output variables #337 now carries the shared cmd_microflows_builder_varutil.go helper used by both branches.
  • Added comments documenting the active-vs-queued pending error-handler state invariant.
  • Added comments for the O(N) rejoin-merge scan and the top-entry anchor used by error-handler flows.
  • Replaced the redundant hasReturnValue field with hasDeclaredReturnValue().

Local validation after the update and latest conflict resolution:

make build
make test

@ako
Copy link
Copy Markdown
Collaborator

ako commented May 1, 2026

PR #366 Review — fix: preserve custom error handler continuations

Substantial and well-structured fix. The queued pending-state approach (pendingErrorHandlerState + rewritePendingErrorHandlers) is the right architecture for deferred routing decisions. Good unit test coverage for the core scenarios. Two moderate issues.


Moderate

1. bodyHasContinuingCustomErrorHandler covers 8 of 16 statement types with ErrorHandling

The function handles: CallMicroflowStmt, CallJavaActionStmt, RestCallStmt, ImportFromMappingStmt, CreateObjectStmt, MfCommitStmt, DeleteObjectStmt, CallExternalActionStmt, plus IfStmt, LoopStmt, WhileStmt.

Missing from the switch:

RetrieveStmt, CallNanoflowStmt, CallJavaScriptActionStmt,
ExecuteDatabaseQueryStmt, DownloadFileStmt, SendRestRequestStmt,
ExportToMappingStmt, TransformJsonStmt

Impact: if a THEN or ELSE branch returns and contains e.g. a call nanoflow ... on error without rollback { log ... }, the function returns false, so thenNeedsErrorMerge stays false, no merge node is created for the IF, and the pending error handler leaks out to the parent flow's next statement instead of rejoining at the IF merge. The error handler ends up at the wrong continuation point.

2. errorHandlerStatementVarRefs has the same gap

CallNanoflowStmt, CallJavaScriptActionStmt, CallExternalActionStmt, ImportFromMappingStmt, ExportToMappingStmt are all missing from the switch. These statements can reference the skip variable in their arguments. If the next main-flow statement after an output-producing activity is a nanoflow call that reads the missing output, statementReferencesVar returns false and the error handler incorrectly rejoins before it.

Both functions should cover all 16 statement types. The fix is mechanical — add the missing cases; most can delegate to exprVarRefs on their argument lists.


Minor

AnchorLeftAnchorTop needs mx check confirmation

Changing the destination connection index for error handler flows is a BSON change that affects how Studio Pro renders the connection. The PR validation section only lists make test. Per CLAUDE.md, BSON changes should be verified against a real MPR with mx check. The bug test comment says to run mx check manually — please confirm that was done and add a note to the PR description.

_ = errorY — vestigial parameter

handleErrorHandlerMergeWithSkip silences the errorY int parameter with _ = errorY. The parameter can be removed entirely since handleErrorHandlerMerge is an internal helper.


Summary

# Severity Item
1 Moderate bodyHasContinuingCustomErrorHandler: add 8 missing statement types (CallNanoflowStmt, CallJavaScriptActionStmt, ExecuteDatabaseQueryStmt, RetrieveStmt, DownloadFileStmt, SendRestRequestStmt, ExportToMappingStmt, TransformJsonStmt)
2 Moderate errorHandlerStatementVarRefs: add same missing types for skip-var detection
3 Minor Confirm AnchorLeft→AnchorTop with mx check and note in PR description
4 Minor Remove vestigial errorY int parameter from handleErrorHandlerMergeWithSkip

@hjotha hjotha force-pushed the submit/custom-error-handler-routing branch from 8be98db to fa45c7c Compare May 2, 2026 08:18
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • In mdl/executor/cmd_microflows_builder_flows.go, the function newErrorHandlerFlow has a comment that says "connecting from the bottom of the source activity to the top of the handler." but the code sets OriginConnectionIndex: AnchorBottom and DestinationConnectionIndex: AnchorTop. This seems correct based on the comment, but I wanted to double-check if this matches the intended layout (error handlers below source activities). The comment states "Studio Pro lays custom error handlers below their source, so the destination anchor enters from above rather than from the normal left-side continuation." which justifies AnchorTop for destination.

What Looks Good

  • The PR properly addresses bug Preserve custom error-handler routing during microflow roundtrip #349 with a clear before/after description
  • Includes a comprehensive test case in mdl-examples/bug-tests/349-custom-error-handler-routing.mdl that:
    • Reproduces the issue
    • Provides validation instructions (mxcli exec and mxcli describe)
    • Includes expected outcome (0 errors in mx check, proper rejoining)
  • The fix correctly:
    • Queues pending custom-error-handler state until next safe continuation is known
    • Uses merges instead of detached EndEvents for non-terminal handlers
    • Preserves empty custom-handler error flows
    • Prevents output-producing error paths from continuing into statements that read missing output variables
  • Changes are appropriately distributed through the pipeline:
    • AST: Added HasElse field to track ELSE presence even when empty
    • Visitor: Properly sets HasElse when ELSE token is present
    • Executor: Major refactoring of error handler handling in microflow builder
    • Tests: Added unit tests covering various error handler scenarios
  • The solution avoids the root cause (immediate handling of error handlers without retaining pending state)
  • Test coverage includes edge cases like consecutive handlers, empty handlers, and output-dependent continuation prevention
  • No violation of backend abstraction layer (executor uses ctx.Backend.* patterns)
  • Changes are focused and atomic - all related to fixing the error handler continuation issue
  • Follows existing code patterns in the microflow builder

Recommendation

Approve. This is a well-focused bug fix that properly addresses the issue through the full pipeline with adequate test coverage. The changes are minimal, targeted, and maintain consistency with the existing codebase architecture.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

hjothamendix and others added 12 commits May 2, 2026 12:00
Symptom: all open PRs against main failed the shared build-and-test job in
make test-integration, even when their local build/test/lint validation passed.
The failures reproduced on origin/main, so they were baseline CI instability
rather than PR-specific regressions.

Root cause: TestWatcherDebounce could allow stale timer callbacks to send an
extra message under slow scheduling, nanoflow integration fixtures used MDL
syntax that no longer matched the grammar, and the doctype mx-check harness did
not classify known page/nanoflow showcase consistency errors as expected
limitations.

Fix: guard watcher debounce callbacks with a generation counter, tighten the
watcher burst test, update nanoflow fixtures to current MDL syntax, and extend
the known consistency-error allowlist for showcase-only limitations.

Tests: make build
Tests: go test ./cmd/mxcli/tui -run TestWatcherDebounce -count=20 -v
Tests: ./bin/mxcli check mdl-examples/doctype-tests/02b-nanoflow-examples.mdl
Tests: go test -tags integration -count=1 -timeout 30m ./mdl/executor -run 'TestRoundtripNanoflow_(Loop|EnumParameter|Annotations)|TestMxCheck_DoctypeScripts/02b-nanoflow-examples.mdl|TestMxCheck_DoctypeScripts/03-page-examples.mdl' -v
Tests: make test
Tests: make lint-go
Tests: make test-integration
Symptom: custom error-handler bodies that did not explicitly return were rebuilt as detached terminal paths, while empty custom handlers could either lose their error flow or rejoin into statements that read an output variable that is absent on the error path.

Root cause: the microflow builder handled custom error handlers immediately at the source activity. It did not retain pending handler state until the next safe continuation was known, and a later handler could overwrite an earlier pending handler.

Fix: queue pending custom handler state, route non-terminal handlers through the next safe continuation via a merge, preserve empty custom-handler flows, and terminate output-producing handlers before output-dependent continuation statements in void microflows.

Tests: added synthetic builder regressions for non-terminal handler rejoin, consecutive pending handlers, empty output-handler termination, and empty no-output-handler rejoin. Ran make build, make lint-go, and make test.
Adds an MDL script under mdl-examples/bug-tests/ exercising a
non-terminal custom error handler on a microflow call followed by a
continuation call. After exec, `mx check` reports 0 errors,
confirming the handler tail rejoins the continuation instead of
becoming a detached terminal path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom: a custom error handler for an output-producing action could rejoin
before a later DECLARE whose initial value referenced that output variable.
Studio Pro then rejected the roundtripped microflow because the output variable
is not in scope on the error-handler path.

Root cause: skip-variable routing collected references from many statement
kinds, but omitted DECLARE initial values.

Fix: include DECLARE initial values in statement reference analysis so handlers
wait for, or terminate before, output-dependent declarations.

Tests: add a graph-level regression that verifies the error-handler path cannot
reach an output-dependent DECLARE, plus the existing custom-handler routing
tests via make build and make test.
Symptom: merging the custom error-handler routing PR with the variable-alias
PR would redeclare statementVarRefs and statementsVarRefs in package executor.

Root cause: both PRs added local helper names for different pre-passes while
sharing the same package namespace.

Fix: rename the custom error-handler helper pair to handler-specific names and
add review-requested comments documenting the pending-handler state invariant,
the bounded rejoin scan, and the top-entry anchor used for error-handler flows.

Tests: make build; focused error-handler executor tests; make test.
Symptom: a custom error handler inside a returning IF branch could be forced to
terminate when the branch success tail declared values from the failed action
output. The resulting MDL inserted a return into the handler, and the generated
MPR no longer matched the original control flow.

Root cause: branch merge detection only treated empty custom handlers as needing
a merge, and output skip-var routing stopped at the first output-dependent
statement in void microflows. Derived variables declared from the failed output
and SHOW MESSAGE template arguments were also invisible to the skip-var scan.

Fix: treat non-terminal custom handlers as branch merge inputs, route pending
handlers from returning branches to that merge, carry skip-var state across
DECLARE-derived variables, and include SHOW MESSAGE expressions in handler
reference analysis.

Tests: added a synthetic branch-level regression covering a custom handler that
skips an output-derived success tail and rejoins at a shared error continuation.
Ran make build, make test, and make lint-go.
Symptom: after rebasing the custom error-handler routing PR, empty custom handlers on Java and nanoflow calls were still handled by the old body-only path, so output-dependent tails had no safe error-handler termination.

Root cause: the rebase kept legacy per-action handler code for those call actions instead of the shared finishCustomErrorHandler helper used by the rest of the PR.

Fix: route both actions through finishCustomErrorHandler so empty handlers, output skip variables, and non-empty handlers use the same pending-handler state machine.

Tests: make build; make test; make lint-go.
Symptom: the custom error-handler builder stored both returnType and a redundant hasReturnValue boolean, leaving two sources of truth for whether the microflow returns a value.

Root cause: hasReturnValue was assigned from returnType during graph construction and copied into nested error-handler builders even though the same answer can be derived directly from returnType.

Fix: remove the boolean field, add a small hasDeclaredReturnValue helper, and use it at the custom-handler routing decision point.

Tests: ran make build, targeted executor custom-error-handler tests, make test, and make lint-go.
Symptom: describe emitted IF statements inside custom error-handler blocks without the matching END IF, so exec rejected valid roundtripped handlers.

Root cause: error-handler traversal linearized every reachable activity until the first merge and formatted nested splits as plain statements, losing block structure.

Fix: traverse error-handler branches structurally, stop at the handler rejoin merge, and emit IF/ELSE/END IF around nested exclusive splits.

Tests: added a synthetic error-handler graph with a nested conditional and ran make test.
Symptom: describing a custom error handler could drop an empty else branch from
an inner decision, and exec then wrote a decision with no valid false case for
Studio Pro mx check.

Root cause: custom handler traversal skipped false flows that rejoined at the
handler-local merge, matching normal describe cleanup but losing required branch
metadata inside the handler graph.

Fix: emit the else marker whenever the false flow exists, while still avoiding
traversal through an empty false branch.

Tests: extend the structured custom-handler traversal regression to assert the
empty else is preserved.
Symptom: MDL with an explicit empty else branch could execute into a custom error-handler split whose false path had no condition, causing mx check to report a missing or invalid split condition after roundtrip.

Root cause: the visitor represented IF statements only by branch bodies. An explicit `else` with no statements was indistinguishable from an omitted else, so the builder omitted the false continuation that Studio Pro needs for that graph shape.

Fix: add IfStmt.HasElse, set it when the source contains ELSE, and have the builder and MDL re-emitter preserve that explicit empty branch.

Tests: added visitor coverage for parsing empty ELSE and executor coverage that the false path reaches the following return; ran make build && make test.
Symptom: custom error handlers containing `if X then return ... else end if` wrote a false branch rejoin flow without a case value. Studio Pro reported the outgoing sequence flow from the decision as missing its false condition.

Root cause: addErrorHandlerFlow kept only the deferred tail node ID from nested merge-less splits. It discarded nextFlowCase and nextFlowAnchor before the pending error-handler rejoin was materialized.

Fix: carry the deferred tail case and anchor through pending error-handler state, and apply them when the handler rejoins the normal continuation.

Tests: added synthetic coverage for a custom error handler with an explicit empty else rejoining through CaseValue=false; ran make build && make test.
Symptom: custom error handlers could be routed as if their output-dependent continuation were safe when the continuation used less common action types.

Root cause: the continuing-handler detector and skip-variable reference walker only covered a subset of microflow statements with ErrorHandling and input expressions.

Fix: centralize statement ErrorHandling lookup, cover every current action statement with ErrorHandling, extend variable-reference detection for action arguments, REST auth, mapping, transform, download, and validation feedback inputs, and remove the vestigial errorY parameter from the skip helper.

Tests: add synthetic unit coverage for the supported action statement matrix and skip-variable input detection.
@hjotha hjotha force-pushed the submit/custom-error-handler-routing branch from fa45c7c to 1a1524b Compare May 2, 2026 10:03
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Review Summary

This PR fixes custom error handler continuation in microflows (issue #349) by queuing pending error handlers until the next safe continuation is known, instead of processing them immediately. The changes are focused and well-tested.

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • Per the bug fix checklist, the symptom table in .claude/skills/fix-issue.md should be updated with new symptom/layer/file mapping if not already covered. While the PR description thoroughly documents the symptom, there's no evidence this skill was consulted or the table updated.

  • The substantial rewrite of mdl-examples/doctype-tests/02b-nanoflow-examples.mdl isn't explained in the PR description. While likely necessary for test validity with the fix, the connection between this cleanup and the bug fix isn't documented.

What Looks Good

  • Clear bug identification: The PR thoroughly documents the symptom (custom error handlers becoming detached terminal paths, losing error flows, or rejoining incorrectly) and root cause (premature handling at source activity).
  • Sound fix approach: Queuing pending custom-handler state until safe continuation is known, with proper merging instead of fabricating detached EndEvents.
  • Comprehensive testing:
    • Added MDL bug test (mdl-examples/bug-tests/349-custom-error-handler-routing.mdl) with clear usage instructions and validation expectations
    • Added multiple unit tests in cmd_microflows_builder_terminal_test.go covering various scenarios (non-terminal handlers, consecutive handlers, empty handlers, output-dependent paths)
  • Clean implementation:
    • Introduces well-organized state tracking for pending error handlers
    • Properly handles edge cases (empty handlers, non-terminal handlers, output-dependent continuation prevention)
    • Replaces ad-hoc immediate processing with systematic queuing and merging logic
  • Appropriate scope: All changes relate to the single concern of fixing custom error handler continuations.
  • UI stability fix: The watcher.go changes fix a potential race condition in the debounce mechanism using atomic sequencing.

Recommendation

Approve. The PR correctly addresses the bug with a well-designed solution, appropriate test coverage, and clean implementation. Please update the symptom table in .claude/skills/fix-issue.md as per the bug fix checklist before merging.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented May 2, 2026

CI stabilization note: merged #412 into this branch so it uses the same integration-CI baseline as the rest of the active PR set.

Symptom: CI failed in TestMxCheck_DoctypeScripts/empty_java_action_argument.mdl because a Java action declared as RETURNS Void was written as an entity return type named .void, and Studio Pro reported CE1613.

Root cause: the generic data-type visitor treats bare qualified names as entity/enumeration references. Java action return types reused that generic path, so the explicit Void spelling became a qualified name instead of ast.TypeVoid.

Fix: add a Java-action return-type wrapper that maps unqualified Void to ast.TypeVoid while leaving generic data-type parsing unchanged for parameters and attributes.

Tests: added visitor coverage for explicit RETURNS Void; verified mxcli check for the doctype fixture and the targeted integration subtest that failed in GitHub Actions.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • The nanoflow examples file (mdl-examples/doctype-tests/02b-nanoflow-examples.mdl) was significantly simplified, removing many detailed examples. While the core concepts appear to be preserved in a more concise form, this change seems unrelated to the bug fix and could be considered out of scope for this PR. However, since the PR adds a dedicated bug test file and the nanoflow changes don't appear to remove meaningful test coverage, this is minor.

What Looks Good

  • The bug fix is well-targeted and addresses the exact symptoms described in issue Preserve custom error-handler routing during microflow roundtrip #349:
    • Properly queues custom error handler state until safe continuations are known
    • Uses merges instead of detached EndEvents for non-terminal handlers
    • Preserves empty error flows
    • Prevents output-dependent statements from executing with missing variables on error paths
  • Comprehensive test coverage added in:
    • New bug test file: mdl-examples/bug-tests/349-custom-error-handler-routing.mdl
    • Multiple new test functions in mdl/executor/cmd_microflows_builder_terminal_test.go covering various edge cases
  • The microflow builder changes introduce a clean, systematic approach to handling pending error handlers with clear invariants
  • Watcher improvements add proper atomic sequencing for debounce mechanism (unrelated but valuable)
  • All changes follow existing code patterns and maintain backward compatibility
  • Test cases directly validate the fix against the described bug scenarios

Recommendation

Approve the PR. The changes correctly fix the reported bug with appropriate test coverage and minimal scope creep. The nanoflow examples simplification, while slightly out of scope, doesn't undermine the fix's validity and appears to maintain equivalent test coverage in a more concise form.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako
Copy link
Copy Markdown
Collaborator

ako commented May 2, 2026

Review (updated): fix: preserve custom error handler continuations

The previous blocker — statementErrorHandling() covering only 8 of 16 statement types — is fixed. The new statementErrorHandling() switch covers all 17 action types (retrieve, create, commit, delete, call microflow/nanoflow/java/javascript/web service/external action/REST/REST request, execute database query, download file, import/export mapping, transform JSON), and TestBodyHasContinuingCustomErrorHandler_CoversActionStatements pins the coverage as a regression test. No blockers remain.


Minor — futureReferencesSkipVar computed from the active handler's skip var, applied to all queued handlers

In buildFlowGraph (cmd_microflows_builder_graph.go):

fb.addPendingErrorHandlerFlowForStatement(lastID, activityID, stmt,
    statementsReferenceVar(stmts[i+1:], fb.errorHandlerSkipVar))

fb.errorHandlerSkipVar is the active handler's skip var. When there are multiple consecutive error-handling activities with output variables and some are queued (e.g., two back-to-back calls each with ON ERROR and an OutputVariable), rewritePendingErrorHandlers delivers that same futureReferences value to queued handlers whose state.skipVar may be a completely different variable. A queued handler could delay its routing ("future statements will reference my skip var") based on a lookforward that scanned for the wrong variable, or route too early if the active skip var has no further references but the queued one does.

Practical impact is limited to the case of consecutive handlers both having output variables, which is rare. But to fix it cleanly, the lookforward should be computed per handler inside rewritePendingErrorHandlers rather than once outside it:

fb.rewritePendingErrorHandlers(func(state pendingErrorHandlerState) pendingErrorHandlerState {
    futureRefs := statementsReferenceVar(stmts[i+1:], state.skipVar)
    return fb.addPendingErrorHandlerFlowForState(state, lastID, activityID, stmt, futureRefs)
})

Observations (not blockers)

Scope: The PR bundles three independent fixes — error handler routing, the watcher debounce race (sync/atomic counter), and Java action RETURNS Void handling. Each is clean and well-tested, but they're separate concerns. Not blocking for this PR, but the next similar PR should be split up.

newUpwardFlow anchor semantics: addErrorHandlerRejoinFlowForState uses newUpwardFlow when rejoining via an existing merge node. The anchors (AnchorRightAnchorBottom) would connect from the right side of the handler tail to the bottom of the merge — visually non-standard. Functionally correct for Studio Pro since positions drive layout, not anchors alone. Worth revisiting if users report cosmetically odd error handler connections.


Positives

  • 13+ repeated inline addErrorHandlerFlow / handleErrorHandlerMerge blocks replaced with finishCustomErrorHandler() — much cleaner.
  • HasElse bool correctly distinguishes if ... else end if (empty ELSE present) from if ... end if (no ELSE), with visitor test and builder test coverage.
  • collectErrorHandlerStatements now traverses nested IF/ELSE within error handler bodies and emits end if; — previously flattened everything.
  • 10 new flow graph tests directly exercise the routing invariants, including deferred-state propagation across branch boundaries and skip-var chaining.
  • Bug test MDL in mdl-examples/bug-tests/349-custom-error-handler-routing.mdl provides a reproducible Studio Pro validation target.

@ako ako merged commit 027000d into mendixlabs:main May 2, 2026
2 checks passed
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.

Preserve custom error-handler routing during microflow roundtrip

3 participants