Skip to content

ALTER SETTINGS (any command) silently corrupts every constant override in the project, and drops CustomSettings/Tracing, on write #801

Description

@fabric-ian

Severity

High. This is silent data corruption, not a crash — it produces no error, and the affected project continues to work with mxcli itself (which happens to tolerate the corrupted shape), while becoming broken for Studio Pro and mxbuild. It affects every configuration in the project, not just the one being edited, and is triggered by any ALTER SETTINGS ... command, not just something test-runner specific.

Summary

mxcli's default (modelsdk) engine rewrites the entire Settings$ProjectSettings document on every settings write (this is unavoidable — Mendix stores all configurations in one document). For the Settings$ConfigurationSettings part, overlayConfigurationSettings/serverConfigurationToBSON (in mdl/backend/modelsdk/settings_write.go) rebuilt each ServerConfiguration from scratch using only the fields the Go model knows about, instead of overlaying changes onto the original raw document. This silently:

  • Replaced CustomSettings with an empty array on every write, regardless of its original content.
  • Replaced Tracing with nil on every write, regardless of its original content.
  • Rewrote every constant override (ConstantValues) using a flat top-level Value field, discarding the standard Mendix nested SharedOrPrivateValue.Value storage shape.

mxcli's own reader (constantValueOf in mdl/backend/modelsdk/settings_read.go) checks the flat Value field first, falling back to the nested SharedOrPrivateValue.Value only if empty — so mxcli itself never notices anything is wrong; a round-trip through mxcli alone looks perfectly fine. But Studio Pro and mxbuild only understand the standard nested form. The practical effect: after a single ALTER SETTINGS write of any kind, every constant override in the project reads as empty in Studio Pro, and any Integer/Long-typed constant fails mxbuild's type validation with an opaque Invalid Integer/Long value: '' error pointing at whichever configuration happens to contain it.

Steps to reproduce

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

# Create a constant and override its value for the Default configuration
cat > mk_const.mdl << 'EOF'
CREATE CONSTANT MyFirstModule."ProbeConst"
  TYPE String
  DEFAULT 'original-value';
EOF
./mxcli exec mk_const.mdl -p ProbeProj.mpr

cat > override_const.mdl << 'EOF'
ALTER SETTINGS CONSTANT 'MyFirstModule.ProbeConst' VALUE 'overridden-in-default'
  IN CONFIGURATION 'Default';
EOF
./mxcli exec override_const.mdl -p ProbeProj.mpr

# Trigger a COMPLETELY UNRELATED settings write
./mxcli -c "ALTER SETTINGS MODEL AfterStartupMicroflow = 'System.Nothing'" -p ProbeProj.mpr

At this point, mxcli -c "DESCRIBE SETTINGS" -p ProbeProj.mpr still shows the constant correctly (masking the bug), but the on-disk BSON for that constant no longer has a SharedOrPrivateValue key at all -- only a flat Value key. Opening the project in Studio Pro at this point shows the constant as empty. If the constant were Integer/Long-typed and referenced during a build, mxbuild --target=portable-app-package fails with Invalid Integer/Long value: ''.

Confirmed on a production project (with pre-existing constant overrides across five configurations): a single mxcli test run left every constant override, in every configuration, silently converted to the flat shape -- appearing as empty in Studio Pro immediately afterward, despite mxcli's own tooling reporting nothing wrong throughout.

Root cause

mdl/backend/modelsdk/settings_write.go, serverConfigurationToBSON (pre-fix) constructed a brand new bson.M for each configuration using only known fields, discarding the original raw document. ConstantValues were rebuilt via a hardcoded flat shape:

cvArr = append(cvArr, bson.M{
    "$ID":        configID(cv.ID),
    "$Type":      "Settings$ConstantValue",
    "ConstantId": cv.ConstantId,
    "Value":      cv.Value,
})

This ignores that real constant values are stored as ConstantValue.SharedOrPrivateValue.Value (a nested Settings$SharedValue document), as mdl/backend/modelsdk/settings_read.go's own comment on constantValueOf documents.

This also contradicts the file's own stated architecture (ADR-0005, "guard-don't-drop" -- quoted directly from the doc comment on UpdateProjectSettings): every other settings part (ModelSettings, Workflows, Language) overlays new values onto the preserved raw document; ConfigurationSettings was the one part that instead rebuilt from scratch, which is inconsistent with the rest of the file and is what caused the drop.

Suggested fix

Overlay onto the original raw document instead of rebuilding from scratch, matched by configuration Name and constant ConstantId:

  • For each ServerConfiguration: start from the original raw map (when one exists) and only overwrite the known scalar fields, leaving CustomSettings/Tracing/anything else untouched.
  • For each ConstantValue: start from the original raw map and update whichever value field it actually uses (nested SharedOrPrivateValue.Value if present, flat Value otherwise) rather than replacing the whole document. New constants with no prior raw shape should be written using the standard nested SharedValue form, not the flat one.

I have a working fix along these lines (verified: constants preserve their exact original storage shape and survive repeated unrelated settings writes; confirmed byte-for-byte identical structure to a clean baseline on a production project after a full mxcli test run) and am happy to open a PR once this issue is approved.

Environment

  • mxcli: built from main @ ead892672f82fdc5f54ed8a944e98026845700a2 (2026-07-28)
  • Mendix version: 11.12.0
  • Reproduced on a throwaway blank project; separately confirmed (structurally, without ever inspecting or exposing actual secret values) against a real 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