CI/CD Integration
Full CI/CD setup guide. Read more
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:
# Analyze everything from repo rootcd my-monorepounfault review
# Analyze just the payments servicecd services/paymentsunfault reviewThis 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:
pyproject.toml, Cargo.toml, package.json)unfault.toml if no manifest existsmy-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:
[tool.unfault]dimensions = ["stability", "security"] # Payments cares most about these
[tool.unfault.rules]exclude = ["python.http.missing_circuit_breaker"] # Handled at gatewayFor 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# Analyze everything, all languagescd my-monorepounfault review
# Just the backend servicescd servicesunfault review
# Just the APIcd services/apiunfault reviewname: 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 - doneFor 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 reviewThe code graph understands cross-service relationships when imports or calls cross boundaries:
# What depends on a file in the shared library?unfault graph impact libs/common/utils.py
# Find external dependencies of the API servicecd services/apiunfault graph deps src/main.pyThis helps understand how changes propagate across service boundaries.
my-monorepo/├── libs/│ └── common/ # Shared utilities└── services/ ├── api/ # Uses libs/common └── worker/ # Uses libs/commonAnalyze the library to see impact across consumers:
unfault graph impact libs/common/core.py[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:
# Instead of the whole monorepocd my-monorepounfault review
# Analyze just what you're working oncd services/apiunfault reviewMake 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:
unfault review -vThis shows which config files are being loaded and what settings are applied.