Skip to content

fix: prevent concurrent R2 mount attempts causing s3fs passwd file co…#202

Open
piffie wants to merge 16 commits intocloudflare:mainfrom
mc2ventures:claude/fix-r2-mount-upstream-SrDsd
Open

fix: prevent concurrent R2 mount attempts causing s3fs passwd file co…#202
piffie wants to merge 16 commits intocloudflare:mainfrom
mc2ventures:claude/fix-r2-mount-upstream-SrDsd

Conversation

@piffie
Copy link

@piffie piffie commented Feb 8, 2026

fixes #194

Multiple concurrent requests (e.g. the loading-page waitUntil + the next polling request) can both call mountR2Storage before the first one finishes. Each call to sandbox.mountBucket() appends credentials to the s3fs passwd file, so concurrent calls produce duplicate entries and s3fs refuses to mount with: "there are multiple entries for the same bucket(default) in the passwd file."

This adds a module-level in-flight promise that coalesces concurrent mount calls: only the first caller actually attempts the mount, while subsequent callers await the same promise. The lock is released in a finally block so retries are possible after failures.

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo

piffie and others added 8 commits February 3, 2026 23:39
…nflict

Multiple concurrent requests (e.g. the loading-page waitUntil + the next
polling request) can both call mountR2Storage before the first one finishes.
Each call to sandbox.mountBucket() appends credentials to the s3fs passwd
file, so concurrent calls produce duplicate entries and s3fs refuses to
mount with: "there are multiple entries for the same bucket(default) in
the passwd file."

This adds a module-level in-flight promise that coalesces concurrent mount
calls: only the first caller actually attempts the mount, while subsequent
callers await the same promise. The lock is released in a finally block so
retries are possible after failures.

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo
fix: prevent concurrent R2 mount attempts causing s3fs passwd file co…
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from 6415c54 to fb977a8 Compare February 8, 2026 13:09
Two issues caused the "multiple entries for the same bucket(default) in
the passwd file" s3fs error:

1. sandbox.mountBucket() appends credentials to the s3fs passwd file on
   every call.  Because the container persists across Worker invocations
   (keepAlive / sleepAfter), stale entries from previous mounts accumulate
   and s3fs refuses to mount.  Fix: clear /etc/passwd-s3fs and
   ~/.passwd-s3fs before each mount attempt.

2. Concurrent requests (e.g. the loading-page waitUntil + the next polling
   request) can both call mountR2Storage before the first one finishes,
   producing parallel mountBucket() calls that each append entries.
   Fix: coalesce concurrent callers behind a single in-flight promise.

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from fb977a8 to ae6e1b3 Compare February 8, 2026 13:15
…rDsd

fix: prevent duplicate s3fs passwd entries on R2 mount
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from ae6e1b3 to a0fe7c5 Compare February 8, 2026 13:57
sandbox.mountBucket() appends credentials to the s3fs passwd file each
time it is called.  Because the container persists across Worker
invocations (keepAlive / sleepAfter), entries accumulate and s3fs refuses
to mount with "there are multiple entries for the same bucket(default)
in the passwd file."

Three-layer fix:

1. Inflight lock — coalesces concurrent callers within the same Worker
   isolate behind a single in-flight promise, preventing parallel
   mountBucket() calls.

2. Pre-mount deduplication — runs `sort -u` on s3fs passwd files inside
   the container before calling mountBucket(), so even if mountBucket
   appends a duplicate entry it starts from a single-entry (or empty)
   state.

3. Retry on failure — when mountBucket fails with "multiple entries",
   deduplicates the passwd file again and retries the s3fs mount directly
   inside the container (without calling mountBucket again, which would
   append yet another entry).

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from a0fe7c5 to 894f1df Compare February 8, 2026 14:05
…rDsd

fix: prevent duplicate s3fs passwd entries on R2 mount
@travisirby
Copy link

nice!

@travisirby
Copy link

validated this resolved the issue on my deployment thanks!

@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from 894f1df to 08ce56d Compare February 8, 2026 15:18
…uplication

sandbox.mountBucket() manages the s3fs passwd file from the orchestration
layer outside the container and appends a new credential entry on every
call.  Because the container persists across Worker invocations the entries
accumulate and s3fs refuses to mount with "multiple entries for the same
bucket(default) in the passwd file."  In-container cleanup (rm, sort -u)
cannot reach the orchestration-layer file.

Replace mountBucket() with direct s3fs mounting inside the container,
following the pattern from the Cloudflare Containers FUSE-mount docs:

1. Write credentials to /etc/passwd-s3fs via startProcess (overwrite, not
   append — always exactly one entry)
2. Run s3fs directly inside the container
3. Verify the mount succeeded

Credentials are passed via process env vars (R2_KEY, R2_SECRET) to avoid
embedding secrets in the command string.

The in-flight promise lock is retained to coalesce concurrent callers
within the same Worker isolate.

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from 08ce56d to 20c60fb Compare February 8, 2026 15:24
…rDsd

fix: replace mountBucket() with direct s3fs mount to prevent passwd d…
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from 20c60fb to a36283d Compare February 8, 2026 16:23
sandbox.startProcess() does not support the env option, so the previous
approach of passing R2_KEY/R2_SECRET as process env vars resulted in an
empty passwd file (:) that s3fs could not parse.

Base64-encode the credentials in TypeScript and decode inside the
container instead.

https://claude.ai/code/session_01E5t9gPHDGGrTUWeagkDjVo
@piffie piffie force-pushed the claude/fix-r2-mount-upstream-SrDsd branch from a36283d to a1f37dd Compare February 8, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants