Skip to content

Instantly share code, notes, and snippets.

@bastianolea
Created September 11, 2026 20:20
Show Gist options
  • Select an option

  • Save bastianolea/9fa6f3a83beddc5e5af1266ccf49a261 to your computer and use it in GitHub Desktop.

Select an option

Save bastianolea/9fa6f3a83beddc5e5af1266ccf49a261 to your computer and use it in GitHub Desktop.
Fixing R `sf` load failure on Intel macOS: `libx265.216.dylib` / `x265_api_get_216` not found

Fixing R sf load failure on Intel macOS: libx265.216.dylib / x265_api_get_216 not found

Platform: macOS 14.6.1 (Sonoma), Intel x86_64 · Homebrew 6.x · R 4.6.1 · sf linked to GDAL via Homebrew

If you landed here from an error like Library not loaded: libx265.216.dylib or Symbol not found: _x265_api_get_216 when loading sf (or terra, gdal, anything that pulls in libheif), this gist explains what happened, the fix that worked, and the alternative approaches.

Full error I got:

library(sf) Error: package or namespace load failed for ‘sf’ in dyn.load(file, DLLpath = DLLpath, ...): unable to load shared object '/usr/local/lib/R/4.6/site-library/sf/libs/sf.so': dlopen(/usr/local/lib/R/4.6/site-library/sf/libs/sf.so, 0x0006): Library not loaded: /usr/local/opt/x265/lib/libx265.216.dylib Referenced from: <13B19319-38AC-30F1-8915-7F1DB6F58C49> /usr/local/Cellar/libheif/1.23.1/lib/libheif.1.23.1.dylib Reason: tried: '/usr/local/opt/x265/lib/libx265.216.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/opt/x265/lib/libx265.216.dylib' (no such file), '/usr/local/opt/x265/lib/libx265.216.dylib' (no such file), '/usr/local/Cellar/r/4.6.1/lib/R/lib/libx265.216.dylib' (no such file), '/var/folders/gt/vdp_nx_x3bq5wgnlr1nphkd1zbdps_/T/rstudio-fallback-library-path-1062076913/libx265.216.dylib' (no such file), '/usr/local/Cellar/x265/4.3/lib/libx265.216.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/x265/4.3/lib/libx265.216.


Symptoms

Loading sf fails at dyn.load:

Error: package or namespace load failed for 'sf' in dyn.load(file, DLLpath = DLLpath, ...):
 unable to load shared object '/usr/local/lib/R/4.6/site-library/sf/libs/sf.so':
  dlopen(.../sf.so): Library not loaded: /usr/local/opt/x265/lib/libx265.216.dylib
  Referenced from: .../libheif/1.23.1/lib/libheif.1.23.1.dylib
  Reason: tried: '/usr/local/opt/x265/lib/libx265.216.dylib' (no such file), ...

After a naive symlink attempt (see below), the error changes to:

  Symbol not found: _x265_api_get_216
  Referenced from: .../libheif/1.23.1/lib/libheif.1.23.1.dylib
  Expected in:     /usr/local/Cellar/x265/4.3/lib/libx265.217.dylib

Root cause

The dependency chain is:

sf.so  ->  libheif  ->  libx265
  • The installed libheif 1.23.1 was built against x265 API build 216. It hard-links both the file libx265.216.dylib and the versioned symbol x265_api_get_216.
  • A brew upgrade bumped x265 to 4.3, which ships only libx265.217.dylib and exports x265_api_get_217. The .216 file and symbol are gone.
  • Normally you'd just brew reinstall libheif so it rebuilds against x265 4.3. But Homebrew dropped macOS Intel x86_64 support (announced Aug 2025, effective 2026). There are no more Intel bottles, so brew falls back to building from source, and the aom dependency build fails by design. Homebrew even prints: "This build failure was expected, as this is not a Tier 1 configuration."

So on an Intel Mac you cannot rebuild libheif through Homebrew, and the x265 upgrade left a version/ABI gap that breaks everything downstream of libheif.

Why the quick symlink does NOT work

# DON'T rely on this — it only fixes the filename, not the ABI
ln -s /usr/local/opt/x265/lib/libx265.217.dylib /usr/local/opt/x265/lib/libx265.216.dylib

This resolves the missing-file error but then fails with Symbol not found: _x265_api_get_216, because x265 4.3 genuinely renamed the versioned entry point to x265_api_get_217. It's a real API bump, not just a renamed file.


The fix that worked (targeted, reversible)

Restore the old x265 4.2 libx265.216.dylib and place it alongside the current 4.3 library. libheif then loads .216; everything else keeps using .217. Both coexist because they have different version suffixes.

