Fabrik User Guide

Fabrik is an automated Claude Code SDLC driver powered by GitHub Issues and Projects. This guide covers everything you need to set up, configure, and use Fabrik effectively.

For a quick overview see the README. For details on the internal stage lifecycle, see Stage Lifecycle.


Table of Contents

  1. Getting Started
  2. Configuration Reference
  3. Workflow Patterns
  4. Stage Reference
  5. Plugin & Skills
  6. Labels Reference
  7. TUI Dashboard
  8. Observability
  9. Troubleshooting

1. Getting Started

Prerequisites

Initial Setup

Option A: Install binary (requires gh)

# Requires: gh auth login (with access to shadoworg/fabrik)
cd ~/bin  # or any directory on your PATH
gh release download --repo shadoworg/fabrik \
  --pattern "fabrik_*_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').tar.gz" \
  -O - | tar xz
# Platform-specific alternatives:
#   darwin/arm64:  --pattern "fabrik_*_darwin_arm64.tar.gz"
#   darwin/amd64:  --pattern "fabrik_*_darwin_amd64.tar.gz"
#   linux/amd64:   --pattern "fabrik_*_linux_amd64.tar.gz"
#   linux/arm64:   --pattern "fabrik_*_linux_arm64.tar.gz"

Option B: Build from source (requires Go)

# Build
go build -o fabrik .

Then initialize:

# Initialize stage configs, plugin, and config template
./fabrik init
# Creates:
#   .fabrik/stages/       — stage YAML configs
#   .fabrik/plugin/       — Claude Code plugin
#   .fabrik/config.yaml   — project config template (edit this)
# Updates:
#   .git/info/exclude     — adds .fabrik/repos/, .fabrik/worktrees/, .fabrik/debug/,
#                           and .fabrik/history.json so they don't appear as untracked
#                           in git status (local excludes, not committed to the repo)

Pass your GitHub Project URL to auto-populate owner, project, and owner_type in config.yaml. The --user flag enables fully non-interactive setup:

./fabrik init --user you https://github.com/orgs/your-org/projects/5
# or for a personal project:
./fabrik init --user you https://github.com/users/you/projects/3

Without a URL, fabrik init behaves as before: if your terminal is interactive it prompts for owner, repo, project number, and username; otherwise it writes a fully-commented template for you to fill in.

To refresh plugin skills without touching stages or config (e.g., after upgrading Fabrik):

./fabrik upgrade

Edit .fabrik/config.yaml with your project settings and commit it to git. Add your GitHub token to a gitignored .env file:

# .env (gitignored — keep secrets here)
FABRIK_TOKEN=ghp_...

Create a Project Board

Create a GitHub Project (v2) for your repository. Add board columns that correspond to your stage names – the column name must match the name field in each stage YAML file exactly (case-sensitive). The default pipeline uses:

Backlog -> Specify -> Research -> Plan -> Implement -> Review -> Validate -> Done

First Run

With settings in .fabrik/config.yaml:

./fabrik

Or pass settings as flags:

./fabrik \
  --owner your-org \
  --repo your-repo \
  --project 1 \
  --user your-github-username

Fabrik polls the project board every 30 seconds by default (configurable with --poll or poll: in config.yaml). Move an issue to Specify on the board to start processing it.

Git Repositories and Worktrees

Fabrik always bare-clones each managed repository on first access. Run Fabrik from any directory (no need to be inside a git checkout of a managed repo):

mkdir ~/my-fabrik-dir && cd ~/my-fabrik-dir
fabrik init
./fabrik --owner myorg --project 5 --user me
# No --repo needed — Fabrik processes all repos on the board

Fabrik always bare-clones each managed repository on first access:

.fabrik/repos/ is excluded from git tracking by default (gitignored). Bare clone directories can be very large and should not be committed to your repository. When you run fabrik init inside a git repo, these paths are also added to .git/info/exclude automatically.

To restrict processing to a single repository, pass --repo owner/repo.

The .fabrik/ directory (config, stages, plugin) always lives in the directory where you run fabrik.

Auto-upgrade

The --auto-upgrade flag enables Fabrik to upgrade itself when idle. After 2 consecutive idle polls, Fabrik checks the GitHub Releases API for a newer version. If one is found, it downloads the new binary, replaces the running executable, runs fabrik upgrade to refresh plugin skills, and re-execs itself.

./fabrik --auto-upgrade --owner your-org --repo your-repo --project 1 --user you

2. Configuration Reference

Settings Overview

Fabrik resolves settings in this order (highest to lowest priority):

CLI flag  >  shell env var  >  .env file  >  .fabrik/config.yaml  >  built-in default

Use .fabrik/config.yaml for non-secret project settings (commit it to git). Use .env for secrets only (FABRIK_TOKEN / GITHUB_TOKEN).

.fabrik/config.yaml

Generated by fabrik init. Commit this file — it carries project settings with the repo.

# .fabrik/config.yaml
owner: your-org
# repo: your-repo  # omit for multi-repo mode (processes all repos on the board)
project: 1
user: your-github-username

# Optional settings (defaults shown):

# Path to stage YAML configs directory.
# stages: ./.fabrik/stages

# Polling interval in seconds. Lower values are more responsive; higher values
# reduce GitHub API usage. Tradeoff: 10s is very responsive but consumes ~360 REST
# requests/hour; 30s (default) is a good balance.
# poll: 30

# Maximum number of parallel Claude sessions. Tune based on your API tier capacity.
# Each active session counts against your Anthropic API concurrency limit.
# max_concurrent: 5

# Maximum stage failures before pausing an issue. When exceeded, fabrik:paused and
# stage:<name>:failed labels are applied. Set to 0 for unlimited retries.
# max_retries: 3

# Auto-advance issues through stages without human card moves on the board.
# When true, completed issues advance to the next stage automatically.
# Per-stage auto_advance: in stage YAML can override this setting per-stage.
# yolo: false

# Self-upgrade from origin/main when idle. After 2 consecutive idle polls, Fabrik
# checks for new commits, rebuilds the binary, runs fabrik upgrade, and re-execs.
# Intended for the self-evolving workflow where Fabrik develops itself.
# auto_upgrade: false

# Disable the interactive TUI dashboard (enabled by default when a real terminal is detected).
# tui: false

# Save raw Claude output to .fabrik/debug/ for diagnosing unexpected behavior
# or prompt issues. Files are named by issue number and stage.
# debug_output: false

# Project version shown in the TUI footer. Auto-inferred from package.json,
# go.mod (returns module path, not semver), Cargo.toml, or pyproject.toml.
# Set explicitly to override auto-inference (e.g., version: "1.2.0").
# version: ""

Multi-repo mode: When repo: is commented out or omitted, Fabrik processes issues from all repositories on the project board. Use this when your project board spans multiple repos (cross-org collaborations, monorepos with independent sub-repos, or a single board managing several distinct services). To restrict Fabrik to one repository, uncomment and set repo:.

Note: .fabrik/config.yaml should NOT be listed in .gitignore. Fabrik warns (non-fatal) if it is.

.env File

Keep only secrets here. Fabrik refuses to start if .env exists but is not gitignored.

# .env (gitignored)
FABRIK_TOKEN=ghp_...         # Preferred token env var
GITHUB_TOKEN=ghp_...         # Fallback token env var

For per-developer identity overrides (when your username differs from config.yaml):

FABRIK_USER=my-personal-username

Command-Line Flags

Flag Description Default
--owner GitHub repo owner (org or user) required
--repo GitHub repo name; omit to enable multi-repo mode (processes all repos on the board) optional
--project GitHub Project (v2) number required
--user Your GitHub username – only processes comments by this user required
--token GitHub API token $GITHUB_TOKEN
--stages Directory containing stage YAML configs ./.fabrik/stages
--yolo Auto-advance issues through stages without human approval false
--auto-upgrade When idle, self-upgrade from origin/main false
--notui Disable the interactive TUI dashboard TUI on by default
--plugin-dir Path to Fabrik plugin directory (overrides .fabrik/plugin/) auto-detected
--poll Poll interval in seconds 30
--max-concurrent Maximum number of concurrent issue workers 5
--max-retries Max failed stage attempts before pausing the issue (0 = unlimited) 3
--debug-output Save Claude stage output to .fabrik/debug/ false

Environment Variables

Variable config.yaml key Description Default
FABRIK_TOKEN (secrets only) GitHub personal access token (preferred) required
GITHUB_TOKEN (secrets only) GitHub personal access token (fallback) required
FABRIK_OWNER owner GitHub repo owner
FABRIK_REPO repo GitHub repo name; optional — omitting enables multi-repo mode (all repos on the board)
FABRIK_PROJECT_NUMBER project GitHub Project (v2) number
FABRIK_USER user Your GitHub username
FABRIK_STAGES stages Stage configs directory ./.fabrik/stages
FABRIK_YOLO yolo Auto-advance (true/1/yes) false
FABRIK_POLL poll Poll interval in seconds 30
FABRIK_MAX_CONCURRENT max_concurrent Max parallel Claude sessions 5
FABRIK_MAX_RETRIES max_retries Max retries before pausing (0 = unlimited) 3
FABRIK_AUTO_UPGRADE auto_upgrade Self-upgrade when idle (true/1/yes) false
FABRIK_TUI tui Disable TUI dashboard (false/0/no) true
FABRIK_PLUGIN_DIR (no config.yaml key) Override plugin directory .fabrik/plugin/
FABRIK_DEBUG_OUTPUT debug_output Save raw Claude output for debugging false
FABRIK_REVIEW_WAIT_TIMEOUT (no config.yaml key) Minutes to wait for all requested PR reviewers to submit before auto-advancing (positive integer; invalid or unset values default to 15) 15

Token precedence: --token flag > FABRIK_TOKEN > GITHUB_TOKEN

Stage YAML Reference

Each stage is a YAML file in your stages directory. The filename is arbitrary; the name field determines which board column it matches.

name: Research            # Required. Must match a Project board column exactly.
order: 2                  # Required. Lower values processed earlier in the pipeline.
skill: fabrik-research    # Plugin skill to use (recommended; alternative to inline prompt).
                          #   When set, Fabrik sends a minimal directive and Claude loads
                          #   the skill methodology via the plugin system.
comment_skill: fabrik-research-comment  # Plugin skill for comment review (overrides comment_prompt).
prompt: |                 # Inline prompt (used when skill is not set; legacy but still supported).
  You are a research agent...
comment_prompt: |         # Inline comment-review prompt (used when comment_skill is not set).
  You are reviewing user comments...
model: sonnet             # Optional. Claude model: "opus", "sonnet", etc.
max_turns: 50             # Optional. Max conversation turns per main stage invocation.
comment_max_turns: 15     # Optional. Max turns when processing user comments. Defaults to
                          #   min(max_turns, 15) when max_turns is set, otherwise 15.
                          #   Keeps comment processing bounded independently of stage budget.
read_only: true           # Optional. Stashes the dirty worktree before Claude runs and
                          #   restores it after. Use for analysis stages that should not
                          #   modify files (e.g., Specify, Research).
update_issue_body: false  # Optional. Allow FABRIK_ISSUE_UPDATE_BEGIN/END markers in Claude
                          #   output to update the issue body. By convention only Specify
                          #   sets this to true.
post_to_pr: true          # Optional. Routes detailed Claude output to the linked PR; a
                          #   brief summary is still posted on the issue. Falls back to
                          #   posting on the issue if no linked PR is found.
create_draft_pr: true     # Optional. Pushes the branch and creates a draft PR *before*
                          #   Claude runs. Idempotent if a PR already exists.
mark_pr_ready_on_complete: true  # Optional. Marks the draft PR as review-ready after the
                                 #   stage completes. Triggers external review bots.
auto_advance: false       # Optional. Per-stage override for the global yolo setting.
                          #   true  = always auto-advance this stage (even if yolo: false)
                          #   false = never auto-advance this stage (even if yolo: true)
                          #   omit  = inherit the global yolo setting
cleanup_worktree: false   # Optional. Removes the issue worktree instead of invoking Claude.
                          #   Use for terminal stages (e.g., Done) where no further work
                          #   is needed on the branch.
wait_for_reviews: false   # Optional. When true and auto-advance is active, Fabrik waits for
                          #   all requested PR reviewers to submit before advancing the issue.
                          #   Controlled by FABRIK_REVIEW_WAIT_TIMEOUT (default 15 minutes).
                          #   See §3 Pending Reviewer Gate for full details.
allowed_tools:            # Optional. Restrict Claude's available tools.
  - Read
  - Grep
  - Glob
completion:
  type: claude            # Only supported type (default).

Either skill or prompt is required (unless cleanup_worktree is true). When skill is set, Fabrik sends a directive prompt telling Claude to follow the named skill; the skill provides the detailed methodology via the plugin system. Prefer skill for complex stages — it supports rich methodology, quality checklists, and scope boundaries. Use prompt for simple single-purpose stages or quick overrides.

auto_advance and yolo interaction:

There are four cases:

  1. Global yolo: true in config.yaml → all stages auto-advance after completion.
  2. Per-stage auto_advance: true in stage YAML → this stage always auto-advances, regardless of whether global yolo is true or false.
  3. Per-stage auto_advance: false in stage YAML → this stage never auto-advances, even when global yolo: true. This is a meaningful override — explicitly setting false is different from omitting the field.
  4. Per-stage auto_advance: absent from stage YAML → the stage inherits the global yolo setting.

A fifth case applies at the issue level: adding the fabrik:yolo label to an issue forces auto-advance for that issue even when auto_advance: false is set in the stage YAML. The fabrik:yolo label also triggers auto-merge of the linked PR when the Validate stage completes (equivalent to running with --yolo globally, but scoped to a single issue).


3. Workflow Patterns

How Issues Move Through the Pipeline

  1. Create an issue and add it to your GitHub Project board.
  2. Move the issue to a stage column (e.g., Specify).
  3. Fabrik picks it up on the next poll, creates a worktree, and invokes Claude Code.
  4. Claude works in the worktree and posts progress as issue comments.
  5. When Claude completes the stage, the stage:<name>:complete label is applied.
  6. In --yolo mode, the issue is automatically moved to the next stage column. Otherwise, a human reviews and drags the card.

The Specify Stage

The Specify stage is the first active stage. It takes a rough backlog issue and refines it into a clear, unambiguous spec:

The user answers questions via comments. Claude incorporates the answers and updates the issue body. Once all questions are resolved, the stage completes and the issue is ready for Research.

