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

Open
#905 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.