Skip to content

Canvas annotations inside ON ERROR { } are accepted and silently ignored, and describe emits none of them — the error branch's layout is unwritable and lost on round-trip #991

Description

@TDSC0706

mxcli version

Reproduced on both:

mxcli version v0.19.0 (2026-08-21T13:13:26Z)
mxcli version v0.20.0 (2026-08-28T13:22:53Z)   <- current release, windows-amd64

mxcli diag:

mxcli diagnostics
  Version:     v0.19.0
  Go:          go1.26.6 windows/amd64
  Sessions:    520 (last 7 days)
  Errors:      4 (last 7 days)     <- all four unrelated "not found" lookups

Mendix version

Mendix 11.12.1, MPR v2. The repro below runs on a blank project created with
mx create-project --app-name ReproApp, so nothing project-specific is involved.

Scenario

We generate nanoflows from MDL and steer the canvas with @position, @anchor and
@curve. Our house convention (enforced by a layout linter) is that an error-handling
branch descends in a column under the activity that owns the handler: the edge leaves
the source's bottom (oci=2) and enters the destination's top (dci=0), with the
conventional control vectors (0,+30) / (0,−30).

Every annotation inside the block below is deliberately non-default, so that "it came
out right" cannot be confused with "the annotation was applied". The third statement is the
control: the same annotations at top level.

CREATE MICROFLOW MyFirstModule.SUB_Callee ()
BEGIN
  @position(200, 200)
  LOG INFO NODE 'Repro' 'callee';
  @position(400, 200)
  RETURN;
END;
/

-- Repro: annotations inside ON ERROR
CREATE NANOFLOW MyFirstModule.NF_ErrorBranch ()
BEGIN
  @position(240, 200)
  CALL MICROFLOW MyFirstModule.SUB_Callee() ON ERROR WITHOUT ROLLBACK {
    @position(240, 380)
    @anchor(from: right, to: left)
    @curve(from: (0, 30), to: (0, -30))
    LOG ERROR NODE 'Repro' 'handled';
  };
  @position(560, 200)
  RETURN;
END;
/

-- Control: the same annotations at top level
CREATE NANOFLOW MyFirstModule.NF_TopLevelControl ($A: Integer)
RETURNS Integer AS $R
BEGIN
  @position(200, 200)
  @anchor(from: bottom, to: top)
  @curve(from: (0, 30), to: (0, -30))
  DECLARE $R Integer = $A + 1;
  @position(200, 400)
  RETURN $R;
END;
/
mxcli check repro.mdl -p ReproApp.mpr --references   ->  Syntax OK (4 statements), All references valid
mxcli exec  repro.mdl -p ReproApp.mpr                ->  Created ... (exit 0)
mx check ReproApp.mpr                                ->  The app contains: 0 errors.

Expected output

The handler edge stores the authored sides and control vectors —
OriginConnectionIndex = 1, DestinationConnectionIndex = 3,
OriginControlVector = 0;30, DestinationControlVector = 0;-30 — exactly as the control
nanoflow does with the same annotations, and describe round-trips them.

Experienced output

Read back from the stored BSON (mxcli bson dump --type nanoflow --object …), v0.20.0:

NF_ErrorBranch
  ActionActivity (240,200) -> ActionActivity (240,380)  anc=(2,0)  cv=(0,0) -> (0,0)   [error edge]

NF_TopLevelControl      <- control, same annotations, top level
  ActionActivity (200,200) -> EndEvent       (200,400)  anc=(2,3)  cv=(0;30) -> (0;-30)

Everything passes mxcli check, mxcli exec reports success, and mx check reports 0
errors. No headless view shows the loss.

(Side note, unrelated to this issue but visible in the control: to: top stored dci=3
rather than 0 — that is #990, reproduced here independently on 11.12.1.)

Second half: describe emits nothing for the branch

Round-tripping NF_ErrorBranch with v0.20.0:

create or modify nanoflow MyFirstModule.NF_ErrorBranch ()
begin
  @position(240, 200)
  call microflow MyFirstModule.SUB_Callee() on error without rollback {
    log error node 'Repro' 'handled';
  };
  @position(560, 200)
  return;
end;
/

The log error inside the block comes back with no @position — even though the stored
RelativeMiddlePoint is 240;380, i.e. a value the writer did accept. So describe
exec does not merely fail to improve the branch's layout: it discards the position that
was there
, collapsing the handler back onto auto-placement.

Same on a hand-drawn flow: a nanoflow with three error branches aligned by hand in Studio
Pro describes with @position / @curve on every top-level activity and none on the
activities inside on error without rollback { … }.

Impact

This is the one annotation gap with no second tool behind it. Microflow layout can be
patched through Studio Pro's PED API (ped_update_document), but that API does not write
nanoflows
— so for a nanoflow the MDL surface is the only writer, and an error branch's
geometry is unreachable from any script.

Concretely it makes two layout rules mutually unsatisfiable. The branch must descend in a
column (each handler gets its own x, so two handlers cannot fight over one horizontal
lane); a descending edge needs curvature or it renders as a bare diagonal; curvature cannot
be written inside the block. We measured four different drawings of the same two-handler
nanoflow — 4 lint findings (branch horizontal, violating the column rule), 6 (branch in
a column, the correct drawing), 10 (handler rejoining the merge). None reaches zero, and the
residue is entirely (0,0) control vectors on edges MDL cannot name.

Because describe also drops the branch's @position, regenerating a nanoflow a human has
aligned in Studio Pro destroys that work silently — Replaced nanoflow: on stdout,
mx check 0 errors, and the canvas quietly worse. Same class as the mapping round-trips
fixed in 0.20.0: the statement reported success and the model came back different.

Related

Suggested resolution

  1. Honour @anchor / @curve / @merge on statements inside an ON ERROR block — the
    error edge, the edges between handler activities, and the rejoin edge.
  2. Emit @position (and non-default @anchor / @curve) for activities inside the block
    in describe, so the round-trip stops losing geometry that is already stored.
  3. Failing 1, reject the annotations there instead of accepting them — an ignored
    annotation that parses is worse than a syntax error, which is the argument the @size
    note in Microflow canvas: container Size, edge waypoints and merge positions are not writable — hand-quality layout impossible via MDL #884 already makes.

Happy to test a fix build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions