# Initialize a workspace

> Run `rift init` with Git-root vs `--here` selection, btrfs subvolume conversion progress, marker restoration, and success signals.

- Repository: anomalyco/rift
- GitHub: https://github.com/anomalyco/rift
- Human docs: https://www.grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662
- Complete Markdown: https://www.grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662/llms-full.txt

## Source Files

- `README.md`
- `specs.md`
- `crates/cli/src/main.rs`
- `crates/core/src/lib.rs`
- `crates/core/src/strategy/mod.rs`

---

---
title: "Initialize a workspace"
description: "Run `rift init` with Git-root vs `--here` selection, btrfs subvolume conversion progress, marker restoration, and success signals."
---

`rift init` registers a source workspace in the central SQLite registry, writes a `.rift` ULID marker at the workspace root, and—on Linux btrfs—may convert an ordinary directory into a btrfs subvolume before registration. The CLI resolves the target path (Git root vs current directory) before calling core `init` on exactly one absolute directory; the JavaScript `init()` API skips that selection and always initializes `at`.

## Command

```bash
rift init [PATH] [--here]
```

<ParamField body="PATH" type="path">
Optional directory to initialize. Defaults to the current working directory after canonicalization.
</ParamField>

<ParamField body="--here" type="flag">
Initialize exactly `PATH` (or the current directory). Disables upward selection of an existing Rift root or Git root.
</ParamField>

<Note>
Hidden global flags `--database` and `--shell-cwd` exist for testing and shell integration. See [CLI reference](/cli-reference) and [Shell integration](/shell-integration).
</Note>

## Target selection (CLI only)

Without `--here`, the CLI chooses the directory passed to core `init` before any filesystem work runs.

```text
requested = canonicalize(PATH or cwd)

if --here:
  init_at = requested
else if upward search finds .rift marker:
  init_at = that managed workspace root     → outcome: "Already initialized"
else if registry has record but .rift missing:
  init_at = that registered root            → outcome: "Restored marker" (+ optional conversion)
else if upward search finds .git directory:
  init_at = nearest Git root
else:
  init_at = requested
```

<Warning>
Git-root selection walks ancestors for a `.git` entry. Linked Git worktrees (`.git` is a file, not a directory) are rejected during core initialization with `unsafe_git`, not during target selection.
</Warning>

| Mode | From `~/code/app/pkg` (Git root at `app/`, no Rift marker) | Effect |
|------|--------------------------------------------------------------|--------|
| Default | `rift init` | Initializes `~/code/app` |
| `--here` | `rift init --here` | Initializes `~/code/app/pkg` |
| Inside existing root | `rift init` from a child of an initialized workspace | Initializes the existing root; prints `Already initialized` |

The JavaScript `init({ at })` function and FFI `init` request initialize exactly `at` with no upward resolution. Git-root and `--here` behavior is CLI-only.

## What initialization does

Core `init` runs a fixed sequence on the selected absolute directory:

1. **Verify directory** — path must exist and be a directory (`canonicalize`).
2. **Git safety check** — refuse linked worktrees and in-progress merge/rebase/cherry-pick/revert/bisect or lock states.
3. **Registry lookup** — if the path is already registered:
   - Restore `.rift` when the marker file is missing (same ULID from registry).
   - Verify marker matches registry when present.
   - Run platform `initialize_directory` (btrfs conversion may still apply).
   - Add `/.rift` to `.git/info/exclude` when the workspace is a Git repository.
4. **First registration** — if unregistered:
   - Reject stray `.rift` files without a registry record (`marker_mismatch`).
   - Run platform initialization.
   - Write a new ULID marker, register as root (`parent_id = NULL`), hide marker from Git status.

```text
unregistered path
  → strategy.initialize_directory
  → RegisteringWorkspace (core progress)
  → write .rift + insert_root

registered path
  → restore/verify marker
  → strategy.initialize_directory
  → hide marker from Git (if repo)
```

### Platform behavior

| Platform / filesystem | `initialize_directory` | Registration outcome (typical) |
|-----------------------|------------------------|--------------------------------|
| Linux btrfs, already subvolume | No-op | `Registered` or `AlreadyInitialized` |
| Linux btrfs, ordinary directory | Reflink-import into staged subvolume, atomic path swap | `Converted` |
| Linux non-btrfs (XFS, etc.) | Verify `FICLONE` reflink support | `Registered` / `AlreadyInitialized` |
| macOS APFS | Default strategy: no conversion | `Registered` / `AlreadyInitialized` |
| Windows | No CoW strategy | `cow_unavailable` on create; init uses default no-op strategy |

On btrfs, first-time conversion creates staging paths `.rift-init-{ULID}` and `.rift-init-original-{ULID}` beside the workspace, reflink-imports the tree, renames the original aside, activates the subvolume at the original path, copies metadata, and removes the original. Failed activation rolls back to the original directory.