Steering with Comments

Fabrik responds to natural language comments you post on an issue. Claude sees the full issue body and all prior comments, so context carries forward.

Effective comment patterns:

When you post a comment:

  1. Fabrik reacts with eyes to acknowledge the comment.
  2. Claude is invoked with the stage’s comment prompt (or a default).
  3. Claude performs any requested actions.
  4. If the issue body should be updated, Claude outputs the new body between FABRIK_ISSUE_UPDATE_BEGIN and FABRIK_ISSUE_UPDATE_END markers.
  5. Fabrik updates the issue body and strips the markers from the posted comment.
  6. Fabrik reacts with rocket to mark the comment as processed.

The rocket reaction is durable – on restart, Fabrik skips comments that already have it.

Reaction Flow

Reaction Meaning
Eyes Comment received and queued for processing
Rocket Comment has been fully processed

When to Intervene

You do not need to babysit the pipeline. The intended human role is:

Draft PR Workflow

The Implement stage creates a draft PR linked to the issue. This gives you a place to review incrementally. The Review stage then rebases, reviews, fixes, and pushes – turning the draft into a review-ready PR.

Retry and Escalation

When a stage doesn’t complete (Claude doesn’t output FABRIK_STAGE_COMPLETE):

  1. Cooldown: Fabrik waits poll_interval x 10 seconds (default 5 minutes) before retrying.
  2. Resume: On retry, Claude resumes the existing conversation session with full context. The worktree is left as-is (no rebase) to preserve Claude’s context.
  3. WIP commit: Partial work is committed and pushed to preserve progress.
  4. Max retries: After --max-retries failures (default 3):
    • fabrik:paused and stage:<name>:failed labels are added
    • An explanatory comment is posted on the issue
    • The issue stops being processed until a human investigates

To resume after escalation: remove the fabrik:paused label. Fabrik will clear the failed label, reset the retry count, and try again immediately.

Stages Waiting for Input

A stage can signal that it needs user input before it can proceed by outputting FABRIK_BLOCKED_ON_INPUT. This is different from a failure — the stage is not broken, it just needs a question answered.

When a stage outputs FABRIK_BLOCKED_ON_INPUT:

  1. fabrik:paused and fabrik:awaiting-input labels are added to the issue
  2. The retry counter is not incremented — this does not count as a failure
  3. The issue waits silently until the configured Fabrik user (--user / FABRIK_USER) posts a new comment

When the configured user posts a new comment:

  1. Fabrik detects the comment and automatically removes both labels
  2. Comment processing is triggered immediately (no manual card move needed)
  3. The comment processing run can output FABRIK_STAGE_COMPLETE to finish the stage directly, without needing an additional stage invocation

This is the intended mechanism for Q&A in stages like Specify — Claude asks a question, the configured user answers it in a comment, and the stage resumes automatically.

Dependency-Based Sequencing (Formations)

Fabrik supports dependency-based sequencing of issues using GitHub’s native “Blocked by” relationships. This enables formations — coordinated sets of issues that execute in parallel where possible and respect ordering constraints automatically.

Setting Up Dependencies

To mark one issue as blocked by another on GitHub.com:

  1. Open the issue in GitHub
  2. In the right sidebar, find the Relationships section
  3. Click “Mark as blocked by”
  4. Search for and select the blocking issue (same repo or cross-repo)

Repeat for each dependency. This uses GitHub’s native blockedBy GraphQL field (available on all GitHub plans since August 21, 2025).

How Fabrik Detects and Respects Dependencies

Fabrik uses the fabrik:blocked label to track blocked issues. The label lifecycle is fully automatic:

  1. Detection: On each poll, issues with fabrik:blocked are deep-fetched every cycle to detect unblocking promptly (within one poll interval, typically ~30 seconds).
  2. First block: When Fabrik first detects that an issue is blocked, it posts a comment listing the open blocking issues and adds the fabrik:blocked label automatically. Fabrik creates this label on first use — no pre-creation needed.
  3. While blocked: The issue is skipped silently each poll cycle (no duplicate comments).
  4. Automatic unblocking: When all blocking issues are closed, Fabrik removes fabrik:blocked and resumes processing on the next poll — no human action required.

Key behavior callouts:

Formation Recipe

  1. File all issues for the formation. Write specs at the right level of granularity — each issue should be independently implementable.
  2. Add blocked-by edges in GitHub using the Relationships sidebar (see above).
  3. Label all issues fabrik:yolo — this makes the formation hands-free. Without fabrik:yolo, each stage requires a manual card move on the project board.
  4. Move all issues to Specify on the project board. Fabrik will pick them up on the next poll.
  5. Watch it run — Fabrik executes Specify for all issues in parallel, then gates subsequent stages on dependency resolution automatically.

Example Formation

graph TD
    A["#1 Data model design"] --> C["#3 API layer"]
    B["#2 Auth design"] --> C
    C --> D["#4 Frontend integration"]
    C --> E["#5 Background jobs"]

