Skip to content

Stale-cache bug class is only half-mitigated: ~/.fhir/packages is never invalidated, and clearBunCache misses the global bun cache #144

Description

@quotentiroler

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:

  1. Built the IG, added a CodeSystem + ValueSet to the FSH.
  2. Re-ran the build at the same version → generated package had no valuesets/ directory. Output was from the pre-change FSH.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions