Skip to content

Monorepo Setup

Monorepos bring multiple services, libraries, and applications into a single repository. Unfault handles this by analyzing from whatever directory you run it in.

Unfault analyzes from the current directory down:

Terminal window
# Analyze everything from repo root
cd my-monorepo
unfault review
# Analyze just the payments service
cd services/payments
unfault review

This is the primary workflow. Navigate to what you want to analyze, then run the review.

Each service in a monorepo can have its own configuration. Unfault looks for config in these locations:

  1. Current directory’s manifest file (pyproject.toml, Cargo.toml, package.json)
  2. Parent directories up to the repo root
  3. unfault.toml if no manifest exists
my-monorepo/
├── services/
│ ├── payments/
│ │ ├── pyproject.toml # Payments-specific config
│ │ └── src/
│ └── users/
│ ├── pyproject.toml # Users-specific config
│ └── src/
└── pyproject.toml # Root config (optional)

Each pyproject.toml can have its own [tool.unfault] section:

services/payments/pyproject.toml
[tool.unfault]
dimensions = ["stability", "security"] # Payments cares most about these
[tool.unfault.rules]
exclude = ["python.http.missing_circuit_breaker"] # Handled at gateway

For settings that apply everywhere, put them in the root config:

# pyproject.toml (root)
[tool.unfault]
dimensions = ["stability", "correctness", "performance"]
[tool.unfault.rules]
exclude = [
"python.missing_structured_logging:scripts/*", # Scripts everywhere
]

Child directories inherit from parent configs and can override specific settings.

Monorepos often contain multiple languages. Unfault detects and analyzes each:

my-monorepo/
├── services/
│ ├── api/ # Python (FastAPI)
│ ├── worker/ # Go
│ └── gateway/ # Rust
├── frontend/ # TypeScript
└── scripts/ # Python
Terminal window
# Analyze everything, all languages
cd my-monorepo
unfault review
# Just the backend services
cd services
unfault review
# Just the API
cd services/api
unfault review
.github/workflows/unfault.yml
name: Unfault Review
on:
pull_request:
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed services
id: changed
run: |
# Get unique service directories that changed
SERVICES=$(git diff --name-only origin/main | grep -E '^services/' | cut -d/ -f1-2 | sort -u)
echo "services=$SERVICES" >> $GITHUB_OUTPUT
- name: Install Unfault
run: |
mkdir -p ~/.local/bin
curl -L -o ~/.local/bin/unfault https://github.com/unfault/cli/releases/latest/download/unfault-x86_64-unknown-linux-gnu
chmod +x ~/.local/bin/unfault
- name: Review changed services
if: steps.changed.outputs.services != ''
run: |
for service in ${{ steps.changed.outputs.services }}; do
echo "Reviewing $service..."
cd $service
unfault review
cd -
done

For large monorepos, analyze services in parallel using a matrix:

jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.detect.outputs.services }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: detect
run: |
SERVICES=$(git diff --name-only origin/main | grep -E '^services/' | cut -d/ -f1-2 | sort -u | jq -R -s -c 'split("\n") | map(select(. != ""))')
echo "services=$SERVICES" >> $GITHUB_OUTPUT
review:
needs: detect-changes
if: needs.detect-changes.outputs.services != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
steps:
- uses: actions/checkout@v4
- run: |
mkdir -p ~/.local/bin
curl -L -o ~/.local/bin/unfault https://github.com/unfault/cli/releases/latest/download/unfault-x86_64-unknown-linux-gnu
chmod +x ~/.local/bin/unfault
- name: Review service
working-directory: ${{ matrix.service }}
run: unfault review

The code graph understands cross-service relationships when imports or calls cross boundaries:

Terminal window
# What depends on a file in the shared library?
unfault graph impact libs/common/utils.py
# Find external dependencies of the API service
cd services/api
unfault graph deps src/main.py

This helps understand how changes propagate across service boundaries.

my-monorepo/
├── libs/
│ └── common/ # Shared utilities
└── services/
├── api/ # Uses libs/common
└── worker/ # Uses libs/common

Analyze the library to see impact across consumers:

Terminal window
unfault graph impact libs/common/core.py
services/public-api/pyproject.toml
[tool.unfault]
dimensions = ["security", "stability", "performance"]
# services/internal-worker/pyproject.toml
[tool.unfault]
dimensions = ["correctness", "stability"]
[tool.unfault.rules]
exclude = ["python.http.*"] # Worker doesn't expose HTTP
# Root pyproject.toml
[tool.unfault.rules]
exclude = [
"*:scripts/*",
"*:tools/*",
"*:examples/*",
"*:**/tests/*",
]

Narrow the scope by running from a subdirectory:

Terminal window
# Instead of the whole monorepo
cd my-monorepo
unfault review
# Analyze just what you're working on
cd services/api
unfault review

Make sure you’re running from the same directory in both environments. The working directory determines what gets analyzed.

Use verbose mode to see what’s happening:

Terminal window
unfault review -v

This shows which config files are being loaded and what settings are applied.

CI/CD Integration

Full CI/CD setup guide. Read more

Configuration

All configuration options. Read more

Suppressing Rules

Customize per-service rules. Read more