In this 5-issue formation:

Real-world validation: A 9-issue formation with 7 dependency edges was run on the Ambient project — 4 issues started in parallel, all pipeline constraints were respected automatically, completing in ~88 minutes wall-clock time at $31 total cost.

Issue Decomposition

When Plan determines that an issue is too broad for a single Implement cycle, it can autonomously split it into focused sub-issues — each small enough to implement cleanly and independently.

No user configuration required. Decomposition is Plan’s judgment call. If the issue is well-scoped, Plan produces a normal implementation plan. If it’s too broad, Plan decomposes it.

What Happens

  1. Plan creates sub-issues via gh CLI, labels them fabrik:sub-issue, and adds them to the project board.
  2. Plan sets up “blocked by” edges between sub-issues where sequential ordering is required.
  3. Plan outputs FABRIK_DECOMPOSED and stops — no FABRIK_STAGE_COMPLETE is emitted.
  4. The engine adds stage:Plan:complete to the parent issue and moves it to Done.
  5. Sub-issues appear in Research and flow through the full pipeline independently.

Sub-issues form a formation automatically — dependency edges set by Plan are enforced during execution.

What You Observe

Depth Limit

Sub-issues (those labeled fabrik:sub-issue) are never decomposed further. If Plan encounters an issue with that label, it produces a normal implementation plan regardless of scope. Maximum decomposition depth is 1.

Pending Reviewer Gate

When a stage has wait_for_reviews: true set and auto-advance is active (global yolo: true, per-stage auto_advance: true, or the fabrik:yolo label on the issue), Fabrik waits for all requested PR reviewers to submit their reviews before advancing the issue to the next stage.

Enabling the Gate

Add wait_for_reviews: true to the relevant stage YAML (typically Review or Validate):

name: Review
order: 4
wait_for_reviews: true
...

The gate only fires when auto-advance is active. If you’re manually dragging cards through the board, the gate has no effect — you control the timing.

Label Lifecycle

When the gate is active, Fabrik adds the fabrik:awaiting-review label to the issue. This label:

Timeout Configuration

Set FABRIK_REVIEW_WAIT_TIMEOUT to the number of minutes Fabrik should wait before giving up and advancing anyway. Must be a positive integer; invalid or unset values default to 15 minutes. There is no way to disable the timeout entirely — the minimum value is 1 minute.

FABRIK_REVIEW_WAIT_TIMEOUT=30  # Wait up to 30 minutes for reviewers

Two-Phase Mechanism

The gate uses a two-phase design to handle the propagation delay between when Fabrik requests reviewers and when GitHub’s API returns them in the PR data:

  1. Phase 1 (always-gate): On stage completion, Fabrik immediately adds fabrik:awaiting-review and skips auto-advance. This fires even before reviewer assignments propagate.
  2. Phase 2 (catch-up): On subsequent poll cycles, Fabrik re-fetches the PR with fresh GraphQL data and evaluates whether all requested reviewers have submitted. When they have (or the timeout elapses), the gate clears and auto-advance proceeds.

This means there is always at least one extra poll cycle delay after stage completion — typically 30 seconds.

Restart Persistence

The timeout is based on the timestamp of when the fabrik:awaiting-review label was added to the issue, which is stored in GitHub’s event history. If Fabrik restarts while waiting, it recalculates the remaining wait time from the label timestamp rather than resetting the clock.


4. Stage Reference

Default Pipeline

Stage Order Purpose
Backlog Parking lot. No stage config needed.
Specify 0 Refine rough issues into clear, unambiguous specs.
Research 1 Explore codebase, surface technical findings and questions.
Plan 2 Design implementation approach with task checklist.
Implement 3 Write code and tests, commit frequently, push to branch.
Review 4 Rebase, review, fix issues, push. Posts output on PR.
Validate 5 Run tests, verify requirements, confirm PR is ready.
Done 99 Terminal state. Cleanup stage removes worktree. Item remains on the board.

Done Stage and Archiving

When an issue reaches Done, Fabrik:

  1. Removes the worktree — frees disk space from the issue’s working copy
  2. Adds stage:Done:complete — marks cleanup as finished

Note: Auto-archive is currently disabled. It was removing items from the board before users could see them, and is being reworked to track actual Done stage completion time. Items will remain in the Done column until auto-archive is re-enabled in a future release. When re-enabled, archived items will not be deleted — they will remain accessible via the project board’s “Archive” view in GitHub.

Customizing Stages

# Initialize with defaults
./fabrik init

# Edit stage configs and skills
vim .fabrik/stages/research.yaml
vim .fabrik/plugin/skills/fabrik-research/SKILL.md

# Or point to a custom stages directory
./fabrik --stages ./my-custom-stages ...

You can add, remove, or reorder stages. Stages must have name, order, and either skill or prompt. The name must match a board column and order values define the sequence.


