> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mesa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# POSIX Mount

> Expose Mesa repositories as real directories on the host

export const BookmarkTt = () => <Tooltip headline="Bookmark" tip="Equivalent to git branch" href="/content/core-concepts/glossary#bookmark" cta="Read the glossary">
    bookmark
  </Tooltip>;

export const ChangeTt = () => <Tooltip headline="Change" tip="Equivalent to git commit" href="/content/core-concepts/glossary#change" cta="Read the glossary">
    change
  </Tooltip>;

export const CommitTt = () => <Tooltip headline="Commit" tip="An atomic state of your repo at any given point in the past" href="/content/core-concepts/glossary#commit" cta="Read the glossary">
    commit
  </Tooltip>;

Mesa uses [FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) 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.

<Note>
  MesaFS can be run as either a POSIX mount or app mount. If you aren't sure which to use, see [Filesystem](/content/concepts/filesystem) for an overview and
  comparison.
</Note>

## Installation

Install the `mesa` CLI on **macOS** or **Linux**:

```bash theme={null}
curl -fsSL https://mesa.dev/install.sh | sh
```

Re-run the same command at any time to update to the latest version.

## 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 repos** — `cd` 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.

```bash theme={null}
# 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
```

<Tip>
  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](/content/integrations/sandboxes/daytona) or other provider-specific
  guides.
</Tip>

POSIX mounts can see reads and writes from other mounts on the same change in realtime. See [realtime docs](/content/mesafs/advanced/realtime) for more details.

## Platform requirements

### macOS

On macOS, the Mesa install script uses Homebrew to install required
dependencies. If you don't have Homebrew yet, install it from
[brew.sh](https://brew.sh).

### 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.

<Note>
  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.
</Note>

### Docker

Slim and minimal base images strip out system libraries that Mesa needs at runtime. Install them explicitly:

<Tabs>
  <Tab title="Debian / Ubuntu">
    For Debian-based images like `node:22-slim` or `debian:bookworm-slim`:

    ```dockerfile theme={null}
    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
    ```
  </Tab>

  <Tab title="Alpine">
    For Alpine-based images like `node:22-alpine` or `alpine:3.21`:

    ```dockerfile theme={null}
    RUN apk add --no-cache \
        ca-certificates \
        fuse3 \
        openssl
    ```
  </Tab>
</Tabs>

* **`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.

<Warning>
  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`.
</Warning>

The same dependencies apply to sandbox environments (Daytona, E2B, etc.) — see provider-specific guides like the [Daytona guide](/content/integrations/sandboxes/daytona) 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.

```bash theme={null}
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:

```bash theme={null}
cursor ~/.local/share/mesa/mnt/my-org/my-repo-1
```

<Tip>
  Mesa mounts every repo your API key can access. To restrict which repos are
  available, scope the API key.
</Tip>

### Editing files in repos

Writes edit the <ChangeTt /> you have checked out — by default, the change the mounted
<BookmarkTt /> points at — in place, advancing any bookmark pointing at it. See
[Writing to a Mount](/content/concepts/filesystem#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 <CommitTt />
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:

```bash theme={null}
# 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

| Constraint                | Limit                                    |
| ------------------------- | ---------------------------------------- |
| Per-attribute value       | 64 KiB (strict, kernel `XATTR_SIZE_MAX`) |
| Per-path total xattr size | \~64-72 KiB raw (storage-bounded)        |
| Attribute-name length     | 255 bytes                                |
| Allowed namespace         | `user.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:

| Name                   | Value                           |
| ---------------------- | ------------------------------- |
| `user.mesa.org`        | The org owning the mounted repo |
| `user.mesa.repo`       | The repo name                   |
| `user.mesa.daemon-pid` | The 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](/content/api-reference/overview) 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 <CommitTt /> from a <BookmarkTt /> or <ChangeTt /> ID:

```bash theme={null}
mesa new main
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
```

Omitting `-m` creates the change with no description. Pass `-m` or `--message` to set one:

```bash theme={null}
mesa new main -m "Try new copy"
```

You can also create a new <ChangeTt /> based on an existing one by passing its <ChangeTt /> ID:

```bash theme={null}
mesa new <change-id>
```

<Note>
  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.
</Note>

#### `mesa edit`

Switch to a different bookmark:

```bash theme={null}
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 <ChangeTt /> by ID:

```bash theme={null}
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.

<Tip>
  `checkout` is an alias for `edit`: `mesa checkout main` works identically.
</Tip>

#### `mesa bookmark create`

Creates a new <BookmarkTt /> pointing at the current <ChangeTt />:

```bash theme={null}
mesa bookmark create my-feature
```

You can also specify which revision the bookmark should point to with `-r`:

```bash theme={null}
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`:

```bash theme={null}
mesa bookmark create my-feature
mesa edit my-feature
```

#### `mesa bookmark list`

List all bookmarks for a repository:

```bash theme={null}
mesa bookmark list
```

#### `mesa log`

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

### Workflow examples

#### Making changes on a feature bookmark

Create a feature bookmark and write some files.

```bash theme={null}
# 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.

```bash theme={null}
# 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](/content/reference/mesa-cli).
