Repository navigation
fix(storage): fall back to sidecar metadata when xattrs are unsupported - #329
Conversation
Storage's file backend keeps object content type, cache control and multipart part etags in extended attributes. Docker Desktop's macOS file sharing rejects them with ENOTSUP, so every upload into a bind-mounted uploads directory failed. Replace the bundled fs-xattr package with a wrapper that delegates to the real addon and, on ENOTSUP, stores values in a JSON sidecar under <storage root>/.slim-xattr. Native misses also consult the sidecar so a directory keeps its metadata when served by an engine with xattrs. The build fails if fs-xattr changes its entry point or exports. The image smoke forces the sidecar path, then re-serves the same volume with native attributes and checks the stored headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes, new object versions and multipart parts never go through fs-xattr, so their sidecars stayed forever. Docker Desktop dogfooding saw 9,026 sidecars for 492 live objects, and small operations slowed down as the single directory grew. Shard sidecars into 256 subdirectories and sweep entries whose file is gone. The sweep never competes with requests: it starts 30 s after the process loads (each wake from idle sleep is a fresh container), repeats hourly, uses async I/O one entry at a time, and pauses after every 50 entries, longer when the filesystem is slow. A cursor resumes a pass cut short by idle sleep, and a short-lived lock keeps one sweeper per directory. The smoke deletes an object in sidecar mode and checks that a restarted container sweeps its sidecar while live metadata survives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgoux
left a comment
There was a problem hiding this comment.
A few non-blocking notes; none of them block merge.
|
|
||
| export function setAttributeSync(file, attr, value) { | ||
| try { | ||
| return callNative(() => native.setAttributeSync(file, attr, value)) |
There was a problem hiding this comment.
Non-blocking: when the native set succeeds, any existing sidecar copy of attr stays as it was, whereas removeAttributeSync clears it (line 138). Regular uploads, copies and TUS always write fresh versioned paths, so object metadata is unaffected. Multipart part-N, though, is rewritten in place:
- Upload part N where xattrs are rejected (sidecar ETag A).
- Re-upload it on an engine with xattrs (native ETag B).
- Complete back on the no-xattr engine: the sidecar answers A, and completion fails with
InvalidChecksum.
That takes two engine switches during one upload, so it's contrived. Still, dropping or updating the sidecar entry after a successful native set would match the remove path.
| batchStarted = Date.now() | ||
| } | ||
| } | ||
| const next = SHARDS[SHARDS.indexOf(shard) + 1] |
There was a problem hiding this comment.
Non-blocking: the cursor only advances once a whole shard is done. A pass that keeps getting cut short partway through one shard restarts that shard on every wake and never reaches the later shards. That only happens if one shard (about 1/256 of all sidecars) takes longer than the time the container stays awake, so only for very large projects. If it ever comes up, saving the last processed entry name within the shard (over a sorted readdir) would fix it.
| } | ||
| const modified = await modifiedAt(lock) | ||
| if (modified !== undefined && Date.now() - modified < SWEEP_LOCK_STALE_MS) return false | ||
| await fsp.rm(lock, { force: true }) |
There was a problem hiding this comment.
Non-blocking: two processes that both see a stale lock can both rm it and continue. The second rm can delete the lock the first one just created, and whichever finishes first then deletes the other's lock in the finally at line 260. Removals are idempotent, so this only duplicates work, which fits the best-effort note above. Just flagging it in case the lock ever needs to guarantee more than that.
| () => undefined | ||
| ) | ||
|
|
||
| // Every upload, copy and multipart part writes a fresh path, so a file that is |
There was a problem hiding this comment.
Nit: not every path is fresh. The Iceberg S3 PutObject route calls backend.uploadObject(icebergBucket, key, undefined, …) (src/http/routes/s3/commands/put-object.ts), so those paths have no version and can be rewritten. Each upload sets both attributes again, so the only exposure is a narrow race: a delete and a re-upload of the same path land between the sweep's stat and its rm, and the sweep removes the new sidecar. It may be worth softening this comment, and the matching "never reused" line in REPORT.md.
| mid-pass leaves it behind, so the resume waits for it to expire. Unreadable entries are left alone. | ||
| `SLIM_STORAGE_XATTR_SWEEP=immediate` skips the delay and interval for the | ||
| smoke. | ||
| - The fallback needs an absolute storage root; a relative one leaves the |
There was a problem hiding this comment.
Nit: the wrapper doesn't reject a relative root. storageRoot() resolves it against process.cwd(), while FileBackend resolves it against __dirname/../../... Those usually differ, so the file falls outside the wrapper's root and the original ENOTSUP is rethrown. Wording like "a relative root is resolved against the working directory, which usually differs from Storage's, so the original ENOTSUP remains" would be more accurate.
…upabase#6921) ## Summary On Docker Desktop for macOS, every upload to a stack's Storage on the Docker runtime failed with 500 `ENOTSUP`. Storage's file backend keeps object metadata in extended attributes, and Docker Desktop's file sharing drops them on bind mounts. This pins the stack's Storage to slim `v1.79.28-r1`, whose `fs-xattr` wrapper falls back to sidecar metadata files when xattrs are rejected. The bind mount at `supabase/.temp/stack-uploads/<stack-id>` stays as it is. This replaces the engine-volume workaround in supabase#6908. ### Before ```mermaid flowchart LR A["Upload to stack Storage<br/>(Docker Desktop macOS)"] --> B["storage v1.73.0<br/>fs-xattr setAttributeSync"] B -->|ENOTSUP on bind mount| C["500 on every upload"] ``` ### After ```mermaid flowchart LR A["Upload to stack Storage<br/>(Docker Desktop macOS)"] --> B["storage v1.79.28-r1<br/>fs-xattr wrapper"] B -->|xattrs work| C["native attributes"] B -->|ENOTSUP| D["sidecar under<br/>stack-uploads/.slim-xattr"] ``` ### What changed - **Storage pin:** `packages/stack/src/Artifacts.ts` moves from `v1.73.0` to `v1.79.28-r1`, pinned by digest. This is the first catalog pin to a revisioned slim release, and the release tag, asset names, native OCI tags and image tag all follow the `v1.79.28-r1` form the catalog already derives. - **Fix location:** the fix lives in the slim build, in supabase/slim-services#329 and supabase/slim-services#330. - Native xattrs stay authoritative wherever they work: Linux, OrbStack and Windows Docker Desktop. - On `ENOTSUP`, metadata goes to `.slim-xattr/<2 hex>/<sha256>.json` under the uploads directory. - A throttled background sweep removes sidecars for deleted objects without blocking requests. - **`supabase services`:** in stack mode it reports catalog versions without the slim revision suffix. A linked project on the same upstream release, for example `v1.79.28`, therefore doesn't show a version mismatch. `SIDE_EFFECTS.md` is updated to match. ### Reviewer notes - The Storage version jump from `v1.73.0` to `v1.79.28` was compared on Docker Desktop against the pinned image: status codes and headers matched across a broad set of Storage API scenarios, both on native volumes and through the sidecar. - Known limitations of the fallback are recorded in slim-services `services/storage/REPORT.md`. - An object uploaded where xattrs work, then read under Docker Desktop, is served as `application/octet-stream`. - A multipart part rewritten across two engine switches within one upload keeps a stale etag. - The legacy `supabase start` Dockerfile pins (`supabase/storage-api:v1.77.0`) are unchanged. It mounts Storage from a named volume, so it isn't affected. ## Linked issue Closes supabase#6900 - [x] The linked issue is **open** and carries the `open-for-contribution` label (or I'm a Supabase maintainer). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [supabase](https://redirect.github.com/supabase/cli) ([source](https://redirect.github.com/supabase/cli/tree/HEAD/apps/cli)) | `2.118.0` → `2.119.0` |  |  | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/10) for more information. --- ### Release Notes <details> <summary>supabase/cli (supabase)</summary> ### [`v2.119.0`](https://redirect.github.com/supabase/cli/releases/tag/v2.119.0) [Compare Source](https://redirect.github.com/supabase/cli/compare/v2.118.0...v2.119.0) ##### Bug Fixes - **cli:** declare the [@​libpg-query/parser](https://redirect.github.com/libpg-query/parser) import plpgsql-deparser leaves undeclared ([#​6827](https://redirect.github.com/supabase/cli/issues/6827)) ([6e98622](https://redirect.github.com/supabase/cli/commit/6e98622c332eaedf99ea1cd0e45833252d9d87bf)), closes [supabase/cli#6822](https://redirect.github.com/supabase/cli/issues/6822) - **cli:** decline unrecognised yes/no prompt answers (CLI-2524) ([#​6910](https://redirect.github.com/supabase/cli/issues/6910)) ([b29e743](https://redirect.github.com/supabase/cli/commit/b29e7437b278ebe8102fd1334c7adb5a1d79f9ef)) - **cli:** keep migra session settings on reconnect (CLI-2522) ([#​6884](https://redirect.github.com/supabase/cli/issues/6884)) ([72b10f8](https://redirect.github.com/supabase/cli/commit/72b10f88c639b43f0f1937416c6d1b3adb54bb6b)) - **cli:** label non-stable builds in the root help description ([#​6868](https://redirect.github.com/supabase/cli/issues/6868)) ([04b1ce9](https://redirect.github.com/supabase/cli/commit/04b1ce9f14a5d46625c42670da158ea4ce792a70)) - **cli:** prefer Docker, then Podman, for automatic stack runtime selection ([#​6859](https://redirect.github.com/supabase/cli/issues/6859)) ([1ef5bc0](https://redirect.github.com/supabase/cli/commit/1ef5bc05a99829bfc60e1f48ff17b70458cedf14)) - **cli:** read request bodies before local functions respond ([#​6830](https://redirect.github.com/supabase/cli/issues/6830)) ([f70cda5](https://redirect.github.com/supabase/cli/commit/f70cda594448d35865549dcdc41ca16316bd8f37)), closes [#​6564](https://redirect.github.com/supabase/cli/issues/6564) - **cli:** reject `test new` names that escape supabase/tests (CLI-2480) ([#​6790](https://redirect.github.com/supabase/cli/issues/6790)) ([c877778](https://redirect.github.com/supabase/cli/commit/c877778f3394af39db88e75e1ed5225206e049ed)) - **cli:** show stack connection details on start and status (CLI-2546, CLI-2531, CLI-2530) ([#​6894](https://redirect.github.com/supabase/cli/issues/6894)) ([288edc2](https://redirect.github.com/supabase/cli/commit/288edc26628844b6a09c378a126a200bcd2adeae)) - **cli:** skip the contrib\_regression clone on pg-delta shadows ([#​6920](https://redirect.github.com/supabase/cli/issues/6920)) ([3704383](https://redirect.github.com/supabase/cli/commit/37043833af5d913ee233d76cd9fa63760c43063c)) - **cli:** state stack destroy --yes instead of self-answering prompt ([#​6903](https://redirect.github.com/supabase/cli/issues/6903)) ([63a2ff5](https://redirect.github.com/supabase/cli/commit/63a2ff5d8577df9a4625db6be251186eecd4abc7)), closes [supabase/cli#6889](https://redirect.github.com/supabase/cli/issues/6889) - **cli:** stop splitting escape strings on escaped quotes (CLI-2537) ([#​6906](https://redirect.github.com/supabase/cli/issues/6906)) ([233d145](https://redirect.github.com/supabase/cli/commit/233d1459e22ad14be9104ceb1254a7c3fe11b84d)) - **cli:** tidy stack start and non-TTY output friction ([#​6889](https://redirect.github.com/supabase/cli/issues/6889)) ([b8fae81](https://redirect.github.com/supabase/cli/commit/b8fae815ea95b5e298a02c2a97335f17a3652b03)) - **cli:** treat the stack database --db-url as local ([#​6904](https://redirect.github.com/supabase/cli/issues/6904)) ([2d7609d](https://redirect.github.com/supabase/cli/commit/2d7609dd9d655ad742be557819e923432b2e1244)) - **config:** ignore unset sms provider (CLI-2463) ([#​6685](https://redirect.github.com/supabase/cli/issues/6685)) ([eb0c3ff](https://redirect.github.com/supabase/cli/commit/eb0c3ff6a70e144e33173558130a900a81adc450)) - **stack:** clean up stacks when Docker is unavailable on start and destroy ([#​6825](https://redirect.github.com/supabase/cli/issues/6825)) ([e836519](https://redirect.github.com/supabase/cli/commit/e83651930de7f365b8e3d9e8800cf5f975a56177)) - **stack:** connect Studio to the database and restore its settings ([#​6865](https://redirect.github.com/supabase/cli/issues/6865)) ([a883019](https://redirect.github.com/supabase/cli/commit/a8830191962ac9de72accae701880b3bd537e012)) - **stack:** give each Mailpit instance its own database ([#​6901](https://redirect.github.com/supabase/cli/issues/6901)) ([2b13026](https://redirect.github.com/supabase/cli/commit/2b13026196177a2766bc8bca2fd4fd41435dca11)) - **stack:** group database helper containers with their stack ([#​6870](https://redirect.github.com/supabase/cli/issues/6870)) ([2547ce5](https://redirect.github.com/supabase/cli/commit/2547ce5d4b6d826c2199bf46a5217d8da38752a5)), closes [supabase/cli#6867](https://redirect.github.com/supabase/cli/issues/6867) [supabase/cli#6867](https://redirect.github.com/supabase/cli/issues/6867) - **stack:** harden readiness, port claims, and container storage ([#​6835](https://redirect.github.com/supabase/cli/issues/6835)) ([2c006dd](https://redirect.github.com/supabase/cli/commit/2c006dd11a22faf6530054da8885e08e5ce30a6d)), closes [#​6834](https://redirect.github.com/supabase/cli/issues/6834) [#​6836](https://redirect.github.com/supabase/cli/issues/6836) [#​6837](https://redirect.github.com/supabase/cli/issues/6837) [#​6838](https://redirect.github.com/supabase/cli/issues/6838) [#​6839](https://redirect.github.com/supabase/cli/issues/6839) [#​6840](https://redirect.github.com/supabase/cli/issues/6840) [#​6841](https://redirect.github.com/supabase/cli/issues/6841) - **stack:** keep in-progress artifact extractions safe from the leftover reaper ([#​6881](https://redirect.github.com/supabase/cli/issues/6881)) ([98cda96](https://redirect.github.com/supabase/cli/commit/98cda96f069fbceb2748beba11660bca61b4efe4)) - **stack:** keep in-use cached artifacts when a request needs more paths ([#​6882](https://redirect.github.com/supabase/cli/issues/6882)) ([e49b791](https://redirect.github.com/supabase/cli/commit/e49b7913be8b4b62ba8cefc86ce59c76c02b0678)) - **stack:** keep Storage uploads working on Docker Desktop for macOS ([#​6921](https://redirect.github.com/supabase/cli/issues/6921)) ([a0711c0](https://redirect.github.com/supabase/cli/commit/a0711c0c0872c4de7404eb47f0fd30175be7c67d)), closes [supabase/slim-services#329](https://redirect.github.com/supabase/slim-services/issues/329) [#​6900](https://redirect.github.com/supabase/cli/issues/6900) - **stack:** keep Studio awake for 5 minutes after its last request ([#​6866](https://redirect.github.com/supabase/cli/issues/6866)) ([66e92c7](https://redirect.github.com/supabase/cli/commit/66e92c71899da7b2cc1fdc53bae7d1fed972bf55)) - **stack:** load function deno.json files as Deno config ([#​6907](https://redirect.github.com/supabase/cli/issues/6907)) ([4371ed3](https://redirect.github.com/supabase/cli/commit/4371ed369d5f7f806e6905e0ec9ba79eb831c78b)) - **stack:** name containers by project and service ([#​6867](https://redirect.github.com/supabase/cli/issues/6867)) ([315dbcd](https://redirect.github.com/supabase/cli/commit/315dbcd13f810547a206b444ea3dcbad058e3357)) - **stack:** open a fresh upstream connection per proxied request ([#​6897](https://redirect.github.com/supabase/cli/issues/6897)) ([a1be13e](https://redirect.github.com/supabase/cli/commit/a1be13e80573a0eac2ce13713dc2501a1f27c2fd)) - **stack:** reach stack services from Windows and Docker Desktop containers ([#​6887](https://redirect.github.com/supabase/cli/issues/6887)) ([87d9efb](https://redirect.github.com/supabase/cli/commit/87d9efbb28f13a7093c82c2ced0a4ff5fc3090dd)) - **stack:** reduce native stack friction in agent sandboxes ([#​6856](https://redirect.github.com/supabase/cli/issues/6856)) ([ff14e91](https://redirect.github.com/supabase/cli/commit/ff14e91a715b0beb248a5119468ff5483f91f744)) - **stack:** release log followers on exit (CLI-2521) ([#​6861](https://redirect.github.com/supabase/cli/issues/6861)) ([621768f](https://redirect.github.com/supabase/cli/commit/621768f7918c3b04f467ce45bbb4d680af4aaf25)), closes [#​6594](https://redirect.github.com/supabase/cli/issues/6594) - **stack:** require passwords on the native database socket (CLI-2503) ([#​6833](https://redirect.github.com/supabase/cli/issues/6833)) ([b8026df](https://redirect.github.com/supabase/cli/commit/b8026df5ea50d49bc06dcfa8ab877e5f92c032a9)) - **stack:** retry image pulls after transient registry failures ([#​6879](https://redirect.github.com/supabase/cli/issues/6879)) ([ed05cb8](https://redirect.github.com/supabase/cli/commit/ed05cb84d7d340a1e38f3cda195f33d92a1c26d7)) - **stack:** reuse keep-alive upstream connections in the HTTP gateway ([#​6925](https://redirect.github.com/supabase/cli/issues/6925)) ([985e0ae](https://redirect.github.com/supabase/cli/commit/985e0ae9e9e9f364630a00dc45ea300f5f5019d8)), closes [#​6897](https://redirect.github.com/supabase/cli/issues/6897) [#​6922](https://redirect.github.com/supabase/cli/issues/6922) - **stack:** serve Storage S3 and resumable uploads behind the gateway ([#​6917](https://redirect.github.com/supabase/cli/issues/6917)) ([481cb3f](https://redirect.github.com/supabase/cli/commit/481cb3f43650c093be29147b32e685e5b4b0bf77)), closes [#​6900](https://redirect.github.com/supabase/cli/issues/6900) [#​6916](https://redirect.github.com/supabase/cli/issues/6916) - **stack:** start auth with zero session limits (CLI-2506) ([#​6848](https://redirect.github.com/supabase/cli/issues/6848)) ([643d648](https://redirect.github.com/supabase/cli/commit/643d648202ffcc71ed2fad95c17b92e822bcc205)) - **stack:** stop Postgres containers with a fast shutdown ([#​6834](https://redirect.github.com/supabase/cli/issues/6834)) ([e03ea7c](https://redirect.github.com/supabase/cli/commit/e03ea7c59335ac5979f3cf1926b8495f772ec642)), closes [#​6835](https://redirect.github.com/supabase/cli/issues/6835) [#​6836](https://redirect.github.com/supabase/cli/issues/6836) [#​6837](https://redirect.github.com/supabase/cli/issues/6837) [#​6838](https://redirect.github.com/supabase/cli/issues/6838) [#​6839](https://redirect.github.com/supabase/cli/issues/6839) [#​6840](https://redirect.github.com/supabase/cli/issues/6840) [#​6841](https://redirect.github.com/supabase/cli/issues/6841) - **stack:** upgrade edge-runtime to v1.77.1 ([#​6844](https://redirect.github.com/supabase/cli/issues/6844)) ([e29a712](https://redirect.github.com/supabase/cli/commit/e29a71245659d7b9bd5b27197cb8611704cd368a)) ##### Features - **cli:** drive gen types languages from the [@​supabase/typegen](https://redirect.github.com/supabase/typegen) registry (SDK-1956) ([#​6822](https://redirect.github.com/supabase/cli/issues/6822)) ([d021886](https://redirect.github.com/supabase/cli/commit/d021886ede50ff8b59b50950da4f7393e2d7fcef)), closes [supabase/sdk#231](https://redirect.github.com/supabase/sdk/issues/231) [supabase/sdk#233](https://redirect.github.com/supabase/sdk/issues/233) - **cli:** graduate OrioleDB configuration from experimental ([#​6828](https://redirect.github.com/supabase/cli/issues/6828)) ([48cabe3](https://redirect.github.com/supabase/cli/commit/48cabe354c16ce4c67199c4b46bbdca4265d17ae)) - **cli:** report stack MCP at the API URL and include connection env in JSON (CLI-2546) ([#​6914](https://redirect.github.com/supabase/cli/issues/6914)) ([26dc09a](https://redirect.github.com/supabase/cli/commit/26dc09a3245055d71352888d1d8f93beceedafc3)), closes [#​6894](https://redirect.github.com/supabase/cli/issues/6894) [#​6898](https://redirect.github.com/supabase/cli/issues/6898) - **cli:** upgrade pg-delta to 1.0.0-alpha.56 ([#​6913](https://redirect.github.com/supabase/cli/issues/6913)) ([8a7963e](https://redirect.github.com/supabase/cli/commit/8a7963e31241d7bc947c33a22cfac2459ddeb01d)) - **stack:** add a testing entrypoint and derive the Promise API ([#​6841](https://redirect.github.com/supabase/cli/issues/6841)) ([7894283](https://redirect.github.com/supabase/cli/commit/7894283333a1c706f43865cb516235779ceb50a2)), closes [#​6834](https://redirect.github.com/supabase/cli/issues/6834) [#​6835](https://redirect.github.com/supabase/cli/issues/6835) [#​6836](https://redirect.github.com/supabase/cli/issues/6836) [#​6837](https://redirect.github.com/supabase/cli/issues/6837) [#​6838](https://redirect.github.com/supabase/cli/issues/6838) [#​6839](https://redirect.github.com/supabase/cli/issues/6839) [#​6840](https://redirect.github.com/supabase/cli/issues/6840) - **stack:** lease owners with a lock and bind session stacks to creators ([#​6838](https://redirect.github.com/supabase/cli/issues/6838)) ([dfc52e6](https://redirect.github.com/supabase/cli/commit/dfc52e6ac7b67918bdc38e3b986e3b0a1568eb6f)), closes [#​6834](https://redirect.github.com/supabase/cli/issues/6834) [#​6835](https://redirect.github.com/supabase/cli/issues/6835) [#​6836](https://redirect.github.com/supabase/cli/issues/6836) [#​6837](https://redirect.github.com/supabase/cli/issues/6837) [#​6839](https://redirect.github.com/supabase/cli/issues/6839) [#​6840](https://redirect.github.com/supabase/cli/issues/6840) [#​6841](https://redirect.github.com/supabase/cli/issues/6841) - **stack:** pin slim artifacts by revision and make the catalog the single version table ([#​6883](https://redirect.github.com/supabase/cli/issues/6883)) ([62aac0f](https://redirect.github.com/supabase/cli/commit/62aac0ff87aac7651f2066ecd5da2fe8078ca591)), closes [supabase/slim-services#326](https://redirect.github.com/supabase/slim-services/issues/326) [#​328](https://redirect.github.com/supabase/cli/issues/328) - **stack:** serve Studio's MCP server at /mcp on the API listener (CLI-2546) ([#​6898](https://redirect.github.com/supabase/cli/issues/6898)) ([8ae22bc](https://redirect.github.com/supabase/cli/commit/8ae22bcba2fbe5b9e5de2c42c54bf332463f059a)), closes [#​6894](https://redirect.github.com/supabase/cli/issues/6894) - **stack:** ship the functions bootstrap with the package ([#​6839](https://redirect.github.com/supabase/cli/issues/6839)) ([7cd1a79](https://redirect.github.com/supabase/cli/commit/7cd1a79d0581f4160342913db94bfb0d8f1f3400)), closes [#​6834](https://redirect.github.com/supabase/cli/issues/6834) [#​6835](https://redirect.github.com/supabase/cli/issues/6835) [#​6836](https://redirect.github.com/supabase/cli/issues/6836) [#​6837](https://redirect.github.com/supabase/cli/issues/6837) [#​6838](https://redirect.github.com/supabase/cli/issues/6838) [#​6840](https://redirect.github.com/supabase/cli/issues/6840) [#​6841](https://redirect.github.com/supabase/cli/issues/6841) ##### Performance Improvements - **cli:** initialize catalog schemas without temporary servers ([#​6831](https://redirect.github.com/supabase/cli/issues/6831)) ([257bee9](https://redirect.github.com/supabase/cli/commit/257bee9bce93c096a6e7f9ba00fded141fd751ac)) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/paulthvt/loomia). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
TL;DR
On Docker Desktop for macOS, every upload to the CLI stack's Storage returned 500
ENOTSUP. Storage's file backend keeps object metadata in extended attributes, and Docker Desktop's bind mounts reject them. The slim Storage build now wrapsfs-xattr: it uses native xattrs when they work, and otherwise falls back to JSON sidecar files, which a throttled background sweep keeps from piling up. The CLI can keep its bind mount, with no engine-volume workaround.Before
After
Why
Docker Desktop mounts host directories over virtiofs with
no_xattr. The stack bind-mountssupabase/.temp/stack-uploads/<stack-id>to/mnt, and Storage'ssetAttributeSyncfails:The CLI-side alternative, supabase/cli#6908, moves uploads into engine volumes. It needs a probe container, volume cleanup on destroy and new prompt wording, and uploads disappear when switching Docker contexts. This PR fixes it where the failure happens instead.
What changed
fs-xattrwrapper (services/storage/overlay/fs-xattr/index.js):prepare-bundle-dist.mjsmoves the real package tonode_modules/fs-xattr/nativeand installs the wrapper in its place.ENOTSUP, values go to<storage root>/.slim-xattr/<2 hex>/<sha256(relative path)>.json, which also records the relative path.fs-xattrchanges its entry point or adds an export the wrapper doesn't cover.fs-xattr, so their sidecars would stay forever..tmpfiles..sweep-cursorlets a pass cut short by idle sleep resume on the next start..sweep-lockkeeps one sweeper per directory; it's refreshed per batch and expires after 2 minutes.services/storage/smoke.sh):SLIM_STORAGE_XATTR_SIDECAR=forcemakes a container act as if xattrs were rejected. It uploads with a content type and cache control, checks the headers and the sidecar, then deletes an object.SLIM_STORAGE_XATTR_SWEEP=immediate).services/storage/fs-xattr-fallback.test.ts(bun, fake native module), added to repository checks.services/storage/REPORT.md.Reviewer notes
validation_onlyrelease runs for v1.79.27 (unpublished) and v1.79.21 (floor) on linux/amd64, linux/arm64 and darwin/arm64.v1.73.0) failed withENOTSUP; the candidate passed.whole-stack.docker.lifecycle.e2e.test.ts) passed..handoff/storage-xattr/on supabase/cli branchavallete/stack-storage-xattr-sidecar-test.drvfswithmetadata), so the fallback never triggers there.application/octet-stream.supabase/storage-apiimage reading a fallback directory servesapplication/octet-stream/no-cache.v1.73.0, below this repo's floorv1.79.21, so the CLI needs a pin bump to a fixed version to benefit.hotfix=truefor any published v1.79.21–26 that should carry the fallback.Part of supabase/cli#6900
🤖 Generated with Claude Code