fix(cli): make mops install safe under parallel invocations - #543
Merged
Kamirus merged 3 commits intoMay 22, 2026
Merged
Conversation
Concurrent `mops install` runs against the same project (editor watcher, vscode-motoko fixture installer, CI matrix) used to race in two places — global cache writes and local `.mops/<pkg>` copies — leaving zero-byte / truncated files that surfaced later as missing completions or type-check errors. GitHub installs were strictly worse because `mkdirSync(cacheDir)` ran before the download, so a peer process saw an empty dir as cached. Cache writes now stage into a sibling `.staging-*` dir and atomically rename onto the canonical path; the loser of a race discards its staging silently. `mops install`, `mops add`, and the local `.mops/` sync follow the same pattern. The shared `.mops/_tmp/` zip dir used by GitHub installs is also per-invocation (`mkdtemp`) now. Stale staging dirs from crashed runs are swept on the next install (mtime threshold so a sibling that is mid-staging is never clobbered). Refs LANG-1310, caffeinelabs/vscode-motoko#461. Co-authored-by: Cursor <cursoragent@cursor.com>
…github - commitStagingDir only swallows EPERM/EEXIST/ENOTEMPTY when dest exists. - Parallel-install test isolates the global cache via XDG_CACHE_HOME so the global-write code path actually executes (was a hot-cache no-op). - Sweep covers <rootDir>/.mops/_github/ and the github zip download dir (renamed to .staging-github-dl-). - Migrate dead-but-exported addCache to the staging pattern. - SIGINT exits 130 (POSIX convention). - Trim comments; CHANGELOG note about `mops cache clean` for upgrades. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Kamirus
deleted the
kamillistopad/lang-1310-mops-make-mops-install-safe-under-parallel-invocations
branch
May 22, 2026 08:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two
mopsprocesses installing into the same project (editor watcher, vscode-motoko's fixture installer, CI matrix sharing a global cache) race on cache writes and leave zero-byte / truncated files in.mops/. Symptom downstream: missing completions, hover, type-check errors. v2.13.2's concurrent-invocations fix coveredcheck/build/check-stablescratch dirs, not the install cache path.Two race windows:
install-mops-dep.ts):Promise.all(mkdir + writeFile)writes directly intocacheDirwhileisDepCached = fs.existsSync(cacheDir)— peer sees "cached" mid-write and copies a half-populated tree..mops/<pkg>(syncLocalCache): two concurrent installs both missexistsSync(dest)and interleave writes viancp.GitHub installs were strictly worse —
mkdirSync(cacheDir)ran before the download, so peers saw an empty dir as cached. The shared.mops/_tmp/zip dir was also clobbered by concurrent github installs.What
Cache writes stage into a sibling
.staging-*dir (mkdtemp, same filesystem) and atomicallyrenameonto the canonical path. Race-loser catchesENOTEMPTY/EEXIST/EPERM(only whendestexists, to avoid masking real Windows AV / permission errors), drops its staging, and observes a complete cache. Applies to mops registry installs, github installs, local.mops/sync, and the github zip download dir. Stale staging is swept on the next install with a 1h mtime cutoff.Test plan
mops installruns against an isolatedXDG_CACHE_HOMEproduce no zero-byte files and no.staging-*leftovers (cold-cache path actually exercised).parallel builds of the same canister both succeedstill passes.npm run check+npm run lintclean.Refs LANG-1310, caffeinelabs/vscode-motoko#461.