Skip to main content
Mesa uses FUSE to expose Mesa repositories as a POSIX-compliant filesystem. Any tool that works with files — editors, language servers, build systems, agents — can read and write repository contents through normal filesystem operations.
MesaFS can be run as either a POSIX mount or app mount. If you aren’t sure which to use, see Filesystem for an overview and comparison.

Installation

Install the mesa CLI on macOS or Linux:
curl -fsSL https://mesa.dev/install.sh | sh
Re-run the same command at any time to update to the latest version.
On macOS, Mesa also requires macFUSE. See Platform requirements below for setup instructions.

General flow

  1. Install Mesa — install the mesa CLI and any platform-specific FUSE dependencies.
  2. Configure and mount — set MESA_API_KEY and MESA_ORG, then run mesa mount or mesa mount --daemonize for background operation.
  3. Work with your reposcd into the mount path (default: ~/.local/share/mesa/mnt/<org>/<repo>) and use any tool — editors, shell commands, agents. File changes are automatically persisted back to Mesa.
# Install
curl -fsSL https://mesa.dev/install.sh | sh

# Mount
MESA_ORG=my-org MESA_API_KEY=mesa_... mesa mount --daemonize

# Work with your repos
cd ~/.local/share/mesa/mnt/my-org/my-repo
ls src/
cat README.md
Running inside a sandbox (Docker, Daytona, E2B, etc.)? The same flow applies for installing the CLI, setting MESA_API_KEY and MESA_ORG, and mounting. See the Daytona guide or other provider-specific guides.
POSIX mounts can see reads and writes from other mounts on the same change in realtime. See realtime docs for more details.

Platform requirements

macOS

Mesa requires macFUSE. Follow the official macFUSE guide to install it, including:

Linux

Mesa uses FUSE3 via libfuse3. Most full Linux distributions include this by default and mesa’s installer installs it for you otherwise.

allow_other and user_allow_other

Mesa mounts with the FUSE allow_other option so that other processes — like your agent, editors, and language servers — can access the mounted filesystem. Without this, only the process that ran mesa mount would be able to read the files. On Linux, this requires user_allow_other to be enabled in /etc/fuse.conf. Uncomment or add the following line:
user_allow_other
If this is not set, mesa mount will fail with a permission error.
If your environment runs as root (common in some CI and container setups), allow_other works without this setting. But if you’re running as a non-root user — which is the default in most sandbox providers like Daytona — you’ll need to ensure this is configured.

Docker

