Skip to content

mxcli test cleanup crashes restoring AfterStartupMicroflow, leaving the project's security/after-startup settings permanently mutated #803

Description

@fabric-ian

Summary

When a project already has an AfterStartupMicroflow configured, mxcli test's cleanup step fails to restore it correctly and throws a parse error instead. Because this happens in cleanup(), which runs unconditionally after a build failure (and after every run in general), the failure leaves the project in a mutated state: security level still forced OFF and AfterStartupMicroflow still pointing at the now-deleted MxTest.TestRunner, with no error surfaced beyond a swallowed "Warning".

Observed originally on a production project when a mxcli test run hit an unrelated mxbuild failure; the subsequent cleanup produced:

Warning: could not restore after-startup: exit status 1: Parse error: line 1:90 token recognition error at: '''

...and the project was left with AfterStartupMicroflow pointing at the deleted MxTest.TestRunner module.

Steps to reproduce

mxcli new MxTestRepro --version 11.12.0 --skip-init
cd MxTestRepro

# Give the project a real AfterStartup microflow so getAfterStartup() has
# something non-empty to round-trip (this is the normal case for any real project).
./mxcli -c "ALTER SETTINGS MODEL AfterStartupMicroflow = 'System.Nothing'" -p MxTestRepro.mpr

cat > smoke.test.mdl << 'EOF'
/**
 * @test Basic arithmetic sanity check
 * @expect $result = 4
 */
DECLARE $result Integer = 2 + 2;
/
EOF

./mxcli test smoke.test.mdl -p MxTestRepro.mpr --verbose

Expect: after the run, DESCRIBE SETTINGS shows AfterStartupMicroflow restored to System.Nothing. Instead, the restore step fails with a parse error, and AfterStartupMicroflow is left pointing at MxTest.TestRunner (which no longer exists).

Root cause

cmd/mxcli/testrunner/runner.go, getAfterStartup():

for _, line := range strings.Split(string(output), "\n") {
    line = strings.TrimSpace(line)
    if strings.Contains(line, "AfterStartupMicroflow") {
        parts := strings.SplitN(line, "=", 2)
        if len(parts) == 2 {
            val := strings.TrimSpace(parts[1])
            val = strings.Trim(val, "'\"")
            val = strings.TrimSuffix(val, ";")
            val = strings.TrimSpace(val)
            return val, nil
        }
    }
}

DESCRIBE SETTINGS emits AfterStartupMicroflow as one line among several in a comma-separated alter settings model ... ; block, e.g.:

  AfterStartupMicroflow = 'System.Nothing',

Whenever this key isn't the last one in the block (the normal case), the line ends in , rather than ;. strings.Trim(val, "'\"") only strips leading/trailing characters that are ' or " — it stops at the first non-matching character, so the trailing , blocks it from removing the second '. The subsequent TrimSuffix(val, ";") does nothing since the suffix is , not ;. The result is a corrupted value like System.Nothing', (stray quote + comma retained).

This corrupted value is then fed back into cleanup():

cmd := fmt.Sprintf("ALTER SETTINGS MODEL AfterStartupMicroflow = '%s'", origAfterStartup)

producing ALTER SETTINGS MODEL AfterStartupMicroflow = 'System.Nothing',' — three consecutive quote characters, which is exactly the reported parse error (token recognition error at: ''').

This also explains why the bug is easy to miss in ad-hoc testing: it only manifests when AfterStartupMicroflow was already set before running mxcli test (i.e. real projects, not blank/scratch projects), which is precisely the common case in production use.

Suggested fix

  1. In getAfterStartup(), strip the trailing ,/; before trimming quote characters:
    val := strings.TrimSpace(parts[1])
    val = strings.TrimRight(val, ",;")
    val = strings.TrimSpace(val)
    val = strings.Trim(val, "'\"")
  2. Defense in depth: in cleanup(), escape any embedded ' as '' before re-interpolating into the MDL string, since qualified names shouldn't ever need escaping but a parser that treats user-controlled round-tripped strings as trusted is fragile:
    escaped := strings.ReplaceAll(origAfterStartup, "'", "''")
    cmd := fmt.Sprintf("ALTER SETTINGS MODEL AfterStartupMicroflow = '%s'", escaped)
  3. Consider having cleanup() fail loudly (non-zero exit / clear final message) rather than a swallowed "Warning" when a restore step fails, since this leaves the project's security level and after-startup mutated with no indication to the user beyond scrollback.

Environment

  • mxcli: built from main @ ead892672f82fdc5f54ed8a944e98026845700a2 (2026-07-28)
  • Mendix version: 11.12.0
  • Reproduced on a throwaway blank project, not a production project

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