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
- Getting Started
- Configuration Reference
- Workflow Patterns
- Stage Reference
- Plugin & Skills
- Labels Reference
- TUI Dashboard
- Observability
- Troubleshooting
1. Getting Started
Prerequisites
- Go 1.26.1+
- Claude Code CLI installed and authenticated
- GitHub personal access token with
repoandprojectscopes - A GitHub Project (v2) with board columns matching your stage names
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:
- Bare clones are stored at
.fabrik/repos/<owner>-<repo>.git - Worktrees are created at
.fabrik/worktrees/<owner>-<repo>/issue-N/on branchfabrik/issue-N - One worktree manager runs per discovered repository, all sharing the same poll loop
.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:
- Global
yolo: trueinconfig.yaml→ all stages auto-advance after completion. - Per-stage
auto_advance: truein stage YAML → this stage always auto-advances, regardless of whether globalyolois true or false. - Per-stage
auto_advance: falsein stage YAML → this stage never auto-advances, even when globalyolo: true. This is a meaningful override — explicitly settingfalseis different from omitting the field. - Per-stage
auto_advance:absent from stage YAML → the stage inherits the globalyolosetting.
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
- Create an issue and add it to your GitHub Project board.
- Move the issue to a stage column (e.g.,
Specify). - Fabrik picks it up on the next poll, creates a worktree, and invokes Claude Code.
- Claude works in the worktree and posts progress as issue comments.
- When Claude completes the stage, the
stage:<name>:completelabel is applied. - In
--yolomode, 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:
- Surfaces missing requirements, ambiguities, and edge cases as questions
- Checks consistency with existing project features and documentation
- Researches prior art and established patterns on the web
- Rewrites the issue body with a structured 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:
- “Please link the PR to this issue” – Claude creates the PR link
- “Let’s use approach B instead” – Claude updates the plan and continues
- “The answer to your question about X is Y” – Claude incorporates your answer
- “Please push and link the PR” – Claude pushes the branch and creates a draft PR
When you post a comment:
- Fabrik reacts with eyes to acknowledge the comment.
- Claude is invoked with the stage’s comment prompt (or a default).
- Claude performs any requested actions.
- If the issue body should be updated, Claude outputs the new body between
FABRIK_ISSUE_UPDATE_BEGINandFABRIK_ISSUE_UPDATE_ENDmarkers. - Fabrik updates the issue body and strips the markers from the posted comment.
- 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:
- File issues with clear specs in the body (or let Specify refine them).
- Answer questions when Specify or Research surfaces unknowns.
- Move cards (or use
--yoloto automate this). - Comment to steer when the plan goes sideways or you want to redirect.
- Review PRs before merging – Fabrik gets them review-ready, not merge-ready (unless
--yoloorfabrik:yololabel is active, in which case Fabrik auto-merges after Validate).
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):
- Cooldown: Fabrik waits
poll_interval x 10seconds (default 5 minutes) before retrying. - 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.
- WIP commit: Partial work is committed and pushed to preserve progress.
- Max retries: After
--max-retriesfailures (default 3):fabrik:pausedandstage:<name>:failedlabels 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:
fabrik:pausedandfabrik:awaiting-inputlabels are added to the issue- The retry counter is not incremented — this does not count as a failure
- The issue waits silently until the configured Fabrik user (
--user/FABRIK_USER) posts a new comment
When the configured user posts a new comment:
- Fabrik detects the comment and automatically removes both labels
- Comment processing is triggered immediately (no manual card move needed)
- The comment processing run can output
FABRIK_STAGE_COMPLETEto 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:
- Open the issue in GitHub
- In the right sidebar, find the Relationships section
- Click “Mark as blocked by”
- 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:
- Detection: On each poll, issues with
fabrik:blockedare deep-fetched every cycle to detect unblocking promptly (within one poll interval, typically ~30 seconds). - First block: When Fabrik first detects that an issue is blocked, it posts a comment listing the open blocking issues and adds the
fabrik:blockedlabel automatically. Fabrik creates this label on first use — no pre-creation needed. - While blocked: The issue is skipped silently each poll cycle (no duplicate comments).
- Automatic unblocking: When all blocking issues are closed, Fabrik removes
fabrik:blockedand resumes processing on the next poll — no human action required.
Key behavior callouts:
- The first stage (Specify) always runs regardless of blockers — dependencies only gate subsequent stages. This allows a formation to be fully specified before execution begins.
- Independent issues start in parallel — issues with no blockers are dispatched concurrently up to the configured
MaxConcurrentlimit. - Failed issues retry independently — a failure in one formation member does not affect siblings.
- Cross-repo dependencies are supported — a blocking issue can be in a different repository. Fabrik displays cross-repo blockers as
owner/repo#Nin log output.
Formation Recipe
- File all issues for the formation. Write specs at the right level of granularity — each issue should be independently implementable.
- Add blocked-by edges in GitHub using the Relationships sidebar (see above).
- Label all issues
fabrik:yolo— this makes the formation hands-free. Withoutfabrik:yolo, each stage requires a manual card move on the project board. - Move all issues to Specify on the project board. Fabrik will pick them up on the next poll.
- 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:
- Issues #1 and #2 start immediately in parallel (no blockers)
- Issue #3 starts after both #1 and #2 are closed
- Issues #4 and #5 start after #3 is closed (in parallel with each other)
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
- Plan creates sub-issues via
ghCLI, labels themfabrik:sub-issue, and adds them to the project board. - Plan sets up “blocked by” edges between sub-issues where sequential ordering is required.
- Plan outputs
FABRIK_DECOMPOSEDand stops — noFABRIK_STAGE_COMPLETEis emitted. - The engine adds
stage:Plan:completeto the parent issue and moves it to Done. - 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
- The parent issue moves to Done on the project board (its label set shows
stage:Plan:complete). - New issues appear in Research, each labeled
fabrik:sub-issue. - If Plan set up blocking edges, sub-issues that depend on each other will wait as a formation.
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:
- Makes the wait state visible on the project board
- Is cleared automatically when all requested reviewers submit (approve, request changes, or comment)
- Is also cleared when the
FABRIK_REVIEW_WAIT_TIMEOUTelapses (fail-open: Fabrik advances the issue even without all reviews)
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:
- Phase 1 (always-gate): On stage completion, Fabrik immediately adds
fabrik:awaiting-reviewand skips auto-advance. This fires even before reviewer assignments propagate. - 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:
- Removes the worktree — frees disk space from the issue’s working copy
- 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
skill:references a plugin skill file (recommended). The skill contains rich methodology, quality checklists, and scope boundaries.prompt:is an inline prompt string in the YAML (legacy, still supported). Useful for quick customization but harder to maintain for complex stages.
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:
- Verifies the working tree is clean (
git status --porcelain). Stops if dirty. - Pulls latest main (
git pull origin main). - Runs
go build ./...andgo test -race ./.... Stops if either fails.
The build/test gate is mandatory — broken releases are worse than delayed ones.
Version determination
- Reads the latest tag via
git describe --tags --abbrev=0. - If you provided an explicit version, validates it: must be valid semver with a
vprefix and greater than the current tag. - If no version is provided, auto-suggests the next patch bump (e.g.
v0.0.11→v0.0.12) and confirms with you before proceeding.
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):
git add release-notes.md- Commits with message:
Release notes for <version> - Creates the tag:
git tag <version> - Pushes branch and tag together:
git push origin main <version> - 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:
- Title:
Update docs for v<version> - Labels:
documentation,fabrik:yolo - Board position: Added to the Fabrik PM project board and moved to the Specify column
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
- Discovers source issues — finds recently closed issues using the selected mode (see below).
- Filters — excludes issues labeled
documentationand issues that are infrastructure/tooling-only with no user-facing behavior change. - Reads the docs — reads
USER_GUIDE.md,README.md, anddocs/index.mdin full. - 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.
- Clears the deck — for each existing open
documentationissue whose gap is now clearly and completely covered, closes it with a comment. - Files new gap issues — for each newly identified gap, creates a GitHub issue labeled
documentationandfabrik: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
- Rate limits: May apply for repos with hundreds of closed issues.
- Tag-based mode coverage: Misses issues whose PRs used
Fixesor bare URLs instead ofCloses #NNNin the PR body.
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:
- Issue title, labels, and current stage
- Live Claude output — streams from the
.logfile as Claude writes it (no polling) - Stage history — completed stages with duration and cost
- PR status — linked PR number, open/draft/merged state
- CI check results — compact pass/fail/pending summary
- Comment count — updates on each GitHub poll
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):
- REST API: requests remaining / limit
- GraphQL API: points remaining / limit
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
- Confirm the board column name exactly matches a stage YAML
name(case-sensitive). - Confirm the issue is on the project board.
- Check for
fabrik:locked:<other-user>label (another instance has it). - Check for stuck
fabrik:editinglabel (remove manually if stale). - Check the poll log – the issue may be filtered by
updatedAtcaching. Restart Fabrik to force a fresh scan.
Stage Keeps Retrying
A stage that never outputs FABRIK_STAGE_COMPLETE retries after each cooldown. Causes:
- Claude needs user input – answer the questions in the issue comments.
- Missing context – add detail to the issue body.
- Bug in the skill – check the SKILL.md in
.fabrik/plugin/skills/.
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:
- Remove:
rm ~/.fabrik/sessions/issue-N/<stage>.session - Remove
stage:<name>:completelabel if incorrectly applied - 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:
- Confirm the issue has a linked PR (via “Development” section or
Closes #Nin PR body) - If no PR is found, output falls back to the issue
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:
- An instance adds its
fabrik:locked:<username>label. - It waits 2 seconds to let a competing instance place its own lock.
- It re-fetches the issue’s labels.
- If no other
fabrik:locked:*label is present → proceed. - 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:
- Verify
.fabrik/plugin/exists (runfabrik initif not) - Check that the stage YAML has
skill:set (notprompt:) - For development, use
--plugin-dirto point at your working copy - Verify Claude Code version supports
--plugin-dir