Fabric
You're on the list! We'll be in touch when we launch.
Please enter a valid email address.
Something went wrong. Please try again.

CLI reference

fabric is a non-interactive client: you pass everything as flags and it never stops to prompt you. By default it prints human-readable output; add --json to any command to get the same result as structured JSON for scripts and agents to parse. fabric --help and fabric <cmd> --help are always accurate: consult them rather than guessing, since the live command set is the source of truth.

Auth

CommandWhat it does
fabric signupBrowser sign-up + CLI auth in one step.
fabric loginDevice-auth in the browser; saves your credentials.
fabric login --no-browserHeadless device-code login: prints an activation URL + one-time code to approve from any browser.
fabric whoamiShow the signed-in account.

Agent principals

Agent principals are named, org-scoped identities for automated callers (CI bots, coding agents, deploy pipelines). Each agent gets its own API key, appears as a distinct participant in channels, and can be revoked independently.

CommandWhat it does
fabric agent create "<name>"Register a new agent and mint its API key. The key is printed once; capture it. The calling identity is recorded as the sponsor.
fabric agent listList the org’s agents (id, name, status).
fabric agent revoke <id-or-name>Revoke an agent and invalidate its key immediately. Accepts a UUID or the agent’s name.
fabric agent serveRun the local agent bridge daemon. Authenticates with an agent API key, follows the hub’s event stream for @-mentions, opens a session per thread, and posts replies via the channel API.

Use the minted key as FABRIC_TOKEN or pass it via --token when running the agent. The agent authenticates on every /v1 request exactly like a user key, but its identity resolves to the agent principal, so channel messages and audit entries attribute the work to the agent (with its sponsor visible).

fabric agent serve flags

FlagDefaultDescription
--agent-key(env FABRIC_AGENT_KEY)The agent’s API key. Required.
--idle-timeout30mClose idle sessions after this duration. A new mention on the same thread reopens the session.
--adapter-cmdnpxACP adapter binary or command. Override to use a different ACP-compatible adapter.
--adapter-args@zed-industries/claude-code-acpArguments for the adapter command.
--stuboffUse the built-in echo responder instead of the ACP adapter (for testing).
--jsonoffStructured JSON log output on stdout.
--hubsaved loginHub URL (same resolution as other commands).

Data directory. The bridge persists its event cursor and session state under ~/.config/fabric/agents/<agent-name>/state.json (respects FABRIC_CONFIG_DIR). On restart it resumes from the persisted cursor, so no events are reprocessed.

Agents can also be managed from the settings UI under Team > Agents.

The change loop

CommandWhat it does
fabric change create "<name>"Fork a new draft change off the current trunk frontier. Becomes the most recent change. --description "<markdown>" stores an intent statement reviewers see rendered on the change page. --ticket TIX-N links the change to the ticket it implements (the key must exist); the change page then links straight to it.
fabric cloneMaterialize the most recent change into a working directory (the repo name by default). The target must be empty: if it already holds files, clone refuses and writes nothing, so it never clobbers work on disk.
fabric clone <dir>Clone into <dir> (same as --dir <dir>). Use fabric clone --dir . to populate the current directory. A leftover .fabric/ from a prior clone does not count as content, so re-cloning an otherwise-empty dir works.
fabric clone --forceWrite the checkout even if the target is not empty. Files with the same path are overwritten; unrelated files are left in place.
fabric pullRe-materialize the tracked change’s current tip into the working dir (pick up ops others pushed). If the change is no longer live (accepted, abandoned, or deleted), pull explains which and points you at the remedy instead of failing with a bare error.
fabric statusLocal diff of your working tree vs the cloned base. No network.
fabric diffLocal detailed diff. No network.
fabric pushUpload your working tree to the change as ops. Idempotent. Does not touch trunk or merge anything. If the change is already proposed, a push re-runs its hub-side checks against the updated diff.
fabric proposeMove the change draft → proposed (open it for review). Rejected if the change’s fabric.yaml is malformed. -m "<markdown>" (alias --description) sets or replaces the change’s description in the same step; without it the existing description is kept.
fabric change describe "<markdown>"Replace the tracked change’s description: the intent statement reviewers see rendered above the diff. Pass an empty string to clear it.
fabric change ticket <TIX-N>Link the tracked change to the ticket it implements, so its page points straight at the ticket (the structured record of intent) instead of relying on a TIX-N: name prefix. The key must resolve to an existing ticket. Pass an empty string to clear the link.
fabric change link add <url>Attach a labeled URL to the tracked change, shown on its page for reviewers. The typical use is a workflow or agent posting its preview deployment URL. Only http/https URLs are accepted. --label "<text>" sets the display text (defaults to the URL’s host). Re-adding the same label + url returns the existing link instead of duplicating it.
fabric change link listList the tracked change’s links: id, label, and url, oldest first.
fabric change link remove <id>Remove one link from the tracked change by its id (from link list).
fabric approveRecord an approving review on the change. --request-changes records the opposite; --note "<text>" attaches a message.
fabric acceptLand the change into trunk through the gated pipeline. Blocked until the repo’s approval policy (set in fabric.yaml) is met. The hub merges the change onto the current trunk, runs the required checks against that exact merged result, and advances trunk only if they pass. If trunk moves while the checks run, the hub re-merges on the new tip and re-checks, so trunk only ever lands on a state a check passed against. When the trunk batches accepts (see the merge queue below), accept does not land inline: it returns queued and the change lands in the background once it passes its checks.
fabric abandonDiscard the change.

