JuiceFS public-client parity
JuiceFS public-client parity
JuiceFS is an independently gated Shared Volume driver. Discover it from
GET /v1/auth/me at features.juicefsVolumesEnabled; do not infer it from
sharedVolumesEnabled. When the global gate is off, the public OpenAPI
document omits the JuiceFS-only operation/reconciliation namespaces and does
not advertise driver: juicefs.
Durable lifecycle contract
Create a JuiceFS volume with type: "shared", driver: "juicefs", a byte
quota (size_gb), and the required logical inode quota (inode_quota). The
public volume projection reports logical bytes/inodes, reconciliation drift,
last reconciliation time, generation, and the write-lock state.
JuiceFS lifecycle mutations require Idempotency-Key; a missing key is a 400
with code: "idempotency_key_required". The caller creates and durably
persists one key before transport I/O, reuses it only to recover that same
request after a lost response, and never reuses it for a changed operation.
The public SDKs, CLI, and MCP never generate a process-local fallback because
it would be lost across a restart or disconnected tool invocation.
The lifecycle endpoints return durable asynchronous operations where relevant.
Read one at GET /v1/volume-operations/{id}?wait_ms=0..5000; terminal states
are succeeded, failed, and cancelled. Client-side wait helpers are
bounded and only poll — they never submit the mutation again.
| Surface | JuiceFS operations |
|---|---|
| TypeScript SDK | volumes.create, resize, attach, detach, delete, usage.reconcile, snapshots.create/restore/delete, operations.get/wait |
| Python SDK | Sync and AsyncPlatinum mirrors: volumes.create, resize, attach, detach, delete, reconcile_usage, snapshot methods, get_operation/wait_operation |
| CLI | pt volume create --driver juicefs --inode-quota ... [--idempotency-key ...] [--wait]; usage reconcile; operations get/wait; snapshots and checkpoints aliases, including restore-fork wait |
| MCP | volume_create, lifecycle/attach tools, checkpoint/restore tools, volume_usage_reconcile, and volume_operation_get/wait |
Checkpoint creation requires the driver’s writer-safety conditions. Restoring a
JuiceFS checkpoint is fork-only: it returns an asynchronous operation and a
target_volume_id; it never overwrites the source volume.
Direct files and Git
The volume file API is object-FUSE only. MCP preflights the volume driver and
refuses direct-file tools unless it is explicitly object_fuse; JuiceFS uses
its POSIX mount workflow instead. This avoids translating POSIX paths into
object operations.
Git workspaces are a second, independently discovered JuiceFS capability:
features.juicefsGitWorkspacesEnabled. When disabled, Git paths and models are
omitted from OpenAPI. When enabled, the SDKs mirror projects, workspaces,
status/diff/log/branches, checkpoints, restore, delete, merge, and
POST /v1/git-projects/{projectId}/usage/reconcile. CLI commands are under
pt volume git; MCP mirrors them as volume_git_* tools. Every Git mutation
uses the same idempotency-key rule as volume lifecycle mutations.
The four Git read endpoints return content only on 200. A bounded host-read timeout or failure is a retryable 503, not a 202 receipt: there is no public command identifier or polling token. TypeScript and Python raise their normal typed 5xx error; CLI and MCP surface the same error. Callers may retry the same read endpoint with their own bounded policy. Public clients never accept or return a repository path, remote URL, gitdir, credential, or storage coordinate.
Advisory locks do not cross the guest boundary
Measured on two hosts, 2026-09-02.
flock and fcntl coordinate correctly between hosts when taken directly on
the JuiceFS mount: one host holds the lock and the other blocks until it is
released (flock -w 3 exits 1). Run the same test from inside two sandboxes on
those same two hosts and both acquire the lock immediately.
host level allen-metal holds -> jang blocked exit=1 correct
guest level sandbox A holds -> sandbox B acquires exit=0 unprotectedThe holder's grip was evidenced, not assumed: it writes a flag file from inside the critical section, and that flag is present when the peer succeeds.
So the gap is not JuiceFS. The virtio-fs export does not carry guest lock
operations through to the backing filesystem, and each sandbox gets its own
virtiofsd, so their lock state is unrelated. Two sandboxes that both take an
advisory lock on the same file will both believe they hold it.
Consequence for callers. Do not rely on advisory locks for mutual exclusion
between sandboxes. Coordinate through something that is genuinely shared —
atomic rename(2), which is proven to work across hosts, or exclusive O_EXCL
creation — or serialise the work outside the sandboxes. This is the same class
of caveat as uncoordinated overlapping writes to one byte range, which are not
merged; the difference is that locking looks like it works.
A checkpoint whose quiesce returns retryable never finishes
Measured on two hosts, 2026-09-02, with writers live on both.
POST /v1/volumes/:id/snapshots dispatched volume.juicefs_checkpoint_quiesce
to the first host holding an RW attachment. The host completed it with
status: retryable (juicefs_completion_receipt_uncertain), which
apps/api/src/api/hosts.ts:2572 records with a finishedAt — terminal for the
command. Nothing re-drives it: exactly one attempt was made, and fifteen
minutes later the operation was still pending, manual_required: false.
host_commands volume.juicefs_checkpoint_quiesce attempts=1 status=retryable
operation kind=checkpoint state=pending manual_required=falseSo an uncertain quiesce leaves the checkpoint hung with no terminal state and no operator signal — it is neither failed, nor uncertain, nor manual_required, and the remaining RW attachments are never quiesced.
It does fail safe: the volume stays fully readable and writable throughout on every attachment, writers are unaffected, and no fence is raised. The cost is a checkpoint that silently never happens.
Until a retry driver exists (or retryable escalates to manual_required after
a bounded number of attempts), treat checkpoint completion as something to poll
and alert on rather than assume.
A held attachment replay leaves the guest writing to its own disk
Measured on two hosts, 2026-09-02. This is the most serious behaviour found.
Restarting platinum-host-agent on a host carrying a live RW attachment:
agent JuiceFS attachment replay held for sandbox sbx_...:
JuiceFS desired attachment device cleanup is uncertain
host virtiofsd count 1 — only the other volume's export was restarted
CP volume_attachments: state=attached, detached_at=NULL (unchanged)
guest df -T /mnt/shared -> /dev/rootThe agent's refusal is correct: it will not re-establish an export whose device cleanup it cannot prove. The damage is what happens around that refusal.
Inside the guest the virtio-fs mount simply disappears, and /mnt/shared
reverts to being an ordinary directory on the sandbox's own root disk.
Writes keep succeeding. They are no longer on the shared volume, no longer
visible to any other sandbox, and are lost when the sandbox is discarded. The
file written before the restart is still intact in the host-side export
directory and still visible to peers on the other host — only this guest has
silently diverged.
Reproduction, end to end:
before A writes durable.bin, md5 cf0dfc6e… B sees the same md5
restart both host agents
after A: md5sum /mnt/shared/durable.bin -> No such file or directory
B: md5sum /mnt/shared/durable.bin -> cf0dfc6e… (unchanged)
A: writes post.txt -> succeeds
B: cat /mnt/shared/post.txt -> No such file or directoryTwo independent gaps:
- The control plane is never told. The attachment stays
attachedwith a nulldetached_at, so nothing reports degraded, nothing re-drives the replay, and a scheduler still treats the host as carrying that attachment. - The guest keeps a writable path at the same location. A mountpoint whose backing export is gone should fail closed — the directory should not remain writable local storage under the volume's name.
Until both are addressed, treat a host-agent restart on a host with live JuiceFS attachments as requiring an explicit re-attach, and do not assume a successful write to a mount path means the data reached the volume.