1. Pull the x265 4.2 bottle from Homebrew's ghcr registry

# anonymous pull token
TOKEN=$(curl -s "https://ghcr.io/token?service=ghcr.io&scope=repository:homebrew/core/x265:pull" \
  | sed -E 's/.*"token":"([^"]+)".*/\1/')

# find the x86_64 Sonoma manifest for tag 4.2 (annotation "4.2.sonoma")
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.oci.image.index.v1+json" \
  https://ghcr.io/v2/homebrew/core/x265/manifests/4.2 | python3 -m json.tool | less
# -> pick the digest for "4.2.sonoma" (Intel). At time of writing:
#    sha256:4b91eb72eaba279480c24aba933f36e4cd409318d627212f5812d91320050df1

Adjust the platform tag to match your macOS: sonoma (14, x86_64), ventura, monterey, or the arm64_* variants on Apple Silicon. Digests will differ over time — always read them from the manifest rather than copying the one above.

MAN=sha256:4b91eb72eaba279480c24aba933f36e4cd409318d627212f5812d91320050df1

# get the single layer (the bottle tarball) blob digest
BLOB=$(curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.oci.image.manifest.v1+json" \
  https://ghcr.io/v2/homebrew/core/x265/manifests/$MAN \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['layers'][0]['digest'])")

# download the bottle
curl -s -L -H "Authorization: Bearer $TOKEN" \
  "https://ghcr.io/v2/homebrew/core/x265/blobs/$BLOB" -o /tmp/x265-4.2.tar.gz

2. Extract just the .216 dylib and drop it into place

mkdir -p /tmp/x265extract
tar xzf /tmp/x265-4.2.tar.gz -C /tmp/x265extract x265/4.2/lib/libx265.216.dylib

# sanity check: it exports the symbol libheif needs
nm -gU /tmp/x265extract/x265/4.2/lib/libx265.216.dylib | grep _x265_api_get_216

# place it next to the current 4.3 library
cp /tmp/x265extract/x265/4.2/lib/libx265.216.dylib \
   /usr/local/Cellar/x265/4.3/lib/libx265.216.dylib

3. Pin x265 so a future brew upgrade won't rip it out

brew pin x265

4. Verify in R

library(sf)
sf::sf_extSoftVersion()
#> Linking to GEOS 3.14.1, GDAL 3.13.2, PROJ 9.8.1; sf_use_s2() is TRUE

Done. sf loads again.

Caveats: this is a coexistence hack. If you later brew reinstall x265 or an upgrade touches libheif/gdal, the manually placed .216 dylib may be removed and you'll need to repeat it.


Other ways to fix it

Approach Effort Durability Notes
Drop in old libx265.216.dylib + brew pin x265 (this gist) Low Medium Fastest unblock; survives until something rebuilds the chain
Downgrade the whole x265 formula to 4.2 Low–Med Medium Same idea, coarser; affects every x265 consumer
Reinstall/rebuild libheif against x265 4.3 — — The "correct" fix, but not possible via Homebrew on Intel (source build of aom fails)
Migrate the geospatial stack to conda-forge Medium High Recommended long-term on Intel; see below
Migrate Homebrew stack to MacPorts High High Homebrew's own suggested route for Intel
Move to Apple Silicon / Docker / Linux High High Sidesteps the dropped-platform problem entirely

Recommended long-term: conda-forge (still ships Intel macOS builds)

Install R and r-sf and the C libraries together so everything links against conda-forge:

# Miniforge (conda-forge by default), Intel build
curl -L -o /tmp/miniforge.sh \
  https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-x86_64.sh
bash /tmp/miniforge.sh -b -p "$HOME/miniforge3"
source "$HOME/miniforge3/etc/profile.d/conda.sh"

conda create -n geo -c conda-forge \
  r-base r-sf r-terra r-stars r-tidyverse \
  gdal geos proj
conda activate geo

Then point RStudio at ~/miniforge3/envs/geo/bin/R (Tools → Global Options → General → R version → Change) and restart.

Keep GDAL-dependent R packages coming from conda-forge (conda install -c conda-forge r-<pkg>) rather than install.packages(), to avoid relinking against the wrong system libraries.


TL;DR

sf broke because a Homebrew x265 upgrade (4.2 → 4.3, API build 216 → 217) left libheif pointing at a libx265.216.dylib / x265_api_get_216 that no longer exist, and Homebrew can't rebuild libheif on Intel macOS anymore. Fix: fetch the old x265 4.2 libx265.216.dylib from ghcr, place it next to the 4.3 library, and brew pin x265. For a durable setup on Intel, move the geospatial stack to conda-forge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment