Repository navigation
Conversation
Re-apply changes from Expensify#98422, which were lost when the fork's main was squashed.
collectioneur
marked this pull request as ready for review
August 25, 2026 09:46
commit/no-commit settings are respected starting from this version.
Contributor
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
mountiny
approved these changes
Aug 25, 2026
Contributor
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
63 tasks done
Contributor
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.61-0 🚀
|
Contributor
|
🚀 Deployed to production by https://github.com/AndrewGable in version: 9.4.61-4 🚀
Bundle Size Analysis (Sentry): |
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.
Explanation of Change
The problem
A Blacksmith sticky disk is not a tarball of files like
actions/cache- it is an ext4 block device stored in Blacksmith's Ceph cluster. When a job mounts one, the last committed snapshot is cloned and mounted at the given path, when the job finishes, the clone is committed back and becomes the new snapshot.The important part is when that commit happens: the sticky disk action commits in its post-job phase, so the snapshot always captures the state of the mounted path at the very end of the job. There is no API to commit at the end of a specific step - we cannot say "snapshot
node_modulesright afternpm ciand ignore everything written afterwards". Everything a job writes into the mount before it ends is part of the snapshot.That matters because plenty of tooling writes into
node_modulesduring a job:node_modules/.cache.node_modules/<lib>/android/build,.cxxobject files per ABI, generated codegen sources.Because every job committed (
commitdefaults totrue), each run layered its own generated files into the snapshot, and the next run started from that fatter snapshot and added more. It compounds, and on the block level it is worse than the file listing suggests: deleting or rewriting a file inside the mount frees ext4 blocks, but the freed blocks are never returned to the storage layer -fstrimon a sticky disk mount fails withthe discard operation is not supported. The billed size therefore tracks every block ever written, not the blocks currently in use.The result:
node_modulesthat is 3.8 GB after a cleannpm cigrew into disks reporting 30 GB of live files and 103 GB of allocated blocks in the Blacksmith dashboard after ~3 weeks on the same key. Inspecting one of those disks showed exactly the expected culprits -react-native-reanimatedat 4.0 GB,react-native-workletsat 3.2 GB,react-native-quick-cryptoat 2.9 GB, and so on for every library with native code, against ~50 MB each when freshly installed. Sticky disk storage is billed at $0.50/GB/month, so a single such key cost ~$50/month on its own.The fix
Separate "who produces the snapshot" from "who consumes it".
setupNodetakes a newSEED_STICKY_DISKSinput that controls thecommitmode of thenode_modulesdisks:seedStickyDisks.ymlworkflow passesSEED_STICKY_DISKS: 'true'and getscommit: if-missing. It installs dependencies and does nothing else, so the snapshot it writes is a cleannpm ciresult by construction. It runs on every push tomain, once a key has a snapshot, the seed is a no-op that mounts it and exits.commit: false. Jobs still readnode_modulesfrom the snapshot and may write whatever they like into their own clone - caches, Gradle output,.cxxand all of it is discarded when the clone is unmounted instead of being committed. Disks stay at the size of a clean install permanently.~/.npmdisk keeps committing, withcommit: on-change. Its key has no hash in it, so it has to keep absorbing new package tarballs across lockfile bumps, and it does not suffer the same growth: tarballs are content-addressed, it sits at ~6 GB, andon-changeskips the commit on the majority of runs that never touch it.Cache-hit detection is unchanged: the
node_modules/.stickydisk-installedmarker is written at the end ofsetupNode, so it lands in the seed's snapshot and consumers skipnpm ciwhen they see it.Fixed Issues
$ #98593
$ #98563
Tests
No QA
Offline tests
N/A
QA Steps
No QA
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.