5. Plugin & Skills

How Skills Work

Each stage references a skill – a markdown file that contains detailed methodology for how Claude should approach the stage. Skills are packaged as a Claude Code plugin in .fabrik/plugin/ and loaded via --plugin-dir on each Claude invocation.

The default skills are:

Skill Purpose
fabrik-specify Requirements clarification, consistency checks, prior art research; preserves the Problem section verbatim (never compressed) and places it first in the spec
fabrik-research Codebase exploration, technical analysis, constraint discovery; includes a Documentation Impact section for user-facing features
fabrik-plan Implementation design, task checklist, decision documentation; includes explicit doc update tasks in the checklist for user-facing features
fabrik-implement Code writing, testing, committing, pushing; updates USER_GUIDE.md and/or README.md in the same PR for user-facing features — never defers documentation to a follow-up issue
fabrik-review Code review, fix issues, rebase, prepare PR
fabrik-validate Final verification, test suite, requirements check

Customizing Skills

Skills live in .fabrik/plugin/skills/<skill-name>/SKILL.md. Edit them to change how Claude approaches each stage. Changes take effect on the next Claude invocation.

The stage YAML references the skill by name:

skill: fabrik-research    # loads .fabrik/plugin/skills/fabrik-research/SKILL.md

Skill vs Prompt

When skill is set, Fabrik sends a minimal directive:

You are operating as the Fabrik Research agent for issue #42.
Follow the instructions in the fabrik-research skill exactly.

The skill is auto-loaded by Claude Code via the plugin system.

Built-in Skill: /cut-release

The /cut-release skill automates the Fabrik release process — pre-flight checks, release notes, commit/tag/push, and documentation issue filing — in a single invocation.

Invocation

/cut-release              # Auto-suggests next patch bump; confirms version with you
/cut-release v0.0.12      # Uses the explicit version; no confirmation prompt

Pre-flight checks

Before doing anything, /cut-release:

  1. Verifies the working tree is clean (git status --porcelain). Stops if dirty.
  2. Pulls latest main (git pull origin main).
  3. Runs go build ./... and go test -race ./.... Stops if either fails.

The build/test gate is mandatory — broken releases are worse than delayed ones.

Version determination

Change gathering

Runs git log <last-tag>..HEAD --oneline and groups commits into four categories:

Category What goes here
Features New user-facing capabilities
Fixes Bug fixes
Improvements Enhancements to existing features
Internal Refactoring, test improvements, CI — summarized briefly, not enumerated

Merge commits and Co-Authored-By lines are ignored.

Release notes format

Writes release-notes.md in the repo root:

# Fabrik <version>

