# Create workspaces

> Create child workspaces with `--name`, `--into`, filtered vs `--copy-all` copy, `--no-hooks`, random name generation, and Git preparation.

- 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/core/src/lib.rs`
- `crates/core/src/filter.rs`
- `crates/core/src/name.rs`
- `crates/core/src/hook.rs`

---

---
title: "Create workspaces"
description: "Create child workspaces with `--name`, `--into`, filtered vs `--copy-all` copy, `--no-hooks`, random name generation, and Git preparation."
---

`rift create` resolves the nearest managed ancestor from the current directory (or an explicit `from` path), copy-on-write clones that workspace into storage, registers the child in the SQLite registry with the immediate source as its parent, prepares Git state in the destination, and optionally runs `.rift.toml` postcreate hooks. On success the new workspace path is printed to stdout.

## Prerequisites

The source directory must belong to an initialized Rift workspace. Rift walks upward from `from` (default: current working directory) until it finds a `.rift` marker tied to a registry record.

<Warning>
If no marker is found, the CLI exits with `no initialized workspace found; run rift init from the root folder`. On Linux btrfs, an ordinary directory that was never converted to a subvolume fails with `this workspace must be initialized first; run rift init from its root folder`.
</Warning>

## Basic usage

```bash
cd ~/code/app
rift create
```

<RequestExample>

```bash
rift create --name parser-fix
```

</RequestExample>

<ResponseExample>

```text
/home/you/code/.rifts/app/parser-fix
```

</ResponseExample>

The printed path is the canonical destination. Shell integration (`rift shell-init`) can consume it via the hidden `--shell-cwd` flag to `cd` into the new workspace automatically.

## CLI flags

| Flag | Default | Effect |
| --- | --- | --- |
| `from` (positional) | Current working directory | Starting path for upward `.rift` resolution |
| `--name` | Random `adjective-noun` name | Single path segment for the child directory |
| `--into` | Root workspace's `.rifts/<name>/` parent | Custom storage parent directory |
| `--copy-all` | Off (filtered copy) | Copy every path, including regenerable artifacts |
| `--no-hooks` | Off (hooks run) | Skip `.rift.toml` loading and postcreate execution |

<ParamField body="from" type="path">
Optional positional argument. Must resolve to a directory inside a managed workspace. Rift copies the workspace at the resolved marker path, not a subdirectory when `from` points deeper inside a managed tree.
</ParamField>

<ParamField body="--name" type="string">
Single path segment. Rejects empty names, `.`, `..`, and names containing path separators. When omitted, Rift generates a readable `adjective-noun` name (for example `amber-badger`) independent of the workspace ULID stored in `.rift`.
</ParamField>

<ParamField body="--into" type="absolute or relative path">
Parent directory for the new child. Rift creates the directory if needed, canonicalizes it, then places `<name>` inside. Must not lie inside the source tree. Custom storage must be on a filesystem that supports the active copy-on-write backend with the source; cross-filesystem `--into` paths fail with `copy-on-write cloning unavailable`.
</ParamField>

<ParamField body="--copy-all" type="boolean">
Selects `CopyMode::All`. Without this flag, Rift uses filtered copy and omits known heavyweight regenerable artifacts at any depth.
</ParamField>

<ParamField body="--no-hooks" type="boolean">
Selects `HookMode::Skip`. Skips reading `.rift.toml` entirely, so invalid config cannot block creation.
</ParamField>

## Default storage layout

When `--into` is omitted, Rift stores children beside the registered root workspace:

```text
~/code/app/                    source (registered root)
~/code/.rifts/app/parser-fix/  created child
```

Storage always anchors to the **root** workspace, even when `from` is itself a created child. Creating from `~/code/.rifts/app/first` still places siblings under `~/code/.rifts/app/`, and records `first` as the immediate parent in the registry.

<Info>
Created workspaces are never stored inside the directory being copied. An exact copy would recursively include existing children.
</Info>

## Copy modes

Filtered copy is the default. Rift preserves manifests, lockfiles, dirty files, staged changes, untracked files, and ignored paths that are not in the built-in exclusion set.

### Filtered (default)

Excluded path components (matched at any depth):

| Category | Excluded names |
| --- | --- |
| Node / JS | `node_modules`, `.pnpm-store`, `.yarn/cache`, `.yarn/unplugged`, `.yarn/install-state.gz`, `.yarn/build-state.yml` |
| Rust | `target` |
| Python | `.venv`, `venv`, `.tox`, `.nox`, `__pycache__`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache` |
| Framework caches | `.next`, `.nuxt`, `.svelte-kit`, `.turbo`, `.vite`, `.parcel-cache`, `.cache` |
| Build output | `dist`, `build`, `coverage` |

Platform backends apply filtered mode differently:

| Platform | Filtered copy | Exact copy (`--copy-all`) |
| --- | --- | --- |
| Linux btrfs | Reflink import into a new subvolume (included paths only) | Writable btrfs subvolume snapshot |
| Linux reflink (XFS, etc.) | Per-file reflinks for included entries | Full tree reflink clone |
| macOS APFS | Per-entry `clonefile` | Directory `clonefile` clone |

<Note>
Full byte copying is not implemented. If no copy-on-write strategy succeeds, creation fails closed.
</Note>

### Exact (`--copy-all`)

```bash
rift create --copy-all --name full-copy
```

Preserves all paths including `node_modules`, `target`, virtualenvs, and build caches. Use when you need a bitwise-equivalent tree without reinstalling dependencies.

## Postcreate hooks

When hooks are enabled (default), Rift loads `.rift.toml` from the **source** workspace before copying. Invalid config (wrong `version`, empty `run`, unknown fields) fails before any filesystem copy begins.

After the workspace is copied, the `.rift` marker is written, Git preparation completes, and the child is registered, configured postcreate commands run sequentially in the **destination** root:

```toml
version = 1

[[hooks.postcreate]]
run = "pnpm install --frozen-lockfile"

[[hooks.postcreate]]
run = "pnpm run codegen"
```

Each hook inherits stdio and receives:

| Variable | Value |
| --- | --- |
| `RIFT_SOURCE` | Source workspace path |
| `RIFT_DESTINATION` | New workspace path |
| `RIFT_ID` | Child ULID |
| `RIFT_PARENT_ID` | Immediate parent ULID |

```bash
rift create --no-hooks
```

`--no-hooks` skips config loading and hook execution. An invalid `.rift.toml` on the source does not block creation.

<Warning>
If a hook fails, the workspace **remains registered and on disk**. `rift create` exits with a `postcreate hook failed` error naming the destination path and failing command. Later hooks in the sequence do not run.
</Warning>

## Git preparation

When the source contains a Git repository (`.git` is a directory), Rift prepares the destination after copying:

1. Adds `/.rift` to `.git/info/exclude` in the destination (and re-applies on the source) so the marker never appears in `git status`.
2. Detaches `HEAD` at the same commit as the source. Symbolic tag heads are peeled to commits. Repositories with no commits keep their unborn branch state unchanged.
3. Preserves index, working tree, staged, unstaged, untracked, and ignored state for all copied paths.

Creation is refused when the source Git state is unsafe:

| Condition | Error |
| --- | --- |
| Linked worktree (`.git` is a file) | `linked Git worktree sources are not supported` |
| Merge, cherry-pick, revert, or bisect in progress | `Git state in progress: <marker>` |
| `index.lock` or `HEAD.lock` present | `Git state in progress: <lock>` |

Rift does not create branches, commit changes, or replace normal Git workflows.

## Create lifecycle

```mermaid
sequenceDiagram
    participant CLI as rift CLI
    participant Mgr as Manager
    participant Reg as SQLite registry
    participant Strat as Strategy
    participant Git as git module
    participant Hook as hook runner

    CLI->>Mgr: create_with_options(from, name, into, options)
    Mgr->>Mgr: resolve source via upward .rift search
    Mgr->>Git: check_source(from)
    Mgr->>Mgr: resolve root for default storage
    Mgr->>Mgr: allocate ULID, validate name and destination
    alt HookMode::Run
        Mgr->>Mgr: load .rift.toml from source
    end
    Mgr->>Strat: copy_directory(from, dest, copy_mode)
    Strat-->>Mgr: CoW clone complete
    Mgr->>Mgr: write .rift marker (new ULID)
    alt is Git repository
        Mgr->>Git: hide_marker(dest), detach_destination(dest)
        Mgr->>Git: hide_marker(source)
    end
    Mgr->>Reg: insert_child(id, parent_id, path)
    Mgr->>Hook: run_postcreate(steps, source, dest)
    Mgr-->>CLI: destination PathBuf
    CLI-->>CLI: print path to stdout
```

On copy failure, Rift removes a partially created destination directory. On failure after copy but before registration completes, it also removes the destination.

## Parentage and identity

Each created workspace receives a new ULID written to `.rift`. The registry records `parent_id` as the immediate source workspace's id (not the root). Creating a child from another child forms a provenance chain traceable with `rift ancestors`.

```bash
rift create --name first      # parent: source root
cd "$(rift create --name second)"   # parent: first; storage still under root's .rifts/
rift ancestors                # first, then source root
```

## JavaScript API

The `rift-snapshot` package exposes the same semantics:

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

const path = create({
  from: process.cwd(),
  name: "schema-work",
  into: "/fast/rifts",
  copyAll: false,
  hooks: true,
});
```

<ResponseField name="return" type="string">
Absolute path of the created workspace.
</ResponseField>

| Option | CLI equivalent | Default |
| --- | --- | --- |
| `from` | positional `from` | Required in FFI (no cwd default in core) |
| `name` | `--name` | Random `adjective-noun` |
| `into` | `--into` | Root's `.rifts/<name>/` parent |
| `copyAll` | `--copy-all` | `false` (filtered) |
| `hooks` | inverse of `--no-hooks` | `true` |

Failures throw `RiftError` with a `code` (for example `workspace_not_initialized`, `unsafe_git`, `hook_failed`, `cow_unavailable`) and optional `path`.

## Common errors

| Situation | CLI message / error code |
| --- | --- |
| No `.rift` ancestor | `no initialized workspace found; run rift init from the root folder` |
| btrfs dir not a subvolume | `this workspace must be initialized first; run rift init from its root folder` |
| Destination already exists | `rift directory already exists: <path>` |
| Destination inside source | `cannot copy a workspace into itself: <path>` |
| Invalid `--name` | `invalid rift name: <name>` |
| Cross-filesystem `--into` | `copy-on-write cloning unavailable: ...` |
| Unsafe Git state | `unsafe Git source: ...` |
| Invalid `.rift.toml` (hooks on) | `invalid rift config at <path>: ...` |
| Hook command failed | `postcreate hook failed at <path>: <command> ...` |

<Check>
**Verify success:** stdout is a single absolute path; the path contains a `.rift` file with a new ULID; `rift list` from the source includes the child; copied project files are present (filtered or exact per flags).
</Check>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Initialize a source workspace, create a filtered child, list it, and remove it with expected stdout signals.
</Card>
<Card title="Copy strategies and platforms" href="/copy-strategies">
Platform-specific copy-on-write backends, filtered vs exact modes, and unsupported platforms.
</Card>
<Card title="Storage layout" href="/storage-layout">
Default `.rifts` sibling storage, custom `--into` paths, and trash relocation naming.
</Card>
<Card title="Git integration" href="/git-integration">
Detached HEAD behavior, marker exclusion, unsafe source states, and preserved index semantics.
</Card>
<Card title="Postcreate hooks" href="/postcreate-hooks">
`.rift.toml` v1 schema, hook environment variables, sequential execution, and failure behavior.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `rift` subcommands, flags, defaults, and exit codes.
</Card>
</CardGroup>
