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
Severity
High. This is silent data corruption, not a crash — it produces no error, and the affected project continues to work with
mxcliitself (which happens to tolerate the corrupted shape), while becoming broken for Studio Pro andmxbuild. It affects every configuration in the project, not just the one being edited, and is triggered by anyALTER SETTINGS ...command, not just something test-runner specific.Summary
mxcli's default (modelsdk) engine rewrites the entireSettings$ProjectSettingsdocument on every settings write (this is unavoidable — Mendix stores all configurations in one document). For theSettings$ConfigurationSettingspart,overlayConfigurationSettings/serverConfigurationToBSON(inmdl/backend/modelsdk/settings_write.go) rebuilt eachServerConfigurationfrom scratch using only the fields the Go model knows about, instead of overlaying changes onto the original raw document. This silently:CustomSettingswith an empty array on every write, regardless of its original content.Tracingwithnilon every write, regardless of its original content.ConstantValues) using a flat top-levelValuefield, discarding the standard Mendix nestedSharedOrPrivateValue.Valuestorage shape.mxcli's own reader (constantValueOfinmdl/backend/modelsdk/settings_read.go) checks the flatValuefield first, falling back to the nestedSharedOrPrivateValue.Valueonly if empty — somxcliitself never notices anything is wrong; a round-trip throughmxclialone looks perfectly fine. But Studio Pro andmxbuildonly understand the standard nested form. The practical effect: after a singleALTER SETTINGSwrite of any kind, every constant override in the project reads as empty in Studio Pro, and any Integer/Long-typed constant failsmxbuild's type validation with an opaqueInvalid Integer/Long value: ''error pointing at whichever configuration happens to contain it.Steps to reproduce
At this point,
mxcli -c "DESCRIBE SETTINGS" -p ProbeProj.mprstill shows the constant correctly (masking the bug), but the on-disk BSON for that constant no longer has aSharedOrPrivateValuekey at all -- only a flatValuekey. 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-packagefails withInvalid Integer/Long value: ''.Confirmed on a production project (with pre-existing constant overrides across five configurations): a single
mxcli testrun left every constant override, in every configuration, silently converted to the flat shape -- appearing as empty in Studio Pro immediately afterward, despitemxcli's own tooling reporting nothing wrong throughout.Root cause
mdl/backend/modelsdk/settings_write.go,serverConfigurationToBSON(pre-fix) constructed a brand newbson.Mfor each configuration using only known fields, discarding the original raw document.ConstantValueswere rebuilt via a hardcoded flat shape:This ignores that real constant values are stored as
ConstantValue.SharedOrPrivateValue.Value(a nestedSettings$SharedValuedocument), asmdl/backend/modelsdk/settings_read.go's own comment onconstantValueOfdocuments.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;ConfigurationSettingswas 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
Nameand constantConstantId:ServerConfiguration: start from the original raw map (when one exists) and only overwrite the known scalar fields, leavingCustomSettings/Tracing/anything else untouched.ConstantValue: start from the original raw map and update whichever value field it actually uses (nestedSharedOrPrivateValue.Valueif present, flatValueotherwise) rather than replacing the whole document. New constants with no prior raw shape should be written using the standard nestedSharedValueform, 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 testrun) and am happy to open a PR once this issue is approved.Environment
main@ead892672f82fdc5f54ed8a944e98026845700a2(2026-07-28)