## Features
- Description of feature (#issue)

## Fixes
- Description of fix (#issue)

## Improvements
- Description of improvement (#issue)

## Internal
- Summary of internal changes

## Upgrading

\```bash
# Auto-upgrade from a running Fabrik instance
# Fabrik checks for new releases each poll cycle and upgrades automatically with --auto-upgrade

# Or download directly
gh release download --repo shadoworg/fabrik --pattern '*<os>_<arch>*' -O - | tar xz
\```

Empty category sections are omitted. The release-notes.md file must be committed before the tag push because the GitHub Actions release workflow (release.yml) uses it to populate the GitHub Release body.

Commit, tag, and push

Once the release notes are written, /cut-release proceeds without a confirmation prompt (pre-flight already passed):

  1. git add release-notes.md
  2. Commits with message: Release notes for <version>
  3. Creates the tag: git tag <version>
  4. Pushes branch and tag together: git push origin main <version>
  5. Reports the triggered GitHub Actions run URL.

Never force-push tags. If a tag already exists at that version, /cut-release stops and tells you.

Documentation issue

After a successful push, /cut-release automatically files a GitHub issue:

The fabrik:yolo label means Fabrik will pick it up automatically and run it through the pipeline. You don’t need to do anything else for documentation updates after cutting a release.

Built-in Skill: /audit-documentation

The /audit-documentation skill compares recently shipped features against USER_GUIDE.md, README.md, and docs/index.md, then files GitHub issues for gaps and closes existing documentation issues whose gaps are now covered.

Invocation

/audit-documentation                  # Scan issues closed in the last 30 days
/audit-documentation --since v0.0.20  # Scan issues referenced in commits since a tag

What it does

  1. Discovers source issues — finds recently closed issues using the selected mode (see below).
  2. Filters — excludes issues labeled documentation and issues that are infrastructure/tooling-only with no user-facing behavior change.
  3. Reads the docs — reads USER_GUIDE.md, README.md, and docs/index.md in full.
  4. Analyzes gaps — for each filtered issue, checks whether the feature is adequately described in the docs. Groups related issues into single gap entries. Errs on the side of filing — false positives are refined by the Specify stage; false negatives cause documentation drift.
  5. Clears the deck — for each existing open documentation issue whose gap is now clearly and completely covered, closes it with a comment.
  6. Files new gap issues — for each newly identified gap, creates a GitHub issue labeled documentation and fabrik:yolo, and places it in the Specify column on the PM board.

Modes

Date-based mode (default): Fetches closed issues via gh issue list --state closed --search "closed:>$CUTOFF_DATE" --limit 200.

Tag-based mode (--since <tag>): Two passes — extracts #NNN references from commit messages since the tag, then scans merged PRs for Closes #NNN references.

Output

After running, /audit-documentation prints a structured summary:

## Audit Documentation — Summary

**Period**: <date range or tag range scanned>

**Issues scanned**: <N>
**Issues excluded**: <N>
  - <issue#>: <title> — <reason>

**Documentation gaps found**: <N>

**New gap issues filed** (<N>):
  - #<number>: <title>

**Existing documentation issues closed** (<N>):
  - #<number>: <title>

**Existing documentation issues left open** (<N>):
  - #<number>: <title> — <reason left open>

If no gaps are found, it reports: “No documentation gaps found — docs appear current.”

Known limitations

Plugin Development

For developing the plugin itself, use --plugin-dir to point at your working copy:

./fabrik --plugin-dir ./plugin/fabrik-plugin ...

6. Labels Reference

Fabrik-Managed Labels

Label Purpose
fabrik:locked:<user> Issue being processed by this user’s instance
fabrik:editing Issue body being updated (comment processing)
fabrik:paused Processing paused (max retries exceeded or manual)
fabrik:awaiting-input Stage paused waiting for user input; auto-clears on a new comment from the configured user
fabrik:awaiting-review Issue waiting for all requested PR reviewers to submit; set when wait_for_reviews: true stage completes with auto-advance active; cleared when all reviewers submit or FABRIK_REVIEW_WAIT_TIMEOUT elapses
fabrik:blocked Issue is waiting for one or more blocking issues to close; added and removed automatically by the engine (Fabrik creates this label on first use — no pre-creation needed)
stage:<name>:in_progress Stage actively running
stage:<name>:complete Stage completed successfully
stage:<name>:failed Stage hit max retries

User-Set Labels

Label Effect
model:opus Override Claude model to Opus for this issue
model:sonnet Override Claude model to Sonnet for this issue
fabrik:paused Manually pause processing (add to pause, remove to resume)
fabrik:yolo Force auto-advance for this issue even when auto_advance: false; also triggers auto-merge of the linked PR when Validate completes
fabrik:unrestricted Pass --dangerously-skip-permissions to Claude Code for this issue only; use when an issue needs to write to paths not covered by .claude/settings.json (e.g. .claude/skills/). Caution: bypasses the permission system.

Model label precedence: model:<name> label > stage YAML model field > default.


7. TUI Dashboard

The interactive terminal dashboard is enabled by default when running in a real terminal. To disable it, use --notui:

./fabrik --notui --owner your-org --repo your-repo --project 1 --user you

Layout

The TUI shows a compact header with poll status, an In Progress pane showing active Claude sessions, and a scrollable History pane with completed jobs.

Keyboard Shortcuts

Key Action
Tab Switch focus between In Progress and History panes
Up/Down or j/k Navigate items within the focused pane
l Open fabrik watch for selected issue (live Claude output, stage tabs, PR/CI status)
enter Toggle inline detail panel for the selected item
r Resume Claude session for selected history item (history pane only, item must not be active)
Escape Close open dialogs; with no dialog open, triggers quit confirmation
n / N Cancel quit or clear-all confirmation dialogs
c Delete selected history entry
C Clear all history (with confirmation)
q Quit

Mouse support is enabled by default: click a row to select it in either pane.

What’s Displayed

In Progress: Issue number, stage name, elapsed time, issue title, latest status message.

History: Issue number, stage name, success/fail icon, duration, timestamp, turns used, cost, and issue title. Status icons:

Icon Meaning
Stage completed successfully
Stage failed (hit max retries)
? Stage blocked / awaiting user input
Stage retrying
💬 Issue has unprocessed comments

History Persistence

Job history is saved to .fabrik/history.json (project-local, alongside .fabrik/config.yaml) and restored on restart.


8. Observability

fabrik watch — Per-Issue TUI

The recommended way to monitor a running issue is fabrik watch:

fabrik watch 42
# or with explicit credentials:
fabrik watch 42 --owner myorg --repo myrepo --token ghp_...

This opens a real-time terminal UI that shows:

Keyboard shortcuts

Key Action
/ or k / j Scroll live output
g / G Jump to top / bottom of output
left / h Switch to previous stage tab
right / l Switch to next stage tab
i Open interactive Claude session in the issue’s worktree (resumes current stage)
esc Quit (same as q)
q / ctrl+c Quit

The session ID for the active Claude session is shown in the header when available.

Credentials

fabrik watch reads owner, repo, and poll interval from .fabrik/config.yaml (see §2). Without a GitHub token, it shows local log output and stage history but skips GitHub API calls (PR/CI/comments).

Log Files

Session logs are saved to ~/.fabrik/logs/issue-<N>/ as NDJSON (one JSON event per line). Each stage produces a .log file written continuously during execution — fabrik watch follows this file live. To view a log manually:

ls -lt ~/.fabrik/logs/issue-42/

# Render a completed log as human-readable text
cat ~/.fabrik/logs/issue-42/<stage>-output-<timestamp>.json | fabrik stream-filter | less -R

Logs are namespaced by repository: ~/.fabrik/logs/<owner>-<repo>/issue-<N>/.

fabrik stream-filter reads NDJSON or JSON array Claude output from stdin and renders thinking blocks, text responses, and tool calls as human-readable text.

Debug Output

Enable --debug-output to save Claude’s raw output to .fabrik/debug/ in the working directory. Useful for diagnosing prompt issues or unexpected behavior.

Rate Limit Monitoring

Fabrik reports GitHub API rate limit stats in each poll cycle (visible in the TUI header or plain-text log output):

The GraphQL query uses a two-phase fetch (shallow board scan + targeted detail fetch) to minimize rate limit consumption. Typical cost is ~5-30 points per poll depending on active items, well within the 5,000 points/hour limit.


9. Troubleshooting

Startup Board Validation Failure

On every startup, Fabrik fetches the project board and compares stage names in .fabrik/stages/*.yaml to the column names on the board. If any non-cleanup stage is missing from the board, Fabrik exits with an error listing the mismatched names.

To fix: ensure stage YAML name fields match board column names exactly (case-sensitive). If you renamed a column on the board, update the matching stage YAML. Extra board columns (with no matching stage) produce a warning but don’t block startup.

Issue Not Being Picked Up

Stage Keeps Retrying

A stage that never outputs FABRIK_STAGE_COMPLETE retries after each cooldown. Causes:

After --max-retries failures, the issue is paused with fabrik:paused. Remove the label to resume.

Stale Worktrees

Worktrees are at .fabrik/worktrees/<owner>-<repo>/issue-N/ on branch fabrik/issue-N. On each stage invocation, Fabrik rebases onto latest main (unless it’s a retry, which preserves the worktree as-is to maintain Claude’s context). If the rebase conflicts, it’s silently aborted and Claude works from the current base.

To manually clean up:

git worktree remove --force .fabrik/worktrees/<owner>-<repo>/issue-N
git branch -D fabrik/issue-N

Killed or Interrupted Sessions

Claude sessions are stored at ~/.fabrik/sessions/issue-N/<stage>.session. On retry, Fabrik resumes from the session file. The Implement stage commits frequently to minimize lost work.

To force a fresh session:

  1. Remove: rm ~/.fabrik/sessions/issue-N/<stage>.session
  2. Remove stage:<name>:complete label if incorrectly applied
  3. Fabrik starts a fresh session on next poll

Comment Reprocessed After Restart

The rocket reaction is the durable “processed” marker. If a comment gets reprocessed after restart, it means the rocket was not applied before shutdown (killed mid-flight). This is expected for in-flight comments.

Issue Body Not Updated

If you see FABRIK_ISSUE_UPDATE_BEGIN/END markers in stage comments, the markers are being processed correctly – the issue body is updated and the markers are stripped from the posted comment. If the markers appear verbatim, update Fabrik to the latest version.

Post-to-PR Output Missing

For stages with post_to_pr: true:

Raw JSON in Comments

If you see raw JSON dumped in issue/PR comments, the output was too large for the JSON parser or the format changed. Fabrik now posts an error message instead of raw JSON. Check ~/.fabrik/logs/issue-N/ for the full output.

Multi-User and Multi-Instance Operation

Multiple users and multiple Fabrik instances can safely share a project board.

Comment processing: Fabrik processes comments from any author, not just the configured --user. Human colleagues, code-review bots (Copilot, Gemini), and other Fabrik instances all trigger comment processing. Only two categories are skipped: comments that start with 🏭 **Fabrik (Fabrik’s own output) and comments that already carry a 🚀 rocket reaction (already processed).

Multi-instance locking: The fabrik:locked:<user> label is still the concurrency guard, but acquisition now uses a lock-then-verify protocol:

  1. An instance adds its fabrik:locked:<username> label.
  2. It waits 2 seconds to let a competing instance place its own lock.
  3. It re-fetches the issue’s labels.
  4. If no other fabrik:locked:* label is present → proceed.
  5. If a competing lock exists → lexicographic tie-breaking: the instance whose username sorts lower alphabetically keeps its lock and proceeds; the instance whose username sorts higher removes its lock and skips this issue for this poll cycle. On the next poll, the winner’s lock is already visible, so the loser skips cleanly without retrying.

This guarantees that exactly one instance processes each issue at a time, even when multiple instances poll simultaneously.

Plugin Not Loading

If Claude doesn’t seem to follow the skill instructions: