Expected Behavior
Read-only Dashmate commands such as dashmate config get and dashmate core cli ... should not rewrite the Dashmate configuration file.
Configuration updates should remain durable after dashmate config set exits successfully, including when another read-only Dashmate command is running concurrently.
Current Behavior
Dashmate hydrates each Config by calling setOptions(), which marks the config as changed. BaseCommand.finally() then persists the complete config file after every successful command, including read-only commands.
The persistence path rewrites the whole config.json without inter-process locking or stale-write detection. Consequently, two overlapping Dashmate processes can perform a lost update:
- A read-only command loads the old full configuration.
dashmate config set loads the same configuration, changes one value, writes it, prints success, and exits successfully.
- The read-only command exits later and writes its stale full configuration over the updated file.
The setting silently reverts even though config set reported success. This can cause subsequent render/update/restart operations to use an older component image or other stale configuration.
The relevant code path appears to be:
Config constructor calls setOptions(), which sets this.changed = true.
BaseCommand.finally() calls configFileRepository.write(configFile) whenever configFile.isChanged() is true.
ConfigFileJsonRepository.write() replaces the complete JSON file without coordinating with other Dashmate processes.
Possible Solution
- Mark configurations loaded from disk as clean after hydration, unless a migration or actual mutation changed them.
- Ensure read-only commands never persist configuration as a side effect.
- Serialize config read-modify-write operations across Dashmate processes with an inter-process lock, or add equivalent stale-write detection/retry. Atomic rename alone would prevent partial writes but would not prevent this lost-update case.
- Add concurrency tests covering a slow read-only command overlapping
config set.
Steps to Reproduce (for bugs)
-
Configure a Dashmate node and record the config.json hash and modification time.
-
Run a read-only command such as:
dashmate core cli getblockchaininfo
The configuration file is rewritten despite no requested configuration change.
-
To reproduce the lost update, start a Dashmate command that remains in flight after loading the configuration, then concurrently run:
dashmate config set platform.drive.abci.docker.image <new-image> --config=testnet
-
Allow the first command to exit after config set. Inspect config.json directly. With the stale writer finishing last, the image setting has reverted to its previous value even though config set exited successfully.
A loop of concurrent read-only CLI calls while repeatedly setting and reading back a value also reproduces the issue nondeterministically.
Context
This was observed during a rolling component update. Concurrent status queries overlapped image-pin updates. Some nodes retained one old image digest while others completed correctly, depending on process completion order. The same failure pattern was observed on separate runs.
Immediate readback/retry reduces the likelihood but does not close the race, because another stale Dashmate process can still write after the readback.
Your Environment
- Version used: Dashmate 4.1.0; the same behavior was also observed with 4.1.0-rc.2
- Environment name and version: Node.js v24.14.1
- Operating System and version: Linux, arm64
- Link to your project: Dash Platform
Expected Behavior
Read-only Dashmate commands such as
dashmate config getanddashmate core cli ...should not rewrite the Dashmate configuration file.Configuration updates should remain durable after
dashmate config setexits successfully, including when another read-only Dashmate command is running concurrently.Current Behavior
Dashmate hydrates each
Configby callingsetOptions(), which marks the config as changed.BaseCommand.finally()then persists the complete config file after every successful command, including read-only commands.The persistence path rewrites the whole
config.jsonwithout inter-process locking or stale-write detection. Consequently, two overlapping Dashmate processes can perform a lost update:dashmate config setloads the same configuration, changes one value, writes it, prints success, and exits successfully.The setting silently reverts even though
config setreported success. This can cause subsequent render/update/restart operations to use an older component image or other stale configuration.The relevant code path appears to be:
Configconstructor callssetOptions(), which setsthis.changed = true.BaseCommand.finally()callsconfigFileRepository.write(configFile)wheneverconfigFile.isChanged()is true.ConfigFileJsonRepository.write()replaces the complete JSON file without coordinating with other Dashmate processes.Possible Solution
config set.Steps to Reproduce (for bugs)
Configure a Dashmate node and record the
config.jsonhash and modification time.Run a read-only command such as:
The configuration file is rewritten despite no requested configuration change.
To reproduce the lost update, start a Dashmate command that remains in flight after loading the configuration, then concurrently run:
Allow the first command to exit after
config set. Inspectconfig.jsondirectly. With the stale writer finishing last, the image setting has reverted to its previous value even thoughconfig setexited successfully.A loop of concurrent read-only CLI calls while repeatedly setting and reading back a value also reproduces the issue nondeterministically.
Context
This was observed during a rolling component update. Concurrent status queries overlapped image-pin updates. Some nodes retained one old image digest while others completed correctly, depending on process completion order. The same failure pattern was observed on separate runs.
Immediate readback/retry reduces the likelihood but does not close the race, because another stale Dashmate process can still write after the readback.
Your Environment