installCommand.ts already documents this bug class and works around it for one case:
// Bun caches extracted tgz content keyed by file path, not content hash.
// When the tgz is regenerated at the same path, bun serves stale content.
// Workaround: nuke the .bun cache entry so bun is forced to re-extract.
// See: https://github.com/oven-sh/bun/issues/29372
clearBunCache(packageName);
The same mistake — cache keyed by identity rather than content — appears in two more places that aren't covered.
1. ~/.fhir/packages/<name>@<version> is reused even when the source tgz changed
babelfhir-ts install ./my-ig-0.1.0.tgz extracts to ~/.fhir/packages/my-ig@0.1.0 and reuses that directory on later runs. Since an IG's version only changes at release, every local rebuild during development regenerates from whatever FSH was compiled the first time that version was built. No warning; the run reports success.
Reproduced 2026-08-02 with maxhealth.consent@0.1.0:
- Built the IG, added a
CodeSystem + ValueSet to the FSH.
- Re-ran the build at the same version → generated package had no
valuesets/ directory. Output was from the pre-change FSH.
rm -rf ~/.fhir/packages/maxhealth.consent@0.1.0 → rebuild → valuesets/ appears.
Only caught because the expected directory was missing. A subtler change (an added code, a tightened cardinality) would have shipped silently as stale types. CI is unaffected — hosted runners are cold — so this bites exactly the person iterating on an IG, and it looks like their FSH edit "did nothing".
Both consumers have had to work around it by hand:
// fhir/build.mjs (proxy-smart)
rmSync(join(homedir(), ".fhir", "packages", `${PKG_NAME}@${PKG_VERSION}`), { recursive: true, force: true });
Suggested fix: hash the input tarball and store it alongside the extraction; re-extract when it differs. Or extract to a content-addressed directory. A --no-cache flag would be a usable stopgap.
2. clearBunCache misses the global bun cache
It clears node_modules/.bun/<pkg>@… and node_modules/<pkg>/, but not ~/.bun/install/cache/<scope>/<pkg>@<version>@@<registry>@@@1.
Under linker = "isolated", node_modules/.bun/** are hardlinks into the global cache. So clearing only the project-local entries can leave the authoritative copy stale, and the next install re-links the stale content back in. Observed exactly that:
rm -rf node_modules/.bun/@scope+pkg*
bun install # "2 packages installed" — restored the STALE copy
rm -rf ~/.bun/install/cache/@scope/pkg*
bun install # correct content
The hardlinking also means anything writing into node_modules in place silently rewrites the global cache for every project on the machine — worth a warning in the docs even if it's out of scope to fix here.
3. Minor: install --skip-install is overloaded
install refers to installing a FHIR IG; --skip-install refers to skipping the npm install step. Two senses of the word in one invocation, and the flag reads as a contradiction. Not urgent, but --no-link / --pack-only / --generate-only would say what it does.
Context
Verified on 1.5.21. Downstream: Max-Health-Inc/proxy-smart fhir/build.mjs and .github/workflows/publish-ig.yml, both of which now carry manual rm -rf workarounds for (1). Related: Max-Health-Inc/proxy-smart#895.
installCommand.tsalready documents this bug class and works around it for one case:The same mistake — cache keyed by identity rather than content — appears in two more places that aren't covered.
1.
~/.fhir/packages/<name>@<version>is reused even when the source tgz changedbabelfhir-ts install ./my-ig-0.1.0.tgzextracts to~/.fhir/packages/my-ig@0.1.0and reuses that directory on later runs. Since an IG's version only changes at release, every local rebuild during development regenerates from whatever FSH was compiled the first time that version was built. No warning; the run reports success.Reproduced 2026-08-02 with
maxhealth.consent@0.1.0:CodeSystem+ValueSetto the FSH.valuesets/directory. Output was from the pre-change FSH.rm -rf ~/.fhir/packages/maxhealth.consent@0.1.0→ rebuild →valuesets/appears.Only caught because the expected directory was missing. A subtler change (an added code, a tightened cardinality) would have shipped silently as stale types. CI is unaffected — hosted runners are cold — so this bites exactly the person iterating on an IG, and it looks like their FSH edit "did nothing".
Both consumers have had to work around it by hand:
Suggested fix: hash the input tarball and store it alongside the extraction; re-extract when it differs. Or extract to a content-addressed directory. A
--no-cacheflag would be a usable stopgap.2.
clearBunCachemisses the global bun cacheIt clears
node_modules/.bun/<pkg>@…andnode_modules/<pkg>/, but not~/.bun/install/cache/<scope>/<pkg>@<version>@@<registry>@@@1.Under
linker = "isolated",node_modules/.bun/**are hardlinks into the global cache. So clearing only the project-local entries can leave the authoritative copy stale, and the next install re-links the stale content back in. Observed exactly that:The hardlinking also means anything writing into
node_modulesin place silently rewrites the global cache for every project on the machine — worth a warning in the docs even if it's out of scope to fix here.3. Minor:
install --skip-installis overloadedinstallrefers to installing a FHIR IG;--skip-installrefers to skipping the npm install step. Two senses of the word in one invocation, and the flag reads as a contradiction. Not urgent, but--no-link/--pack-only/--generate-onlywould say what it does.Context
Verified on 1.5.21. Downstream: Max-Health-Inc/proxy-smart
fhir/build.mjsand.github/workflows/publish-ig.yml, both of which now carry manualrm -rfworkarounds for (1). Related: Max-Health-Inc/proxy-smart#895.