
This is a beta release of ArtifactFS. Your mileage may vary.
ArtifactFS is a Git-backed filesystem daemon (FUSE driver) in Go that mounts repositories as normal working trees while avoiding eager blob downloads.
It exposes the tree quickly, then hydrates file contents on demand. That makes it useful for sandboxes, agents, and other short-lived environments where waiting for a full clone is too expensive.
In practice:
Cloudflare Artifacts is a versioned filesystem that speaks git. It is built for agent toolchains, sandboxes, and CI/CD systems that need fast access to code repositories.
ArtifactFS is the optional FUSE driver -- it lets you mount an Artifact (or any git repo) as a local filesystem without waiting for a full clone.
Requires Go 1.25+ and a FUSE implementation:
fuse3 (apt install fuse3 on Debian/Ubuntu, dnf install fuse3 on Fedora)Install the CLI from the module:
go install github.com/cloudflare/artifact-fs/cmd/artifact-fs@latest
Or build it directly from the module path:
go build -o artifact-fs github.com/cloudflare/artifact-fs/cmd/artifact-fs
Quick start against a public repo:
export ARTIFACT_FS_ROOT=/tmp/artifact-fs-test
# Register, clone, and build the initial snapshot
./artifact-fs add-repo \
--name workers-sdk \
--remote https://github.com/cloudflare/workers-sdk.git \
--branch main \
--mount-root /tmp
# Start the daemon (mounts via FUSE, blocks until killed)
./artifact-fs daemon --root /tmp &
DAEMON_PID=$!
# Use the repo
ls /tmp/workers-sdk/
cat /tmp/workers-sdk/README.md
git -C /tmp/workers-sdk log --oneline -5
# Cleanup
kill $DAEMON_PID
Check the state of a mounted repo with status:
./artifact-fs status --name workers-sdk
# repo=workers-sdk state=mounted head=d4c61587... ref=main ahead=0 behind=0 diverged=false last_fetch=2026-03-27T12:00:00Z result=ok overlay_dirty=false
| Field | Meaning |
|---|---|
state |
mounted or unmounted |
head |
Current HEAD commit OID |
ref |
Tracked branch |
ahead / behind |
Commits ahead/behind the remote tracking branch |
overlay_dirty |
true if there are local writes (created, modified, or deleted files) |
last_fetch / result |
Timestamp and outcome of the last background fetch |
Hydration (blob downloading) is transparent: the file tree is visible immediately after mount, and reads block only until the requested blob is fetched. The daemon prioritizes code and manifests (package.json, go.mod, README.md) over binary files.
To monitor hydration activity, watch the daemon's JSON log output:
./artifact-fs daemon --root /tmp 2>/tmp/daemon.log &
# In another terminal:
tail -f /tmp/daemon.log | grep -i hydrat
Use --hydration-concurrency to control the number of parallel blob-fetch workers (default 4). Each worker maintains a persistent git cat-file --batch process, so higher values trade memory for faster bulk hydration:
./artifact-fs daemon --root /tmp --hydration-concurrency 8
By default, add-repo waits for the blobless clone and initial snapshot before returning. Use --async when the daemon should prepare the repo in the background:
./artifact-fs add-repo \
--name workers-sdk \
--remote https://github.com/cloudflare/workers-sdk.git \
--branch main \
--mount-root /tmp \
--async
The daemon mounts a placeholder immediately. Operations inside that repo mount, such as ls, less, or git -C /tmp/workers-sdk status, wait until the clone/fetch and snapshot publish have completed. If preparation fails, those operations return an I/O error until preparation is retried:
./artifact-fs status --name workers-sdk
./artifact-fs prepare --name workers-sdk
Async HTTPS remotes must use ambient credentials, such as a configured Git credential helper or repo-local Git config. Inline credentials in the remote URL are rejected for async repositories.
For workflows that create the gitdir separately, --prepared-gitdir makes the async step fetch and prepare an existing gitdir instead of running git clone:
git init --separate-git-dir /tmp/workers-sdk.git --initial-branch main /tmp/workers-sdk
git -C /tmp/workers-sdk remote add origin https://github.com/cloudflare/workers-sdk.git
./artifact-fs add-repo \
--name workers-sdk \
--branch main \
--mount-root /tmp \
--async \
--prepared-gitdir \
--git-dir /tmp/workers-sdk.git \
--fetch-ref main
examples/Dockerfile builds artifact-fs and starts a FUSE-mounted repo inside a container. The container requires --cap-add SYS_ADMIN --device /dev/fuse for FUSE access.
# Build the image
docker build -t artifact-fs-example -f examples/Dockerfile .
# Run with the default repo (cloudflare/workers-sdk)
docker run --rm --cap-add SYS_ADMIN --device /dev/fuse artifact-fs-example
# Run with a private repo
docker run --rm --cap-add SYS_ADMIN --device /dev/fuse \
-e REPO_REMOTE_URL=https://<token>@github.com/org/private-repo.git \
artifact-fs-example
# Run a command inside the mounted repo
docker run --rm --cap-add SYS_ADMIN --device /dev/fuse \
artifact-fs-example git log --oneline -5
The entrypoint registers the repo, starts the daemon, waits for the mount, then either runs the provided command or keeps the container alive.
On hosts with AppArmor enabled (Ubuntu default), add --security-opt apparmor:unconfined to the docker run flags.
ArtifactFS has two distinct phases: a one-shot setup (add-repo) that registers and usually prepares a fast blobless clone, and a long-running daemon that mounts it via FUSE and serves file operations. With add-repo --async, setup only registers the repo; the daemon performs clone/fetch and snapshot publishing while FUSE operations wait behind a readiness gate.
┌─────────────────────────────────────────────────┐
│ Daemon │
│ │
┌──────────┐ clone │ ┌──────────┐ ls-tree ┌──────────────┐ │
│ Remote │◄──────────┼──│ GitStore │────────────────►│ Snapshot │ │
│ repo │ fetch │ │ │ cat-file │ (SQLite) │ │
└──────────┘ │ │ batch │ --batch-check │ │ │
│ │ pool │ │ base_nodes │ │
│ └────┬─────┘ │ per gen │ │
│ │ cat-file └──────┬───────┘ │
│ │ --batch │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Blob │ │ Resolver │ │
│ │ Cache │ │ │ │
│ │ (disk) │◄────hydrate─────│ snap + ovl │ │
│ └──────────┘ │ merged view │ │
│ ▲ └──────┬───────┘ │
│ │ │ │
│ ┌────┴─────┐ prefetch ┌─────┴────────┐ │
│ │ Hydrator │◄─────────────────│ Engine │ │
│ │ │ │ │ │
│ │ priority │ ensureOverlay │ read / write │ │
│ │ queue │ copy-on-write │ create / rm │ │
│ └──────────┘ └─────┬────────┘ │
│ │ │
│ ┌──────────┐ ┌──────┴───────┐ │
│ │ Overlay │◄────────────────│ FUSE Layer │ │
│ │ (SQLite │ write ops │ (macFUSE / │ │
│ │ + upper │ │ /dev/fuse) │ │
│ │ dir) │ └──────┬───────┘ │
│ └──────────┘ │ │
│ │ │
│ ┌──────────┐ HEAD poll ┌─────┴────────┐ │
│ │ Watcher │─────────────────►│ Mount point │ │
│ │ (500ms) │ re-index + │ /tmp/myrepo │ │
│ └──────────┘ reconcile └──────────────┘ │
└─────────────────────────────────────────────────┘
Clone/fetch -- add-repo runs git clone --filter=blob:none (blobless) unless --async is used. In async mode, the daemon performs either the blobless clone or a fetch into a prepared gitdir. Only commits, trees, and refs are fetched. No file content is downloaded.
Index -- git ls-tree -r -t -z HEAD enumerates every path in the tree. Sizes are resolved locally via git cat-file --batch-check with GIT_NO_LAZY_FETCH=1 to avoid network round-trips. The result is bulk-inserted into a SQLite base_nodes table as a new generation.
Mount -- The FUSE layer exposes the tree immediately. A synthesized .git gitfile points at the real gitdir so git commands work inside the mount.
Read -- The Resolver merges the snapshot (base tree) with the overlay (local writes). For base files, reads block until the Hydrator fetches the blob via a persistent git cat-file --batch process and streams it to the blob cache.
Write -- The Engine promotes base files to the overlay via copy-on-write (hydrate, then copy to the upper/ directory). Subsequent reads come from the overlay. Deletes are recorded as whiteouts.
Background -- A watcher polls HEAD/refs every 500ms. On HEAD changes (commit, branch switch, fetch), the daemon re-indexes the tree, publishes a new snapshot generation, reconciles stale overlay entries, and refreshes the git index.
| Package | Role |
|---|---|
daemon |
Orchestrates repo lifecycle, refresh loop, watcher callbacks |
fusefs |
FUSE adapter (inode management, op dispatch), Resolver (merged view), Engine (read/write logic) |
gitstore |
Git CLI wrapper: clone, fetch, ls-tree, batch pool for cat-file --batch |
snapshot |
SQLite store for base_nodes keyed by (generation, path) |
overlay |
SQLite metadata + upper/ directory for local writes, whiteouts, reconciliation |
hydrator |
Priority queue with deduped waiters; workers block on a workReady channel |
watcher |
Polls gitdir mtimes (HEAD, index, refs) at 500ms intervals |
registry |
SQLite-backed repo config persistence |
model |
Shared types and canonical interfaces (GitStore, SnapshotStore, OverlayStore, Hydrator) |
Work in progress. The table below reflects operations tested against cloudflare/workers-sdk mounted via macFUSE.
| Operation | Status | Notes |
|---|---|---|
ls (root and subdirectories) |
Supported | Includes synthesized .git gitfile |
cat / read file |
Supported | Triggers on-demand hydration for unhydrated blobs |
stat (file size, mode) |
Supported | Sizes resolved via git cat-file --batch-check |
mkdir |
Supported | Persisted in writable overlay |
| Create new file | Supported | Persisted in writable overlay |
| Write / append to file | Supported | Copy-on-write for tracked files |
| Rename file | Supported | Works for both overlay and tracked (snapshot-only) files |
Delete file (rm) |
Supported | Whiteout recorded in overlay |
rmdir |
Supported | Checks directory is empty first |
| Truncate | Supported | Hydrates blob before truncating |
Symlink read (readlink) |
Supported | Symlink target read from blob content |
| Operation | Status | Notes |
|---|---|---|
git log |
Supported | Reads from pack objects |
git branch |
Supported | |
git rev-parse HEAD |
Supported | |
git show |
Supported | |
git remote -v |
Supported | Credentials stripped from output |
git stash list |
Supported | |
git status |
Supported | ~7s on 5800-entry repo |
git diff |
Supported | Shows correct unified diff for modified files |
git add |
Supported | Stages modified files |
git reset |
Supported | ~6.5s index refresh |
git commit |
Supported | Watcher detects HEAD change; overlay reconciles stale entries |
git checkout |
Supported | Re-indexes tree, reconci |
$ claude mcp add artifact-fs \
-- python -m otcore.mcp_server <graph>