<Info>
After btrfs conversion the workspace path is unchanged, but the directory inode is new. If your shell was inside the directory during conversion, re-enter it—see [Success signals](#success-signals).
</Info>

## Btrfs conversion progress

Progress events are emitted on stderr only during Linux btrfs first-time conversion. The CLI maps `InitProgress` variants to user-visible lines.

<Steps>
<Step title="Creating subvolume">
stderr:

```text
Initializing  /path/to/workspace

First-time setup can take a moment.
New rifts will be instant.

Creating BTRFS subvolume...
```
</Step>

<Step title="Importing workspace">
stderr:

```text
Importing workspace...
```

Core emits `ImportedEntries { entries }` during per-file reflink import; the CLI does not print entry counts.
</Step>

<Step title="Activating and cleanup">
`ActivatingWorkspace`, `RemovingOriginal`, and `RegisteringWorkspace` run without additional CLI lines. On success:

```text
Ready  /path/to/workspace
```

When conversion produced progress output, `Ready` is preceded by a blank line.
</Step>
</Steps>

Non-btrfs platforms and already-converted btrfs subvolumes skip the conversion progress block entirely.

## Marker restoration

A registered workspace can lose its `.rift` file while the SQLite record remains. Upward resolution then returns `MissingMarker` with the registered root path; `rift init` (without `--here`) selects that root automatically.

Restoration behavior:

- Emits core progress `RestoringMarker` (no CLI line).
- Rewrites `.rift` with the **existing** registry ULID (identity is preserved).
- Continues `initialize_directory`—for example, completing a pending btrfs conversion on a partially initialized tree.
- Prints stderr: `Restored marker  <path>` when the outcome is not `Converted`.

If the marker exists and matches the registry on an already-native workspace, the outcome is `AlreadyInitialized` and the CLI prints `Already initialized  <path>`.

### Marker file

```text
/path/to/workspace/.rift
```

Single-line ULID plus newline. The same value is stored in the central registry. See [Workspaces and registry](/workspaces-and-registry).

## Success signals

All completion messages go to **stderr** unless noted. Exit code is `0` on success.

| Outcome | Condition | stderr signal | Extra stdout |
|---------|-----------|---------------|--------------|
| `Registered` | First registration, no btrfs conversion | `Ready  <path>` | — |
| `Converted` | Btrfs ordinary directory → subvolume | Progress lines, then `Ready  <path>` | — |
| `AlreadyInitialized` | Registered, marker OK, no conversion needed | `Already initialized  <path>` | — |
| Marker restored | Registered, marker was missing, no conversion | `Restored marker  <path>` | — |

### Re-enter after conversion

When initialization converts the workspace **and** the current working directory is inside the initialized path, the CLI advises:

```text
run `cd /path/to/workspace` to enter the initialized workspace
```

With shell integration (`rift shell-init` + `--shell-cwd`), the initialized path is printed to **stdout** instead so the wrapper can `cd` automatically. See [Shell integration](/shell-integration).

<Check>
Verify initialization:

```bash
test -f .rift && rift list
```

`list` returns no children for a fresh source root (exit `0`, empty stdout). A restored or new marker contains a ULID readable with `cat .rift`.
</Check>

## Git integration during init

For Git repositories, successful init appends `/.rift` to `.git/info/exclude` so the marker does not appear in `git status`. Init does not detach `HEAD` or alter the index—that happens on `rift create`. See [Git integration](/git-integration).

## Common failures

| Error / code | Typical cause | Guidance |
|--------------|---------------|----------|
| `cow_unavailable` | Linux path not on btrfs (btrfs strategy) or reflink probe failed | Use btrfs or a reflink-capable filesystem; see [Copy strategies](/copy-strategies) |
| `unsafe_git` | Linked worktree or in-progress Git operation | Finish or abort Git state; use a normal repository directory |
| `marker_mismatch` | `.rift` present on an unregistered path | Remove orphan marker or pick the correct directory with `--here` |
| `workspace_not_initialized` | Used by other commands, not `init` itself | Run `rift init` from the project root |
| Non-zero exit | Any `RiftError` | Message on stderr; see [Error codes](/error-codes) |

## JavaScript and FFI

```ts
import { init } from "rift-snapshot";

init({ at: "/absolute/path/to/workspace" });
```

`init` returns `null` on success. It does not surface btrfs progress callbacks; use the CLI for conversion progress output. Optional `database` overrides the default SQLite path (`~/.local/share/rift/rift.sqlite` on Linux). See [JavaScript API reference](/javascript-api).

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Initialize, create a child workspace, list it, and remove it with expected stdout signals.
</Card>
<Card title="Workspaces and registry" href="/workspaces-and-registry">
ULID identity, `.rift` markers, SQLite schema, and parent-child provenance.
</Card>
<Card title="Copy strategies and platforms" href="/copy-strategies">
Btrfs subvolume conversion, Linux reflinks, and APFS clonefile backends.
</Card>
<Card title="Git integration" href="/git-integration">
Repository detection, marker exclusion, and unsafe source states.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full `rift init` flags, exit codes, and hidden options.
</Card>
</CardGroup>
