Repository navigation
Storage: disk usage and free space in Settings, a header warning, and refusing writes when full - #35
Merged
Merged
Conversation
Settings gains a Storage panel, and the header a drive icon that appears only when free space is low. Everyone sees their own library: totals by type, previews, and their ten largest files as links, which is the quickest way to find something to delete. Admins (ADMIN_USERS) also see the server: - one bar per disk, split into GAM media, GAM database, everything else, the filesystem's reserve and free space; inside Docker these are the host's numbers, through the data/ bind mount - stray files: files under data/media that no asset refers to, older than a day, with a button that removes them through storage.delete Below DISK_RESERVE_GB (default 5) uploads, URL imports, generations and sub-video extractions are refused with 507 disk_full. They are checked when queued and again when the job starts. A generation is checked before it is sent to fal, so nothing is billed for an output that cannot be saved, and a request already submitted still saves. An upload stores the files that fit and rejects the rest. DISK_WARN_FREE_GB (default 20) sets the header warning. Both are fixed sizes because single large files are what fill the disk. The container's temp directory is shown when it is a separate disk, but does not count toward the level: nothing persistent lives there, and on a dev machine it is often a small tmpfs that would otherwise block every upload. Tests run against a fake disk (conftest's autouse `disk`), so the suite no longer depends on how full the machine running it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ev6jNsXYxKEPPN6EpqpTE
davior
marked this pull request as ready for review
October 12, 2026 12:45
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.
What this adds
GAM couldn't show how much disk the library uses or how much is left, and nothing stopped writes before the disk filled. A full disk fails an import halfway, leaves
.partialfiles behind, and can fail a SQLite commit.Settings → Storage (new panel, after "What enrichment has cost")
Video · 34 files · 120 GB), previews, and their 10 largest files. Each of those links to its asset page.ADMIN_USERS) also see the server:data/bind mount.data/mediathat nostorage_key/thumb_keyrefers to and that are more than 24 h old. The age limit exists because every write path puts the file on disk before it saves the row. Remove them asks first, then deletes throughstorage.delete.Header. A drive icon appears only when free space is low: amber below
DISK_WARN_FREE_GB(20), red belowDISK_RESERVE_GB(5). It links to/settings#storage. Non-admins get the level but not the numbers.Refusing writes. Below the reserve, uploads, URL imports, generations, sub-video extractions and promotions return 507
disk_full.fal.submit, so nothing is billed for an output that can't be saved. A request already submitted still saves.statvfscan't be read.The container's temp directory is shown when it's on a separate disk, but doesn't count toward the level. Nothing persistent lives there, and on a dev machine it's often a small tmpfs that would otherwise block every upload.
Files
backend/app/storage/capacity.py: volumes (merged by device), levels,has_room/require_room.backend/app/services/storage_usage.py,routers/storage.py,schemas_storage.py:GET /api/storage,GET /api/storage/status,POST /api/storage/cleanup(admin only).LocalStorage.files(): walks exactly<owner>/<name>and doesn't follow symlinks.routers/{assets,imports,generate,clips}.py,enrichment/{import_url,extract_subvideo}.py,generation/run.py.api/storage.ts,StoragePanel.tsx,StorageWarning.tsx.DISK_WARN_FREE_GBandDISK_RESERVE_GBinconfig.py,docker-compose.ymland.env.example. There's a new "Disk space" section indocs/deployment.md.Testing
pytest -q: 1247 passed (43 new). The new tests aretest_storage_usage.py, listing tests intest_storage.py, and job-level disk-full tests in the generation, import and clip suites. The generation test asserts fal is never called.diskfixture fakesstatvfs, so the suite no longer depends on how full the runner is.npm run lint,format:check,npm test(393 passed, 16 new) andnpm run buildare all clean.df -B1anddu -sbbyte for byte.🤖 Generated with Claude Code
https://claude.ai/code/session_017Ev6jNsXYxKEPPN6EpqpTE
Generated by Claude Code