cloudflare / cloudflare/sandbox-sdk
mountBucket on an already-mounted path unmounts the live mount (0.9.4–0.12.9): validation error runs the failed-mount cleanup
- Dominant language
- TypeScript
- Stars
- 1.1k
- Forks
- 114
- Avg merge
- 22h 42m
- Merged PRs (30d)
- 14
Description
## Summary
In `@cloudflare/sandbox` 0.9.4 through 0.12.9, calling `mountBucket()` for a mount path that is already in `activeMounts` throws `InvalidMountConfigError: Mount path "…" is already in use` **and then unmounts the existing, healthy mount**.
`mountBucketFuse` runs `validateMountOptions()` (→ `validateMountPath()`) *inside* the `try`, and its `catch` performs the failed-mount cleanup unconditionally:
```js
} catch (error) {
mountError = error instanceof Error ? error : new Error(String(error));
try {
await this.execInternal(`mountpoint -q ${shellEscape(mountPath)} && fusermount -u ${shellEscape(mountPath)}`);
} catch {}
…
else this.activeMounts.delete(mountPath);
throw error;
}
```
For a validation failure nothing was mounted *by this call*, so the `fusermount -u` tears down the mount a previous call established, and `activeMounts.delete()` forgets it. The next `mountBucket()` then succeeds (the registry is empty), the one after that unmounts again, and so on.
## Reproduction
```ts
const sandbox = getSandbox(env.Sandbox, "demo");
await sandbox.mountBucket("my-bucket", "/data", { endpoint, credentials, s3fsOptions: ["nonempty"] });
await sandbox.exec("mountpoint -q /data"); // exit 0
await sandbox.mountBucket("my-bucket", "/data", { endpoint, credentials }).catch(() => {}); // InvalidMountConfigError
await sandbox.exec("mountpoint -q /data"); // exit 1 — the first mount is gone
```
With `SANDBOX_LOG_LEVEL=debug` the DO log shows `bucket.mount error … already in use` and `sandbox.exec success mountpoint -q '/data' && fusermount -u '/data'` at the same timestamp.
## Impact
Any Worker that treats "already in use" as an idempotent no-op (which the error message and docs suggest is the expected pattern — "Unmount the existing bucket first or use a different mount path") will alternately mount and unmount the path on every other request. Processes started in between run against the bare mount-point directory. We hit this in production-like staging as a "second message in a conversation always fails" bug.
## Versions checked (published tarballs)
| version | cleanup runs on validation error |
|---|---|
| 0.7.21, 0.8.14 | no (`activeMounts.delete` still runs, unmount does not) |
| 0.9.4, 0.10.3, 0.11.0, 0.12.0, 0.12.9 | **yes** |
| 0.13.0-next.751.1 | no — `validateRemoteFuseMount` runs before `runMountAttempt`, so validation errors never reach the cleanup |
## Suggested fix
Validate before entering the block whose `catch` performs cleanup (as `0.13.0-next` already does), or only clean up what this call created. A backport to a 0.12.x patch would help users who cannot move to 0.13 yet. Ideally `unmountBucket()` would also tolerate a stale registry entry whose FUSE mount is already gone (today it throws `BucketUnmountError` from `fusermount -u` and keeps the entry).
## Workaround
Check `mountpoint -q ` via `sandbox.exec()` before calling `mountBucket()`; only mount when it is not mounted. On a typed `InvalidMountConfigError` in that state (stale entry after a container replacement), call `unmountBucket()` best-effort and mount once more.
Contributor guide
Research direction
Start at the mountBucketFuse path described in the issue, tracing validateMountOptions(), validateMountPath(), the cleanup catch, and activeMounts. Compare the validation-before-runMountAttempt behavior noted for 0.13.0-next and add a regression test for mounting the same path twice. Done means the second call reports the validation error without unmounting or deleting the existing mount, and the test confirms the mount remains usable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100