◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-10-22Intermediate

A Video Fails to Save with "Not Enough Space" — Check Whether the File Is Over 2GB Before Blaming Storage

On Android, a 2.2GB video won't save to the gallery and the error says "Not enough space", even with plenty of free storage. I read the public Expo issue and reproduced the same boundary on Linux. Here is a size check to add before saving, and the idea of copying in pieces.

Expo225expo-media-library4Android59large filestroubleshooting71

Short videos save to the gallery just fine, but the long ones never do. The error says there isn't enough space, and the phone has tens of gigabytes free. I came across a report like this in a public Expo issue, and it took me straight back to an afternoon of my own.

As an indie developer who has written image-saving code many times, I once spent half a day staring at the free-space readout because the error message told me to. That is why this issue caught my attention.

What I'd like to say first is that "not enough space" doesn't always mean storage. Below I summarize the report, confirm the boundary number by running it myself, and leave a few lines you can add to check size before saving.

What is reported

In expo/expo issue #47767, createAssetAsync from expo-media-library fails on Android for large files. The environment listed is expo ~55.0.27, expo-media-library ~55.0.18 and react-native 0.83.6. iOS is said to be unaffected.

The error is "Unable to copy file into external storage.", with "Not enough space" attached as the cause. The reporter generated dummy mp4 files in the app cache and got these results:

File sizeResult
1.00 GBSaved
2.10 GBSaved
2.20 GBFailed
4.00 GBFailed

The reporter says free storage was sufficient, so the boundary lives in the size of the file, not in the device. As of my check on 2026-10-22 the issue was still open, and a maintainer had confirmed the root cause on main.

Where the boundary number comes from

The reporter's reading is that the save routine calls FileChannel.transferTo once and never loops. On Linux, a single sendfile call moves at most 2,147,479,552 bytes. If one call can't finish the job, the remainder is treated as a failed write, the half-created entry is deleted, and the message says there isn't enough space.

I can't reproduce Android's internals here. What I could check is whether the same cap exists in the same underlying mechanism (sendfile) on Linux. I created a 2.2GB (2,306,867,200-byte) sparse file and called it once:

import os
 
size = os.path.getsize("src")          # 2,306,867,200 bytes
s = os.open("src", os.O_RDONLY)
d = os.open("dst1", os.O_WRONLY | os.O_CREAT)
 
n = os.sendfile(d, s, 0, size)         # one call only
print(n, size - n)
# 2147479552 159387648

Exactly 2,147,479,552 bytes were moved, and 159,387,648 bytes were left over — 4,095 bytes short of the Int32 maximum (2,147,483,647). That puts the boundary in the same place as the issue's "2.10GB passes, 2.20GB doesn't".

Then I changed it to repeat until nothing remained:

d = os.open("dst2", os.O_WRONLY | os.O_CREAT)
off = 0
while off < size:
    n = os.sendfile(d, s, off, size - off)
    if n == 0:                          # no progress: treat as a real failure
        raise RuntimeError("no progress")
    off += n
print(off == size)                      # True (finished in two calls)

Two calls moved all 2.2GB. The fix suggested in the issue follows the same idea: don't assume one call carries everything; advance by what was moved, and treat zero progress as the only real failure.

What the app can do in the meantime

While the library gets fixed, there are two things you can do. Both just give you more clues.

First, check the size before saving and report it with a different message:

import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library";
 
// About 2GB, placed a little below the boundary reported in the issue
const SAFE_LIMIT_BYTES = 2_000_000_000;
 
export async function saveVideoToGallery(uri: string) {
  const info = await FileSystem.getInfoAsync(uri);
  if (!info.exists) {
    throw new Error("The video to save was not found");
  }
  if (info.size > SAFE_LIMIT_BYTES) {
    // Tell the user this is not about free storage
    throw new Error(
      "This video is too large to save to the gallery. Please save it in shorter pieces."
    );
  }
  return MediaLibrary.createAssetAsync(uri);
}

I haven't reproduced the 2GB failure of createAssetAsync on a physical device, so treat this snippet as a sketch of the flow, and the limit as a rule of thumb set just under the reported boundary. Please confirm on a real device with your SDK version.

Second, keep any single recording from growing that large: split long recordings into fixed-length segments and you never get near this boundary.

The order I check things in

When "not enough space" appears, I now look in this order:

  1. Confirm the failing file's size in bytes
  2. Try saving a smaller file with similar content
  3. If small ones pass and only big ones fail, suspect a size boundary rather than storage

Free space comes after that. An error message only points at the cause its author imagined.

If you're stuck on permissions or albums instead, see media-save permission scopes and album design and fixing saves to the device library.

Read the error, then check the number yourself. The next time a save fails, I'd suggest starting by printing the file size in bytes. That is where I start now, too.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-22
Four Numbers to Record Before R8 Becomes the Default — Android Measurements While We Wait for Expo SDK 58
Expo SDK 58 turns on R8 by default for Android release builds. Here is how I capture four numbers — download size, DEX total, cold start, and build time — so the before and after can be compared with the same ruler.
⬡ Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.