Slim and minimal base images strip out system libraries that Mesa needs at runtime. Install them explicitly:
For Debian-based images like node:22-slim or debian:bookworm-slim:
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    fuse3 \
    libssl3 \
    openssl \
    && rm -rf /var/lib/apt/lists/* \
    && update-ca-certificates
  • ca-certificates — TLS certificate store, required for connections to Mesa’s API.
  • fuse3 — Userspace FUSE library.
  • openssl (and libssl3 on Debian) — OpenSSL shared library, used for TLS.
If you skip these packages on a slim image, connections to Mesa’s API will fail with errors like gRPC TLS configuration failed: transport error.
The same dependencies apply to sandbox environments (Daytona, E2B, etc.) — see provider-specific guides like the Daytona guide for more on setup.

Usage

Browsing your repos

Mesa exposes standard filesystem semantics. You can use regular shell commands — ls, cat, cp, grep, and more — directly against your mounted repos.
cd ~/.local/share/mesa/mnt/my-org
ls
cd my-repo-1
cat README.md
grep -r "TODO" src/
You can also open a mounted repo in your IDE. For example, with Cursor:
cursor ~/.local/share/mesa/mnt/my-org/my-repo-1
Mesa mounts every repo your API key can access. To restrict which repos are available, scope the API key.

Editing files in repos

Writes edit the you have checked out — by default, the change the mounted points at — in place, advancing any bookmark pointing at it. See Writing to a Mount for the model and how to work in isolation instead. Every write you perform in mesa that affects your source code will create a new automatically in the background. Mesa respects your .gitignore and will not create commits for files which you have configured as ignored.

Extended attributes

Mesa supports POSIX extended attributes (xattrs) on files and directories inside a mounted repo. Use them to attach lightweight metadata (provenance markers, build labels, agent annotations) without rewriting commits or maintaining a sidecar database. Mesa scopes the writable xattr surface to the user.mesa.* namespace. Names outside that prefix — including the broader user.* (e.g. user.origin) and the kernel-managed security.* / system.* / trusted.* namespaces — are short-circuited at the FUSE layer: writes return EPERM and reads return ENODATA without crossing into mesa’s gRPC backend. This keeps file-heavy workloads (npm install, cp -a, tar --xattrs) from paying a round-trip on every kernel-issued xattr query they trigger. Symlinks cannot carry user.* xattrs — Linux’s kernel restricts that namespace to regular files and directories at the VFS layer, so attempts to lsetxattr a user.* attribute on a symlink return EPERM. Standard POSIX tooling works against the mount:
# Set a value
setfattr -n user.mesa.origin -v "notion:foo" docs/notes.md

# Read one attribute
getfattr -n user.mesa.origin docs/notes.md

# List every attribute on a path
getfattr -d docs/notes.md

# Remove an attribute
setfattr -x user.mesa.origin docs/notes.md
On macOS, use the xattr CLI (xattr -w user.mesa.origin notion:foo docs/notes.md, xattr -p user.mesa.origin docs/notes.md, etc.). Library callers go through the standard setxattr / getxattr / listxattr / removexattr syscalls and the l*xattr variants for symlinks. Xattrs are sticky: once set, they stay on the path across content edits, and they travel with the path when Mesa records a move_path op. Deleting the path through the mount removes its xattrs. Renames detected only via Git’s content similarity heuristic (no move_path op recorded) drop their xattrs — re-tag the new path if needed.

Limits

ConstraintLimit
Per-attribute value64 KiB (strict, kernel XATTR_SIZE_MAX)
Per-path total xattr size~64-72 KiB raw (storage-bounded)
Attribute-name length255 bytes
Allowed namespaceuser.mesa.* only
The per-attribute cap is exact. The per-path total is bounded by storage rather than by a strict raw-byte count: values are base64-encoded in the backing store, and the storage cap (96 KiB JSONB) translates to roughly 64-72 KiB raw per path depending on how many attributes you set and how long their names are. Linux’s kernel itself doesn’t enforce a strict per-path raw total either — that’s per-filesystem, and Mesa follows suit. Violations surface as standard POSIX errnos: E2BIG (value exceeds 64 KiB — the kernel intercepts oversized syscalls itself; or storage cap reached when adding more attributes to a path), EPERM (name outside the user.mesa.* namespace, or any user.* name on a symlink), ENAMETOOLONG (name length), ENODATA (removing a missing attribute, or any read for a name outside user.mesa.*).

Synthetic xattrs

Mesa exposes a few read-only synthetic attributes on every mounted path:
NameValue
user.mesa.orgThe org owning the mounted repo
user.mesa.repoThe repo name
user.mesa.daemon-pidThe mesa daemon’s PID
If you set a stored value with the same name, the synthetic wins on read — the stored value is invisible until you remove the synthetic source (which you can’t). The mesa CLI uses user.mesa.org to infer --org from the current working directory, so it’s the load-bearing case.

Reading xattrs through REST or the SDK

The Content API and the TypeScript SDK return xattrs read-only on file and symlink responses — directory listings are unchanged. There is no REST or SDK write surface in v1; all writes go through the mount. Reads at a historical change_id return today’s xattrs for the resolved path, not the values as of that change (xattrs are stored separately from Git history).

Commands

mesa new

Create a new detached from a or ID:
mesa new main
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
Omitting -m creates the change with no description. Pass -m or --message to set one:
mesa new main -m "Try new copy"
You can also create a new based on an existing one by passing its ID:
mesa new <change-id>
New changes are always detached — writes still snapshot the change but do not advance any bookmark. To work on a bookmark where writes advance it, use mesa edit instead.

mesa edit

Switch to a different bookmark:
mesa edit my-feature
The filesystem immediately updates to reflect the selected bookmark. Writes will automatically advance the bookmark as you work. You can also resume a previous by ID:
mesa edit <change-id>
Resuming a change by ID enters detached mode. Any bookmarks that were tracking this change will still advance automatically as you write.
checkout is an alias for edit: mesa checkout main works identically.

mesa bookmark create

Creates a new pointing at the current :
mesa bookmark create my-feature
You can also specify which revision the bookmark should point to with -r:
mesa bookmark create my-feature -r main
This only creates the bookmark — it does not switch to it. To start working on the new bookmark, follow up with mesa edit:
mesa bookmark create my-feature
mesa edit my-feature

mesa bookmark list

List all bookmarks for a repository:
mesa bookmark list

mesa log

To look at the log of all your , you can run mesa log. Mesa will display a list of changes, as well as a helpful diagram.

Workflow examples

Making changes on a feature bookmark

Create a feature bookmark and write some files.
# Start the daemon in the background
mesa mount --daemonize

# Navigate to the mounted repo
cd ~/.local/share/mesa/mnt/my-org/my-repo

# Create a feature bookmark based on the current change and switch to it
mesa bookmark create my-feature
mesa edit my-feature

# Make changes
echo 'export function fix() { return true; }' > src/fix.ts
echo 'import { fix } from "./fix";' >> src/index.ts
Every write through the mount snapshots the change automatically. Because you switched to the bookmark with mesa edit, the bookmark also advances automatically. There is no manual save step, changes are persisted to Mesa as you write.

Browsing and switching between bookmarks

Browse a repo, start a feature bookmark, switch between bookmarks, and resume work.
# Start mesa in the foreground
mesa mount

# Navigate to the mounted repo in a separate terminal tab
cd ~/.local/share/mesa/mnt/my-org/my-repo

# Browse the monorepo
ls packages/
cat README.md
grep -r "TODO" src/

# Create a feature bookmark and switch to it
mesa bookmark create my-feature
mesa edit my-feature

# Do some work on the feature bookmark
echo "new feature" > src/new-feature.ts
ls src/
# see the new file new-feature.ts

# Switch back to main
mesa edit main
ls src/
# no new-feature.ts file

# Switch back to the feature bookmark
mesa edit my-feature
ls src/
# see that the new-feature.ts file is back
For the full list of CLI flags and options, see the CLI reference.