The merge queue

When a trunk batches accepts (batch_size or concurrency_window above one in queue.toml), an accepted change does not land inline. It merges into a staged candidate frontier (trunk plus the queued changes), is tested there, and advances trunk only once a check passes against it. Several changes can be trialled at once, stacked in order, and the longest passing prefix lands together. A change that fails is evicted and the ones behind it re-form without it. Trunk only ever lands on a state a check passed against.

CommandWhat it does
fabric queueShow the queue: whether the trunk batches accepts, the resolved window and batch size, the staged cut (base frontier and the candidate frontier it projects to), the ordered positions of the changes waiting to land, each candidate’s status, and the recent terminal outcomes. --json for machine-readable output. A pure read.

On a repo that never opts into batching (the zero-config default, batch size 1), the queue is always empty: each accept lands inline once its checks pass, and fabric queue says so.

Checks and runs

CommandWhat it does
fabric checkRun the change’s .github/workflows/*.yml locally in Docker and print per-job pass/fail. Fully local: nothing is sent to the hub, so it works offline. Needs Docker running.
fabric check --reportSame local run, but afterward record the result on the hub so it shows up in the runs list, tagged local. Best-effort: if the hub is unreachable the local result still stands and the command does not fail.
fabric checksShow the hub-side check runs for the change (the ones that run when you propose, push to an already-proposed change, or accept). By default it polls until they finish, re-rendering the per-job table as jobs change so you see progress, not just a final dump. A job that fails because a tool was missing from the CI runner (e.g. a workflow shells out to make and the runner has none) is shown as SETUP ERROR rather than FAILURE, so an environment gap is not mistaken for a problem with your change.
fabric checks --no-waitPrint the current check states once and exit, without polling. --once is an alias. With --json it returns the single snapshot.
fabric checks --logs <job>Download one job’s full log instead of polling, so a failed check is debuggable from the terminal without opening the web runs page. Name the job by its id (e.g. lint), its workflow / job label if two workflows share a job name, or its check-run id.
fabric checks --timeout <d>How long to keep polling with no progress before giving up (default 30m). Any job status change resets the clock, so a run that is still advancing is never abandoned. Pass 0 to wait indefinitely.

Checks only run once a change is proposed, so fabric checks on a draft change has nothing to poll: it tells you to run fabric propose first instead of blocking. If the checks make no progress for the --timeout budget, the command prints the last states it saw and exits non-zero, so you always get status even on a stuck run.

A run reported with --report is marked local in the web runs list so reviewers can tell it ran on your machine rather than on hub infrastructure. A local run is informational only: it never counts toward the required-checks gate that fabric accept enforces.

When a check fails, fabric checks names the failing step in the summary (for example Step 'Run linter' failed), and each check in the --json payload carries a web_url (the job’s block on the web runs page) and a log_url (the REST endpoint fabric checks --logs reads). Reach for --logs <job> to pull the full output and diagnose the failure in place.

The hub runner has no Docker daemon, so a workflow that needs one (a services: container, a container: job, or a Docker-based action) fails with a clear message instead of running part way. See Runner compatibility for the supported subset.

Build artifacts

A workflow step that runs actions/upload-artifact uploads named files during a run, exactly as on GitHub. Fabric captures each uploaded artifact and stores it against the run, so you can download it afterward.

Artifacts show up in two places:

  • The run-detail page on the web runs view lists every artifact the run produced, each with its size and a Download link. The bytes stream back through the hub as a .zip.
  • The API, for agents and scripts. GET /v1/vcs/runs/<run-id>/artifacts returns the run’s artifacts as JSON (id, name, size, download_url), and GET /v1/vcs/artifacts/<id>/download streams one artifact’s zip. The run-id is the run-group (batch) id you see on the runs page; both endpoints are scoped to your org, so you can only ever fetch an artifact your org owns.

Uploading uses the standard action, so no Fabric-specific workflow change is needed:

- uses: actions/upload-artifact@v4
  with:
    name: build-output
    path: dist/

Artifacts are retained alongside the run. When old runs age out of a change’s retained history, their artifacts are reclaimed with them.

Comments

Comments live on the hub, not in your clone, so these verbs are the only way to read or answer them. Read comment ids from fabric comments list --json, then pass them to the write verbs.

CommandWhat it does
fabric comments listList the comments on the files this clone pulled. Each is resolved against your clone’s base frontier, so its line number points at your working copy.
fabric comments list --path <p>Only list comments on one file.
fabric comments list --change <id>List a change’s review thread instead of the clone’s files (no clone needed).
fabric comments add --path <p> --lines A-B "<body>"Leave a comment on a line span (or a single line, --lines A).
fabric comments reply <id> "<body>"Reply to a comment thread. Nested under the thread root.
fabric comments resolve <id>Mark a comment resolved.
fabric comments unresolve <id>Reopen a resolved comment.
fabric comments react <id> --emoji 👍Toggle an emoji reaction (--off removes). Palette: 👍 👎 😄 🎉 ❤️ 🚀 👀.

list --json returns each comment’s id, anchor, body, author, thread parent_id, resolved state, outdated flag, and reactions. These are the same comments the web file viewer shows. The hub resolves each anchor for you: a comment whose target moved or vanished since it was written is marked (outdated), and a text comment that can no longer be placed shows as unplaced with its original quoted text. Comments on non-text files carry their own coordinate instead of a line: a PDF page (--page N), an audio/video moment (--time-ms N, with an optional --time-end-ms), or an image box (--region "[x,y,w,h]").

Convention: resolve a comment only after the push that addresses it has landed, and reply first with what changed, so the fix is traceable from the thread.

Tickets

Tickets are version-controlled markdown that live in your repo: each one is a file with a front-matter header (title, status, assignee, priority, labels) and a markdown body. Creating or editing a ticket records ops in the operation log, so a ticket carries its full history like any other file. The hub keeps a fast read model behind list and show.

CommandWhat it does
fabric ticket listList tickets, highest priority then most recently updated first.
fabric ticket list --status <s>Only list tickets with that status.
fabric ticket list --assignee <who>Only list tickets with that assignee.
fabric ticket show <key>Show one ticket by key (e.g. TIX-12), including its body.
fabric ticket create "<title>"Create a ticket. The hub mints its key (TIX-N).
fabric ticket edit <key> --status doneEdit a ticket. Only the flags you pass change.
fabric ticket edit <key> --blocks TIX-2,TIX-5Set the typed relations (see below).

create accepts --status (defaults to your workflow’s first state), --assignee, --priority (higher sorts first), --labels (comma-separated), and --body (markdown). edit takes the same flags plus --title; only the flags you pass are changed, so fabric ticket edit TIX-12 --status done leaves everything else as-is. Pass --labels "" to clear labels.

Relations (TIX keys in the same workspace) are set with --blocks, --blocked-by, and --related (each a comma-separated list) and --parent (a single key, making the ticket a sub-issue). On edit each relation flag REPLACES that whole relation with what you pass, so --blocks TIX-2,TIX-5 sets exactly those two and --related "" clears it. An unknown, cross-org, or self target is rejected. show prints every non-empty relation plus the sub-issues and the tickets that reference this one. Read keys from fabric ticket list --json, then feed them to show and edit.

Reporting Fabric bugs

fabric ticket files into your own tracker. When the problem is with Fabric itself (a broken runner, a CLI that misbehaves, an engine error, a missing feature), fabric report sends it upstream to the Fabric team instead, from the terminal where it happened.

CommandWhat it does
fabric report "<title>"File a report with the Fabric team. Attaches an auto-captured context envelope (see below) and returns the report id.
fabric report "<title>" --category <c>Set the triage lane: runner, cli, engine, or feature-request (default cli).
fabric report "<title>" --body "<text>"Add a body with repro steps or expected-vs-actual.
fabric report "<title>" --attach-logsAlso attach the tracked change’s last failed check run and its log (opt-in).
fabric report --from-last-errorBuild the report from the last fabric command that failed, deriving the title and body from it.
fabric report status <id>Poll a filed report’s status so you see the Fabric team’s response without leaving the terminal.

Every report carries a context envelope the CLI gathers for you, so a report is reproducible by construction rather than whatever you remember to paste: the fabric version, the hub URL, the repo id, the tracked change id, and your OS/arch. Pass --change <id> to set the change explicitly when you are not inside a clone.

Because a report leaves your machine for the Fabric team, privacy is enforced before anything is sent:

  • Secrets in any attached logs are scrubbed on your machine (tokens, keys, KEY=value assignments) before they enter the payload. The hub scrubs again as a backstop.
  • The CLI prints a preview of the exact payload (title, body, and the whole context envelope) and asks you to confirm.
  • Log attachment is opt-in (--attach-logs); nothing about your checks is sent unless you ask.
  • Under --json or a non-interactive stdin there is no prompt, so sending requires an explicit --yes. A report is never sent silently.

When fabric checks reports a failure it prints a one-line hint pointing at fabric report --from-last-error --attach-logs, so the fastest path to telling the Fabric team is right where the failure showed up.

Watching events

Instead of polling each surface, watch the hub’s event stream over a long-lived SSE connection. The hub pushes each new event the instant it lands, filtered server-side.

CommandWhat it does
fabric wait --change <id> --on '<selectors>'Block until the first event whose type matches a selector glob (comma-separated globs over the dotted vocabulary, e.g. check.*, comment.*,review.*, *), print it, then exit 0.
fabric wait --change <id> --until <outcome>Block until the change reaches a terminal outcome, then encode it in the exit code: accepted 0, rejected 2, abandoned 3.
fabric wait … --backgroundDetach the wait: print the child pid (and an output file) and return immediately, so a harness can watch that pid and wake a dormant agent when it exits.
fabric eventsStream every event continuously (--json for one object per line), for a supervising daemon that tails the whole feed. Narrow with --change <id> and filter with --on '<selectors>'.

Both commands remember the highest event cursor they delivered (saved per change) and resume from it on the next connection, so an event that landed while nothing was connected is replayed rather than missed. A first-ever wait starts live at the current tip; pass --since <cursor> to start from a specific point, or --from-start to replay the whole history. The event types and each event’s data shape are documented in the agent guide.

Onboarding & history

CommandWhat it does
fabric repo statusShow what’s currently in trunk (empty, or a file count + the most recent change). Run it before importing.
fabric import <folder>Replace the repo with an existing Git project’s full history (runs git fast-export for you). Advances trunk and relabels the repo from the folder name; prints the next step. Warns before replacing a non-empty repo.
fabric import <folder> --forceReplace a non-empty repo without the confirm prompt (required in --json / non-interactive mode).
fabric import <folder> --into <path>Nest the imported project under a subfolder.
fabric clone --at trunkRead-only checkout of the current trunk frontier (how to view an import’s result).
fabric clone --at op:<actor>:<seq>Read-only checkout at a past frontier (time-travel).

Diversion to object storage is by size only: files over the cap divert to LFS pointers; a small binary imports inline and round-trips byte-for-byte.

Keeping the CLI current

CommandWhat it does
fabric --versionPrint the installed CLI version (a short git commit on shipped builds, dev on an unstamped local build).
fabric versionSame as --version; add --json for {"version", "build"}.
fabric upgradeDownload the latest CLI this hub serves and replace the running binary in place. No login needed.
fabric upgrade --forceReinstall even when the versions already match.

fabric update is an alias for fabric upgrade. The hub cross-compiles the CLI on every deploy, so upgrading keeps you in sync with the hub you talk to. It resolves the hub from --hub, FABRIC_HUB_URL, or your saved login.

When your installed CLI differs from the build the hub serves, any command that talks to the hub prints a one-line reminder on stderr (it never touches --json output) telling you to run fabric update. That way a CLI lagging behind a newer hub, missing a command the hub already documents, tells you so instead of failing cryptically.

Conventions

  • --json on any command. Default output is human-readable text; --json gives the same result as structured data for scripting and agents.
  • Every command prints a link. Commands that act on a change end with a View: <url> line you can open to see it in the browser. Under --json the same link is in the web_url field instead.
  • Op refs are actor:seq: a frontier coordinate, not a content hash.
  • Idempotent push. Re-running push only appends what’s new; “nothing to do” means your tree already matches the change tip.
  • Clone never clobbers. fabric clone refuses a target that already holds files and writes nothing, so you never lose local work by cloning over it. Pass --force to write over it anyway (same-path files overwritten, unrelated files kept). Under --json a refusal is {error: "clone_target_not_empty", message, suggestion, dir} with a non-zero exit.
  • Stale working dir. Once the change you cloned is accepted, abandoned, or deleted, pull stops with the reason plus a next step (fabric clone --at trunk to read trunk, or fabric change create then fabric clone to keep editing). With --json the same reason rides in {error, message, suggestion}. status is offline, so it flags only a working dir whose .fabric/state.json tracks no change at all.

The definitive, always-current command set is fabric --help. This page mirrors it for reference. When in doubt, trust the CLI.