This is the full developer documentation for unfault # Welcome to Unfault > A CLI that finds production reliability issues in your code. Unfault is an open-source CLI that analyzes your code for patterns that cause production problems. It’s not a linter. It doesn’t police style or enforce conventions. It looks at how your code behaves at runtime: what calls can hang, where errors disappear, what fails under load, what breaks when a dependency goes down. Run it before you push. Run it in CI. Pipe it into your AI agent. It tells you what’s actually there. ## Commands [Section titled “Commands”](#commands) * `unfault review`: narrative summary with the most important findings, each named and explained * `unfault lint`: all findings grouped by severity and rule, linter-style * `unfault graph`: impact analysis, dependency queries, hub file detection * `unfault config`: configure LLM providers and inspect observability integrations ## Quick Links [Section titled “Quick Links”](#quick-links) [Installation ](/docs/installation/)Get unfault running in under a minute. [Quick Start ](/docs/quick-start/)Run your first review and understand the output. [Rules Catalog ](/docs/reference/rules/)Browse all the patterns unfault detects. [How It Works ](/docs/concepts/how-it-works/)What gets parsed, what gets analyzed, what stays on your machine. ## Use It Where You Work [Section titled “Use It Where You Work”](#use-it-where-you-work) * **Terminal**: `unfault review` before you push. `unfault lint` to see everything. * **CI/CD**: Gate on findings with exit codes. SARIF output for GitHub Code Scanning. * **AI Agents**: `--output json` and `graph` commands are built for agent consumption. See [Use with AI Agents](/docs/guides/agents/). ## Philosophy [Section titled “Philosophy”](#philosophy) Production failures are usually visible in the code before they happen. The HTTP call with no timeout. The retry loop with no backoff. The error handler that swallows the exception. These aren’t hard to spot. They just don’t get spotted because nobody’s looking for them systematically. Unfault looks for them. It won’t catch everything, and it doesn’t try to. It focuses on the patterns that have actually caused production incidents, explains the tradeoff in each case, and lets you decide what to do about it. # Cognitive Context > What cognitive context means and why it matters for writing better code. There’s a gap between the code you write and the system it becomes. You write a function. You know what it does in isolation. But do you know what happens when it runs alongside everything else? What services it depends on? What fails when it fails? What safeguards exist upstream? This is what we mean by cognitive context: the awareness of how your code behaves as part of a running system, available while you’re still writing it. ## The Problem [Section titled “The Problem”](#the-problem) When you’re deep in a function, your mental model is local. You’re thinking about the logic at hand, the variables in scope, the immediate task. That’s normal. That’s how we work. But production systems don’t run in isolation. A function that looks fine on its own might: * Call an external service without a timeout, creating cascading failures * Swallow an exception that should propagate, hiding real problems * Depend on a database connection that has no retry logic * Live in a route with no rate limiting, exposed to abuse These aren’t bugs in the traditional sense. The code does what it says. The issue is that the code doesn’t say enough about how it behaves under stress, at scale, over time. Most of the time, you don’t discover this until production tells you. And by then, you’re debugging at 2am with incomplete information and mounting pressure. ## What Traditional Tools Miss [Section titled “What Traditional Tools Miss”](#what-traditional-tools-miss) Linters check syntax and style. Type checkers verify contracts. Test suites confirm behavior under controlled conditions. These are valuable, but they don’t tell you: * Where this function fits in the call graph * What external dependencies it transitively relies on * Whether error paths are handled or silently dropped * How the framework wires this handler to incoming requests Static analysis tools can find some of this, but they typically operate in batch mode. You run them in CI, get a report, and context-switch back to fix things. The insight arrives too late, disconnected from the moment of writing. ## Cognitive Context in Practice [Section titled “Cognitive Context in Practice”](#cognitive-context-in-practice) Unfault takes a different approach. Instead of generating reports, it provides context where you work: **In your editor**, when you hover over a function, you might see: * “Called by 3 routes, 2 with authentication middleware” * “Makes HTTP calls to payment-service, no circuit breaker” * “Error path logs but doesn’t propagate” **In your terminal**, when you run a review: * Findings grouped by what matters (stability, performance, security) * Each finding explains why it matters, not just what it is * Suggested fixes when the path forward is clear **In CI**, as a gate: * Catch patterns that tend to cause production issues * Exit codes that integrate with your deployment flow * Output formats for your existing tools The goal isn’t to generate more alerts. It’s to give you the information you’d want if you could hold the entire system in your head. ## Why This Matters [Section titled “Why This Matters”](#why-this-matters) Code review catches a lot. Testing catches a lot. But some things slip through because they require thinking about the system as a whole, and that’s hard to do when you’re reviewing a diff or writing a unit test. Consider a simple change: you add a call to fetch user preferences from a new service. The code is correct. The tests pass. The reviewer approves. But nobody noticed: * The new service has p99 latency of 800ms * The call has no timeout * It’s in the critical path for page load * There’s no fallback if the service is down Six months later, that service has a bad deploy, and your unrelated feature is suddenly the cause of a site-wide slowdown. This isn’t a failure of process. It’s a failure of context. The information existed, but it wasn’t available at the moment of decision. ## Not a Gatekeeper [Section titled “Not a Gatekeeper”](#not-a-gatekeeper) Unfault doesn’t block your commits or fail your builds by default. It doesn’t tell you what to do. It surfaces information and lets you decide. Sometimes a missing timeout is fine. Maybe the call is to a local cache with sub-millisecond latency. Maybe you’re in a batch job where latency doesn’t matter. You know your system better than any tool. What Unfault does is make sure you’re making that decision consciously, with the relevant context visible, rather than accidentally. ## The Feeling We’re After [Section titled “The Feeling We’re After”](#the-feeling-were-after) You know that feeling when you’re working with someone who really knows the codebase? They glance at your PR and say, “Oh, be careful here, this function gets called from the payment flow and that service is flaky on Mondays.” That’s the experience we’re building toward. Not a judge. Not an enforcer. Just a knowledgeable colleague who helps you see what you might have missed. ## Next Steps [Section titled “Next Steps”](#next-steps) How It Works The architecture that makes this possible. [Read more](/docs/concepts/how-it-works/) Workspaces How Unfault identifies and tracks your project. [Read more](/docs/concepts/workspaces-sessions/) Dimensions How findings are categorized. [Read more](/docs/concepts/dimensions/) VS Code Extension Get context in your editor. [Read more](/docs/guides/vscode/) # Dimensions > How Unfault organizes facts by what matters to your system. Unfault groups facts into dimensions based on what aspect of your system they affect. This isn’t just categorization for its own sake. It helps you focus on what matters for your particular situation. A high-traffic API gateway cares deeply about performance. A financial service prioritizes correctness. A public-facing app needs to think about security. Dimensions let you filter facts to match your priorities. ## The Dimensions [Section titled “The Dimensions”](#the-dimensions) ### Stability [Section titled “Stability”](#stability) How well does your system handle the unexpected? Stability findings surface patterns that tend to cause cascading failures, hung requests, or unrecoverable states. Things like: * External calls without timeouts * Missing circuit breakers on flaky dependencies * Unbounded retries that amplify load during outages * Resource leaks that accumulate over time These patterns often work fine in normal conditions. They become problems when something goes wrong elsewhere, and your code’s response makes it worse. ### Performance [Section titled “Performance”](#performance) Where might your system slow down under load? Performance findings identify code that may become a bottleneck: * Blocking I/O in async contexts * N+1 query patterns * CPU-intensive work on the event loop * Regex compilation in hot paths * Unbounded caches that grow forever Not every performance finding needs immediate action. A slow path that runs once at startup is different from one in your request handler. The findings give you visibility; you decide what matters. ### Correctness [Section titled “Correctness”](#correctness) Does your code do what it should? Correctness findings catch logic issues that could produce wrong results: * Race conditions in shared state * Recursive functions without base cases * Unsafe deserialization of untrusted input * Integer overflow in arithmetic operations These are closer to traditional bugs, but Unfault looks for patterns that static analysis and tests often miss, especially concurrency issues and edge cases in error handling. ### Security [Section titled “Security”](#security) What attack surface does your code expose? Security findings highlight potential vulnerabilities: * SQL injection via string concatenation * Command injection through unsanitized input * Hardcoded credentials or secrets * Missing authentication on sensitive routes * Insecure defaults in cryptographic operations Security findings tend to require careful evaluation. Context matters: an internal tool has different threat models than a public API. ### Reliability [Section titled “Reliability”](#reliability) Can your system recover gracefully? Reliability overlaps with stability but focuses on recovery: * Missing error handlers on critical paths * Silent exception swallowing * No fallback when dependencies fail * Incomplete cleanup in error paths A reliable system doesn’t just avoid failures; it handles them well when they occur. ### Observability [Section titled “Observability”](#observability) Can you see what’s happening? Observability findings identify blind spots in your monitoring: * Missing correlation IDs across service boundaries * Log statements that omit crucial context * Untracked external calls * Missing metrics on key operations When something goes wrong at 3am, observability determines whether you debug for 10 minutes or 10 hours. ### Scalability [Section titled “Scalability”](#scalability) What breaks when traffic grows? Scalability findings look at patterns that work at low scale but fail at high scale: * Linear scans where indexes exist * Unbounded result sets without pagination * Connection pooling issues * Memory allocation patterns that fragment under load ### Maintainability [Section titled “Maintainability”](#maintainability) How hard is this code to change safely? Maintainability is opt-in and not included in default analysis. When enabled, it identifies: * High cyclomatic complexity * Deep nesting * Long functions * Circular dependencies These aren’t bugs. They’re friction. Code that’s hard to understand is code that’s easy to break. Note Maintainability findings are disabled by default because they’re more subjective. Enable them explicitly if code complexity metrics are useful for your team. ## Using Dimensions [Section titled “Using Dimensions”](#using-dimensions) ### Filtering by Dimension [Section titled “Filtering by Dimension”](#filtering-by-dimension) Focus your review on specific concerns: ```bash # Only stability issues unfault review --dimension stability # Stability and performance unfault review --dimension stability --dimension performance ``` ### Default Dimensions [Section titled “Default Dimensions”](#default-dimensions) By default, Unfault analyzes for: * Stability * Performance * Correctness * Security * Reliability * Observability * Scalability Maintainability is excluded unless explicitly requested. ### Choosing What Matters [Section titled “Choosing What Matters”](#choosing-what-matters) Different projects have different priorities: | Project Type | Focus Dimensions | | ----------------- | ----------------------------------- | | High-traffic API | Stability, Performance, Scalability | | Financial system | Correctness, Security, Reliability | | Internal tool | Correctness, Maintainability | | Public-facing app | Security, Stability, Performance | There’s no universal answer. The dimensions are a lens, not a prescription. ## Dimensions vs. Severity [Section titled “Dimensions vs. Severity”](#dimensions-vs-severity) Dimensions and severity are orthogonal: * **Dimension**: What aspect of the system is affected * **Severity**: How urgent is this finding A high-severity stability finding means “this will likely cause problems soon.” A low-severity performance finding means “this could matter at scale, but isn’t urgent.” You might filter by dimension to focus your review, then prioritize by severity within that set. ## Next Steps [Section titled “Next Steps”](#next-steps) Rules Catalog See all rules organized by dimension. [Browse rules](/docs/reference/rules/) Configuration Customize which dimensions to analyze. [Read more](/docs/reference/configuration/) CLI Usage Filter findings in practice. [Read more](/docs/guides/cli/) # Facts > The building blocks of what Unfault knows about your code. When Unfault analyzes your code, it produces **facts**. A fact is a discrete observation about your codebase: something Unfault noticed, measured, or inferred. Facts are the foundation of everything Unfault tells you. They’re not opinions or suggestions. They’re observations that you can query, filter, aggregate, and reason about. ## What Facts Are [Section titled “What Facts Are”](#what-facts-are) A fact is a typed signal with context. Each fact has: * **Type**: What kind of observation this is * **Location**: Where in your code it applies (file, line, function) * **Dimension**: What aspect of your system it relates to (stability, performance, security) * **Severity**: How important this observation is * **Payload**: The specific details of what was observed Facts are objective. They describe what *is*, not what *should be*. The interpretation, whether something needs action, is up to you. ## Types of Facts [Section titled “Types of Facts”](#types-of-facts) ### Findings [Section titled “Findings”](#findings) The most common fact type today is a **finding**: a pattern detected by a rule. ```plaintext fact_type: rule_finding rule_id: python.http.missing_timeout severity: High dimension: stability file: src/client.py line: 42 payload: title: "HTTP call has no timeout" description: "This requests.get call will block indefinitely if the server doesn't respond." fix_preview: "Add timeout parameter: requests.get(url, timeout=30)" ``` Findings tell you “here’s a pattern that often causes problems.” They’re actionable observations with suggested fixes. ### SLO State (Coming) [Section titled “SLO State (Coming)”](#slo-state-coming) Note SLO facts are in development. This describes where we’re headed. When you link SLOs to your codebase, Unfault can capture their current state as facts: ```plaintext fact_type: slo_state slo_name: "API Availability" provider: gcp target_percent: 99.9 current_percent: 99.87 error_budget_remaining: -0.03 dimension: observability routes_covered: 15 ``` SLO facts tell you “here’s what your observability systems are reporting.” They connect your code to production reality. ### More Fact Types [Section titled “More Fact Types”](#more-fact-types) The fact model is extensible. Future fact types might include: * **Dependency state**: Vulnerability status, version drift, license compliance * **Test coverage**: Which code paths lack test coverage * **Call patterns**: HTTP calls, database queries, external service dependencies * **Complexity metrics**: Cyclomatic complexity, coupling, cohesion Each fact type adds a new lens for understanding your code. ## Facts vs. Findings [Section titled “Facts vs. Findings”](#facts-vs-findings) If you’ve used Unfault before, you’re familiar with “findings.” Here’s how they relate: | | Findings | Facts | | ------------ | -------------------------- | -------------------------------------- | | **Scope** | Rule-detected patterns | Any observation about your code | | **Action** | Usually suggests a fix | May or may not be actionable | | **Tone** | ”Here’s a problem" | "Here’s what I observed” | | **Examples** | Missing timeout, N+1 query | Finding, SLO state, dependency version | Findings are facts. They’re the `rule_finding` fact type. But facts are broader: they’re the general model for everything Unfault knows. Tip Think of facts as storytelling about your codebase. Findings are one chapter, focused on patterns that might cause problems. Other chapters tell different stories: how your SLOs are doing, what your dependencies look like, how your code has changed. ## Working with Facts [Section titled “Working with Facts”](#working-with-facts) ### Querying Facts [Section titled “Querying Facts”](#querying-facts) Today, facts are surfaced through `unfault review` and `unfault graph` commands. Future releases will add more direct querying capabilities: ```bash # Surface findings facts unfault review --output json # Surface graph facts unfault graph critical --json unfault graph impact src/api/client.py --json ``` ### Facts in Sessions [Section titled “Facts in Sessions”](#facts-in-sessions) Every analysis session produces facts. When you run `unfault review`, the facts from that run are stored and associated with your workspace. ```plaintext Session (Jan 10, 2025) ├── 12 rule_finding facts ├── 2 slo_state facts (coming) └── Graph data ``` This history lets you track how facts change over time. Are findings increasing or decreasing? Is your SLO compliance improving? ### Facts and Insights [Section titled “Facts and Insights”](#facts-and-insights) Unfault aggregates facts into **insights**: high-level summaries that help you understand patterns across your codebase. For example, facts might show 47 individual findings across 185 files. The summary output might observe “the `src/clients/` directory has the highest concentration of stability issues, primarily missing timeouts and circuit breakers.” Insights are derived from facts. As more fact types are added, insights become richer. ## Why Facts Matter [Section titled “Why Facts Matter”](#why-facts-matter) The shift to facts reflects how we think about code understanding: **Facts are composable.** You can combine findings with SLO data to ask “which stability findings affect routes that are close to burning error budget?” That question spans two fact types. **Facts are queryable.** Instead of just getting a report, you can ask questions. The fact model makes your codebase knowledge queryable. **Facts tell a story.** A finding says “here’s a problem.” A collection of facts says “here’s what’s happening in your system.” The story is richer than any single observation. **Facts scale.** As Unfault learns more about your code, it produces more fact types. The model grows without changing fundamentally. ## Next Steps [Section titled “Next Steps”](#next-steps) Dimensions How facts are categorized by what they affect. [Read more](/docs/concepts/dimensions/) How It Works The full analysis pipeline. [Read more](/docs/concepts/how-it-works/) Query the Codebase Graph and review commands for exploration. [Read more](/docs/guides/asking-questions/) # How It Works > Understand what happens when you run a review, entirely on your machine. When you run `unfault review`, everything happens locally. There is no API, no cloud analysis, no data leaving your machine. Understanding this helps explain why Unfault behaves the way it does. ## The Short Version [Section titled “The Short Version”](#the-short-version) 1. **Parse locally.** Unfault reads your source files using Tree-sitter and extracts a semantic model: functions, calls, imports, routes. 2. **Build a code graph.** File-level semantics are merged into a unified graph capturing how things connect across your whole project. 3. **Run analysis in-process.** The `unfault-analysis` engine runs rules against the graph and produces findings. 4. **Show results.** Findings appear in your terminal (or CI output), formatted for the mode you chose. ## Why Local Analysis? [Section titled “Why Local Analysis?”](#why-local-analysis) Most analysis tools either send your source code to a server or require heavy language servers. Unfault does neither. Parsing and analysis run in the same process as the CLI, using the same code your project runs with. This means: * **Privacy.** Your source code never leaves your machine. * **Speed.** Parsing runs in parallel on your hardware. No round-trips. * **No account needed.** The core CLI is open source and works offline. * **Consistency.** The same analysis runs on your laptop and in CI. ## What Gets Extracted [Section titled “What Gets Extracted”](#what-gets-extracted) When Unfault parses your code, it builds a graph with nodes and edges: **Nodes** represent things in your code: * Files * Functions and methods * Classes * Imports (internal and external) * Framework constructs (routes, middleware, handlers) **Edges** represent relationships: * Contains (file contains function) * Calls (function A calls function B) * Imports (file imports module) * Inherits (class extends another) * Framework wiring (app registers route, route uses middleware) This graph captures the *structure* of your code without the *content*. The analysis engine can reason about “function `fetch_user` calls `requests.get` with no timeout” without touching the actual URL or request body. ## The Analysis Flow [Section titled “The Analysis Flow”](#the-analysis-flow) Here’s what happens when you run a review: ### 1. Workspace Detection [Section titled “1. Workspace Detection”](#1-workspace-detection) Unfault scans your project to understand what it’s looking at: * Which languages are present (Python, Go, Rust, TypeScript/JavaScript) * Which frameworks are in use (FastAPI, Express, Gin, Axum, Next.js, etc.) * Project structure and entry points ### 2. Parsing and Semantic Extraction [Section titled “2. Parsing and Semantic Extraction”](#2-parsing-and-semantic-extraction) For each source file, Unfault: * Parses the syntax tree with Tree-sitter * Extracts semantic information (functions, classes, calls, imports) * Detects framework-specific patterns (route decorators, middleware registration) * Builds the local portion of the code graph ### 3. Graph Construction [Section titled “3. Graph Construction”](#3-graph-construction) Individual file semantics get merged into a unified graph. This is where Unfault resolves: * Which function calls go where * How imports connect files * Framework topology (which routes exist, what middleware applies) ### 4. Rule Analysis [Section titled “4. Rule Analysis”](#4-rule-analysis) The `unfault-analysis` engine runs rules against the graph. Rules are organized by framework profile (e.g., `python_fastapi_backend`, `go_gin_service`) and dimension (stability, correctness, performance, scalability). Each rule produces **findings**: observations about your code. A finding includes what was detected, where it is, why it matters, and a suggested fix when possible. ### 5. Enrichment (Optional) [Section titled “5. Enrichment (Optional)”](#5-enrichment-optional) If you have observability integrations configured (GCP Cloud Monitoring, Datadog, Dynatrace), Unfault can fetch SLO data and enrich findings with production context: which routes have SLO coverage and which don’t. This step is skipped with `--offline`. ### 6. Output [Section titled “6. Output”](#6-output) Results are formatted for your chosen output mode and printed to stdout. ## What the Analysis Sees [Section titled “What the Analysis Sees”](#what-the-analysis-sees) To be concrete, if you have this code: ```python def fetch_user(user_id: str) -> dict: response = requests.get(f"https://api.example.com/users/{user_id}") return response.json() ``` The analysis sees something like: ```json { "functions": [{ "name": "fetch_user", "file": "users.py", "calls": [{"target": "requests.get", "has_timeout": false}] }] } ``` No URL. No variable names beyond what’s needed for the graph. No string literals. Just enough structure to identify the pattern. ## Next Steps [Section titled “Next Steps”](#next-steps) Dimensions How findings are categorized. [Read more](/docs/concepts/dimensions/) Explore the Code Graph Impact analysis, dependency queries, critical files. [Read more](/docs/guides/code-graph/) Quick Start Run your first review. [Get started](/docs/quick-start/) CLI Reference All commands and flags. [Read more](/docs/reference/cli/) # Workspaces > How Unfault identifies and tracks your project across runs. When you run `unfault review`, Unfault identifies which project it’s analyzing through a **workspace**. Understanding this helps explain how Unfault detects your project, applies configuration, and displays results. ## What a Workspace Is [Section titled “What a Workspace Is”](#what-a-workspace-is) A **workspace** is Unfault’s representation of a project or codebase. It determines: * Which configuration files apply * What the workspace is called in output * How Unfault identifies the project for SLO linking The workspace is derived from the directory you run Unfault from. Everything happens locally. ## How Workspaces Are Identified [Section titled “How Workspaces Are Identified”](#how-workspaces-are-identified) Unfault computes a workspace label from your project. The source depends on what’s available: 1. **Git remote** (most stable): If your project has a git remote configured, Unfault uses that URL to compute a consistent label. The same repo analyzed from different directories gets the same workspace identity. 2. **Project manifest** (fallback): If there’s no git remote, Unfault looks at manifest files like `pyproject.toml`, `package.json`, or `go.mod` to identify the project. 3. **Directory name** (least stable): As a last resort, Unfault uses the current directory name. This is less reliable because renaming a directory changes the workspace identity. Tip For consistent identification across machines and CI, make sure your project has a git remote configured. This gives Unfault the most stable workspace identity. ## Workspace Label [Section titled “Workspace Label”](#workspace-label) The workspace label is the human-readable name you see in analysis output. By default, it’s your directory name (e.g., `payments-service`). ```plaintext 1112ms - python / fastapi - 1 file [payments-service] ``` The label is display-only. It doesn’t affect which files are analyzed or how rules apply. ## Configuration and Workspaces [Section titled “Configuration and Workspaces”](#configuration-and-workspaces) Each workspace can have its own configuration. Unfault looks for configuration in: 1. Current directory’s manifest file (`pyproject.toml`, `Cargo.toml`, `package.json`) 2. Parent directories up to the git root 3. `.unfault.toml` if no manifest contains Unfault configuration This means running `unfault review` from different directories in the same repository applies different configuration. A monorepo with per-service config in each subdirectory works naturally. See [Configuration](/docs/reference/configuration/) for the full reference. ## Workspace-Level SLO Linking [Section titled “Workspace-Level SLO Linking”](#workspace-level-slo-linking) When Unfault detects cloud credentials and links SLOs to your codebase, that mapping is stored locally at `.unfault/` in your project directory: ```plaintext .unfault/ └── cache/ └── enrichment/ └── slo_mapping.json ``` This file persists across runs. Future reviews remember which SLOs belong to this workspace without prompting again. To re-run discovery and update the mapping, force a cache refresh: ```bash unfault review --refresh-cache ``` ## Enrichment Cache [Section titled “Enrichment Cache”](#enrichment-cache) To avoid fetching SLO and trace data on every run, Unfault caches observability data locally: ```plaintext .unfault/cache/enrichment/ ``` The cache has a 5-minute TTL. You’ll see `cached` or `fetch Xms` in the review footer accordingly. Force a cache refresh with: ```bash unfault review --refresh-cache ``` Skip enrichment entirely (for CI or offline use) with: ```bash unfault review --offline ``` ## Privacy [Section titled “Privacy”](#privacy) All workspace data stays on your machine: * **Stored locally**: Workspace labels, SLO mappings, enrichment cache * **Never stored remotely**: Your source code, string literals, comments, or variable values Analysis runs entirely on your hardware. See [How It Works](/docs/concepts/how-it-works/) for details. ## Next Steps [Section titled “Next Steps”](#next-steps) Configuration Customize behavior per workspace. [Read more](/docs/reference/configuration/) SLO Discovery Link cloud SLOs to your workspace. [Read more](/docs/guides/slo-discovery/) How It Works The full local analysis pipeline. [Read more](/docs/concepts/how-it-works/) # How to Contribute > Get started contributing to Unfault. Unfault’s client-side tools are open source, and we welcome contributions. Whether you’re fixing a bug, improving parsing for a language, or enhancing the VS Code extension, there’s a place for your work. ## The Open Source Ecosystem [Section titled “The Open Source Ecosystem”](#the-open-source-ecosystem) The following repositories are open source and accept contributions: | Repository | Language | What it does | License | | --------------------------------------------------- | ---------- | ------------------------------------------------ | ------- | | [unfault/cli](https://github.com/unfault/cli) | Rust | Command-line interface, orchestrates analysis | MIT | | [unfault/core](https://github.com/unfault/core) | Rust | Parsing, semantic extraction, graph construction | MIT | | [unfault/vscode](https://github.com/unfault/vscode) | TypeScript | VS Code extension with LSP | MIT | Most contributions fall into one of these areas: Fix bugs Found something broken? We’d love a fix. [View issues](https://github.com/unfault/cli/issues?q=is%3Aissue+is%3Aopen+label%3Abug) Improve parsing Help Unfault understand more languages and frameworks. [Read more](/docs/contributing/architecture/) Enhance the extension Improve the VS Code experience. [View issues](https://github.com/unfault/vscode/issues) Improve docs Clarify confusing sections or add missing information. [View on GitHub](https://github.com/unfault/unfault/tree/main/www) ## Quick Start [Section titled “Quick Start”](#quick-start) ### Prerequisites [Section titled “Prerequisites”](#prerequisites) * **Rust 1.70+** for CLI and core work * **Node.js 18+** for VS Code extension and documentation * **Git** for version control ### Clone and Build [Section titled “Clone and Build”](#clone-and-build) ```bash # CLI git clone https://github.com/unfault/cli cd cli && cargo build # Core library git clone https://github.com/unfault/core cd core && cargo build # VS Code extension git clone https://github.com/unfault/vscode cd vscode && npm install && npm run compile ``` ## Contribution Workflow [Section titled “Contribution Workflow”](#contribution-workflow) 1. **Find or create an issue** describing what you want to work on 2. **Fork the repository** and create a branch 3. **Make your changes** following the code style guidelines 4. **Write tests** for new functionality 5. **Submit a pull request** with a clear description ### Commit Messages [Section titled “Commit Messages”](#commit-messages) We use [Conventional Commits](https://www.conventionalcommits.org/): ```plaintext feat(parser): add support for Go generics fix(cli): handle expired tokens gracefully docs(readme): clarify installation steps ``` Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` ### Code Review [Section titled “Code Review”](#code-review) All changes go through code review. We look for: * **Correctness**: Does it work? Are edge cases handled? * **Tests**: Is new functionality tested? * **Style**: Does it follow project conventions? * **Documentation**: Are public APIs documented? Be patient. Maintainers review PRs as time allows. If your PR sits for a week without feedback, a gentle ping is fine. ## Where to Start [Section titled “Where to Start”](#where-to-start) ### Good First Issues [Section titled “Good First Issues”](#good-first-issues) Look for issues labeled `good first issue`: * [CLI good first issues](https://github.com/unfault/cli/labels/good%20first%20issue) * [Core good first issues](https://github.com/unfault/core/labels/good%20first%20issue) * [VS Code good first issues](https://github.com/unfault/vscode/labels/good%20first%20issue) ### Documentation [Section titled “Documentation”](#documentation) Documentation improvements are always welcome. The docs live in `www/src/content/docs/` and use MDX (Markdown with components). ```bash cd unfault/www npm install npm run dev # Open http://localhost:4321 ``` ### Parsing and Semantics [Section titled “Parsing and Semantics”](#parsing-and-semantics) The `core` crate handles all client-side parsing and semantic extraction. If you want Unfault to better understand a language or framework: 1. Check the existing parsers in `core/src/parse/` 2. Check the semantic extractors in `core/src/semantics/` 3. Open an issue describing what’s missing 4. Submit a PR with tests See the [Architecture](/docs/contributing/architecture/) page for details on how parsing works. ## Code of Conduct [Section titled “Code of Conduct”](#code-of-conduct) Be kind. Be constructive. Be professional. We’re all here to build something useful. Harassment, discrimination, and toxic behavior have no place in this project. If you experience or witness unacceptable behavior, report it to . ## Getting Help [Section titled “Getting Help”](#getting-help) * **GitHub Discussions**: [unfault/cli/discussions](https://github.com/unfault/cli/discussions) * **Existing Issues**: Search before opening a new one * **Documentation**: You’re reading it ## Recognition [Section titled “Recognition”](#recognition) Contributors are recognized in release notes and the project README. Significant contributions may earn a spot in the maintainers list. We appreciate every contribution, from typo fixes to major features. Thank you for helping make Unfault better. ## Next Steps [Section titled “Next Steps”](#next-steps) Architecture Understand how the client-side pieces fit together. [Read more](/docs/contributing/architecture/) CLI Repository Browse the CLI source code. [View on GitHub](https://github.com/unfault/cli) # Architecture > How Unfault's open source components work together. This document explains how Unfault’s client-side components work. If you’re contributing to the CLI, core library, or VS Code extension, this context helps you understand where your changes fit. ## System Overview [Section titled “System Overview”](#system-overview) Unfault runs entirely on your machine: ### Key Principle: Local Analysis [Section titled “Key Principle: Local Analysis”](#key-principle-local-analysis) **Everything runs on your machine.** The CLI and VS Code extension use the `core` library to parse source code and build a semantic graph. The `analysis` crate then runs rules against that graph in-process. No source code, IR, or findings leave your machine. This architecture provides: * **Privacy**: Your code never leaves your machine * **Speed**: Parsing and analysis are parallel and local * **Offline support**: Works without network access * **Consistency**: Same analysis logic in CLI and extension ## Components [Section titled “Components”](#components) ### Core Library (`core/`) [Section titled “Core Library (core/)”](#core-library-core) The core library is the foundation of client-side analysis. It handles: 1. **Parsing**: Tree-sitter grammars for Python, Go, Rust, TypeScript 2. **Semantic Extraction**: Functions, classes, imports, calls 3. **Framework Analysis**: FastAPI routes, Express middleware, Gin handlers 4. **Graph Construction**: Nodes (files, functions) and edges (calls, imports) 5. **IR Generation**: Serialization for the API ```plaintext core/ ├── src/ │ ├── lib.rs # Public API │ ├── parse/ # Tree-sitter parsing │ │ ├── python.rs │ │ ├── go.rs │ │ ├── rust.rs │ │ └── typescript.rs │ ├── semantics/ # Semantic extraction │ │ ├── mod.rs │ │ ├── python/ # Python-specific semantics │ │ ├── go/ │ │ └── ... │ ├── graph/ # Graph construction │ │ ├── mod.rs # CodeGraph implementation │ │ ├── nodes.rs # Node types │ │ └── edges.rs # Edge types │ └── ir.rs # Intermediate representation ``` #### CodeGraph [Section titled “CodeGraph”](#codegraph) The `CodeGraph` is the central data structure: ```rust pub struct CodeGraph { nodes: Vec, edges: Vec, // Indexes for efficient lookups file_index: HashMap, function_index: HashMap, } pub enum GraphNode { File { path: PathBuf, language: Language }, Function { name: String, qualified_name: String, ... }, Class { name: String, ... }, ExternalModule { name: String, category: ModuleCategory }, // Framework-specific nodes Route { method: HttpMethod, path: String, ... }, Middleware { name: String, ... }, } pub enum GraphEdgeKind { Contains, // File contains Function Calls, // Function calls Function Imports, // File imports Module Inherits, // Class inherits Class UsesLibrary, // Function uses external library // Framework-specific edges RegistersRoute, AppliesMiddleware, } ``` ### CLI (`cli/`) [Section titled “CLI (cli/)”](#cli-cli) The CLI orchestrates the analysis workflow: ```plaintext cli/ ├── src/ │ ├── main.rs # Entry point, argument parsing │ ├── commands/ # Subcommands │ │ ├── review.rs # `unfault review` │ │ ├── lint.rs # `unfault lint` │ │ ├── graph.rs # `unfault graph` │ │ ├── info.rs # `unfault info` │ │ ├── config.rs # `unfault config` │ │ ├── lsp.rs # `unfault lsp` │ │ └── agent_skills.rs # `unfault config agent` │ ├── session/ # Analysis session management │ │ ├── mod.rs # Session lifecycle │ │ └── workspace.rs # Workspace detection │ └── integration/ # Observability integrations │ └── (gcp, datadog, dynatrace) ``` The review flow: 1. **Workspace Detection**: Scan for languages, frameworks, config files 2. **File Collection**: Select files based on hints and exclusions 3. **Parsing**: Use `core` to parse files and build graph 4. **IR Generation**: Serialize graph to JSON (intermediate representation) 5. **Local Analysis**: Pass IR to `unfault-analysis` which runs rules in-process 6. **Output**: Format and display findings ### VS Code Extension [Section titled “VS Code Extension”](#vs-code-extension) The extension provides real-time analysis via LSP: ```plaintext vscode/ ├── src/ │ ├── extension.ts # Extension entry point │ ├── contextView.ts # Context sidebar webview │ └── welcomePanel.ts # Onboarding UI ``` The extension spawns the CLI in LSP mode (`unfault lsp`) and communicates via the Language Server Protocol. The CLI handles all parsing and analysis; the extension focuses on UI. ## Data Flow [Section titled “Data Flow”](#data-flow) ### Review Flow [Section titled “Review Flow”](#review-flow) ### Graph Query Flow [Section titled “Graph Query Flow”](#graph-query-flow) ## Key Design Decisions [Section titled “Key Design Decisions”](#key-design-decisions) ### Why Local Analysis? [Section titled “Why Local Analysis?”](#why-local-analysis) Analysis runs entirely on your machine: 1. **Privacy**: Source code never leaves your system 2. **Speed**: No round-trips, parsing runs in parallel on your hardware 3. **Offline support**: Works without network access 4. **Consistency**: Same analysis in CLI and editor extension ### Why a Separate Core Library? [Section titled “Why a Separate Core Library?”](#why-a-separate-core-library) The `core` library is a separate crate from the CLI. This is good practice: * Clear separation of concerns (parsing vs. orchestration vs. analysis) * Easier testing (test parsing logic independently) * Reusable foundation for future clients ### Why a Separate Analysis Library? [Section titled “Why a Separate Analysis Library?”](#why-a-separate-analysis-library) The `analysis` crate lives separately from `core` and `cli`: * Rules are testable in isolation * New rules can be added without touching parsing logic * Profiles (rule sets) are decoupled from both parsing and output ### What Does the IR Contain? [Section titled “What Does the IR Contain?”](#what-does-the-ir-contain) The Intermediate Representation (IR) passed from `core` to `analysis` contains: * File paths and languages * Function names and signatures * Import relationships * Call relationships * Framework topology (routes, middleware) The IR does **not** contain: * Source code * String literals * Comments * Variable values ## Contributing to Client-Side Code [Section titled “Contributing to Client-Side Code”](#contributing-to-client-side-code) ### Adding Language Support [Section titled “Adding Language Support”](#adding-language-support) To add support for a new language: 1. **Add Tree-sitter grammar** to `core/Cargo.toml` 2. **Create parser** in `core/src/parse/{language}.rs` 3. **Create semantic extractor** in `core/src/semantics/{language}/` 4. **Add to `SourceSemantics` enum** 5. **Update CLI** language detection 6. **Add tests** with sample code Caution Adding a language enables parsing and graph construction. Rules for the new language also need to be added to the `analysis` crate. Open an issue to discuss rule coverage before writing a parser. ### Adding Framework Detection [Section titled “Adding Framework Detection”](#adding-framework-detection) To add support for a new framework: 1. **Add detection logic** in `core/src/semantics/{language}/frameworks/` 2. **Add framework-specific nodes/edges** if needed 3. **Update `FrameworkGuess`** signals 4. **Add tests** with sample framework code ### Improving Semantic Extraction [Section titled “Improving Semantic Extraction”](#improving-semantic-extraction) To capture more semantic information: 1. **Extend the model** in `core/src/semantics/{language}/model.rs` 2. **Update extraction logic** in `core/src/semantics/{language}/mod.rs` 3. **Add tests** covering the new extraction ### Adding an Observability Provider [Section titled “Adding an Observability Provider”](#adding-an-observability-provider) The CLI can discover SLOs from observability platforms and link them to route handlers. Currently supported: GCP Cloud Monitoring, Datadog, Dynatrace. To add a new provider: 1. **Create provider module** in `cli/src/slo/{provider}.rs` 2. **Implement credential detection** (environment variables, config files) 3. **Implement `fetch_slos`** to query the provider’s API 4. **Return `SloDefinition`** structs with name, target, path pattern 5. **Register in `SloEnricher`** (`cli/src/slo/mod.rs`) 6. **Add environment variables** to documentation Example provider structure: ```rust pub struct MyProvider { api_key: String, endpoint: String, } impl MyProvider { pub fn is_available() -> bool { env::var("MY_PROVIDER_API_KEY").is_ok() } pub fn from_env() -> Option { let api_key = env::var("MY_PROVIDER_API_KEY").ok()?; Some(Self { api_key, endpoint: "https://api.myprovider.com".into() }) } pub async fn fetch_slos(&self, client: &Client) -> Result> { // Query API and map to SloDefinition } } ``` ## Testing [Section titled “Testing”](#testing) ### Core Tests [Section titled “Core Tests”](#core-tests) ```bash cd core cargo test # All tests cargo test python # Python-related tests cargo test semantics::python # Specific module ``` ### CLI Tests [Section titled “CLI Tests”](#cli-tests) ```bash cd cli cargo test ``` ### End-to-End Testing [Section titled “End-to-End Testing”](#end-to-end-testing) ```bash # Run the full workspace tests cargo test --workspace ``` ## Next Steps [Section titled “Next Steps”](#next-steps) How to Contribute Contribution workflow. [Read more](/docs/contributing/) CLI Repository Browse the CLI source code. [View on GitHub](https://github.com/unfault/cli) Core Repository Parsing and semantics. [View on GitHub](https://github.com/unfault/core) # Use with AI Agents > Help AI coding assistants get codebase context and review changes. AI coding assistants can write code fast. The failure mode is rarely syntax. It’s misunderstanding: wrong entry point, wrong pattern, missing context about what depends on what. Unfault helps with that. Its graph commands surface codebase structure quickly, and its JSON output is built to be parsed and acted on by agents. ## Set Up Agent Skills [Section titled “Set Up Agent Skills”](#set-up-agent-skills) The quickest way to give your agent access to Unfault is to generate skill files: ```bash # For Claude Code unfault config agent claude # For OpenCode unfault config agent opencode # Install globally (shared across projects) unfault config agent claude --global ``` This writes structured skill definitions to `.claude/skills/` or `.opencode/skills/` so the agent knows what commands are available and how to use them. ## Manual Setup [Section titled “Manual Setup”](#manual-setup) If your agent doesn’t support skills, add this to your agent’s instruction file: * GitHub Copilot: `.github/copilot-instructions.md` * Cursor: `.cursorrules` or `.cursor/rules/*.mdc` * Claude Code: `CLAUDE.md` * OpenCode: `AGENTS.md` ```text # Unfault (codebase context) Use Unfault to understand the codebase before and after edits. Find symbols (semantic search): unfault graph find is not yet available. Use grep or search tools instead. Estimate blast radius before refactoring: unfault graph impact unfault graph function-impact : Find all files using a library: unfault graph library Find the most connected files: unfault graph critical Review the current codebase: unfault review --output json unfault review --dimension stability ``` ## The Context Loop [Section titled “The Context Loop”](#the-context-loop) A useful pattern before making significant changes: ```bash # 1. Understand what depends on what you're touching unfault graph impact src/utils/auth.py # 2. Check the most critical files for context unfault graph critical --limit 5 # 3. Review the current state of the dimension you're working in unfault review --dimension stability --output json ``` ## Interpreting Review Output [Section titled “Interpreting Review Output”](#interpreting-review-output) When `unfault review --output json` returns: * Look at `contexts[].findings`. Each finding has `file_path`, `line`, `severity`, `title`, and `fix_preview`. * Start with `High` and `Critical` severity findings. * `fix_preview` is a hint, not a complete patch. Use it as a starting point. * If a finding is in code you didn’t touch, note it but don’t fix it in the same PR unless it’s directly relevant. ## Related Guides [Section titled “Related Guides”](#related-guides) * [Explore the Code Graph](/docs/guides/code-graph/) * [CLI Reference](/docs/reference/cli/) # Querying the Codebase > Use graph commands to explore and understand your codebase structure. The `unfault graph` commands let you ask structural questions about your codebase. Before making changes, running a migration, or onboarding to unfamiliar code, these queries help you understand what’s there and what might break. ## What You Can Ask [Section titled “What You Can Ask”](#what-you-can-ask) All graph commands run locally against the code graph built during analysis. No network required. ### What depends on a file? [Section titled “What depends on a file?”](#what-depends-on-a-file) ```bash unfault graph impact src/api/auth.py ``` Shows direct and transitive dependents. Use this before refactoring to understand blast radius. ### What depends on a function? [Section titled “What depends on a function?”](#what-depends-on-a-function) ```bash unfault graph function-impact src/api/auth.py:validate_token ``` Narrower than file-level impact. Useful when you’re changing a single function, not an entire module. ### What does this file depend on? [Section titled “What does this file depend on?”](#what-does-this-file-depend-on) ```bash unfault graph deps src/api/routes/payments.py ``` Shows internal imports and external libraries. Helps you understand the full picture before touching a file. ### Which files use a library? [Section titled “Which files use a library?”](#which-files-use-a-library) ```bash unfault graph library requests ``` Useful when migrating dependencies, auditing security vulnerabilities, or understanding how broadly a library is used. ### Which files are the most critical? [Section titled “Which files are the most critical?”](#which-files-are-the-most-critical) ```bash unfault graph critical ``` Returns files sorted by how many other files depend on them. These are your load-bearing files, the ones where changes have the widest effect. ### What does the codebase look like at a glance? [Section titled “What does the codebase look like at a glance?”](#what-does-the-codebase-look-like-at-a-glance) ```bash unfault graph stats ``` A quick overview: file count, function count, total edges, external library usage. ## Combining Queries [Section titled “Combining Queries”](#combining-queries) A practical workflow before a significant change: ```bash # 1. Understand how central the file is unfault graph impact src/core/models.py # 2. See what it depends on unfault graph deps src/core/models.py # 3. Find files using any libraries involved unfault graph library sqlalchemy ``` ## JSON Output [Section titled “JSON Output”](#json-output) All graph commands support `--json` for programmatic use or scripting: ```bash unfault graph impact --json src/api/auth.py unfault graph critical --json --limit 5 unfault graph library --json requests ``` This is useful for CI checks, custom tooling, or feeding context to AI assistants. ## Review as a Query [Section titled “Review as a Query”](#review-as-a-query) The review command itself is a form of query: “what patterns in this codebase tend to cause production problems?” ```bash # Everything unfault review # Only stability patterns unfault review --dimension stability # Only performance patterns unfault review --dimension performance # Detailed output with suggested fixes unfault review --output full # Machine-readable output unfault review --output json ``` Tip For understanding code patterns and conventions in an unfamiliar codebase, the graph commands answer structural questions; the review command answers operational ones. Both together give a thorough picture before you start changing things. ## Next Steps [Section titled “Next Steps”](#next-steps) Explore the Code Graph Detailed guide to all graph commands with examples. [Read more](/docs/guides/code-graph/) CLI Reference Full option reference for every command. [Read more](/docs/reference/cli/) Use with AI Agents Integrate graph output with coding assistants. [Read more](/docs/guides/agents/) # CI/CD Integration > Adding Unfault to your continuous integration pipeline. Unfault integrates with GitHub Actions, GitLab CI, and other CI/CD systems. All analysis runs locally. No credentials or external services are required for the core review. ## Output Formats [Section titled “Output Formats”](#output-formats) | Format | Flag | Use Case | | ------- | ---------------- | --------------------------------------------- | | `basic` | `--output basic` | Human-readable terminal output (default) | | `json` | `--output json` | Machine-readable JSON for custom integrations | | `sarif` | `--output sarif` | SARIF format for GitHub Code Scanning | Tip Use `--output sarif` for seamless integration with GitHub’s security features, including inline annotations in pull requests and the Security tab. ## GitHub Actions [Section titled “GitHub Actions”](#github-actions) Add this workflow to `.github/workflows/unfault.yml`: ```yaml name: Unfault on: pull_request: push: branches: [main] jobs: unfault: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - 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 echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Run Unfault review run: unfault review --output sarif --offline > results.sarif - name: Upload SARIF to GitHub uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif ``` Note `--offline`: this skips SLO/trace fetching, which requires observability credentials not typically present in CI. Remove it if you have those credentials configured. ## CI Platforms [Section titled “CI Platforms”](#ci-platforms) * GitLab CI Add to `.gitlab-ci.yml`: ```yaml unfault: image: debian:bookworm-slim stage: test before_script: - apt-get update && apt-get install -y curl - 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 - export PATH="$HOME/.local/bin:$PATH" script: - unfault review --output sarif --offline > gl-code-quality-report.json artifacts: reports: sast: gl-code-quality-report.json ``` Note GitLab supports SARIF format for its [SAST reports](https://docs.gitlab.com/ee/user/application_security/sast/). Results appear in merge request security widgets. * CircleCI Add to `.circleci/config.yml`: ```yaml version: 2.1 jobs: unfault: docker: - image: cimg/base:current steps: - checkout - run: name: Install Unfault command: | 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 echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV - run: name: Run Unfault command: unfault review --output sarif --offline > results.sarif - store_artifacts: path: results.sarif workflows: check: jobs: - unfault ``` ## Exit Codes [Section titled “Exit Codes”](#exit-codes) | Code | Meaning | Action | | ---- | --------------------- | ------------------ | | `0` | Success, no findings | Proceed | | `1` | General error | Check logs | | `2` | Configuration error | Check config | | `4` | Network error | Check connectivity | | `5` | **Findings detected** | Review issues | | `6` | Invalid input | Check arguments | ## Gate on Findings [Section titled “Gate on Findings”](#gate-on-findings) To block a pipeline when findings are detected: ```bash unfault review --offline if [ $? -eq 5 ]; then echo "Findings detected. Run 'unfault review --output full' locally for details." exit 1 fi ``` ## Caching [Section titled “Caching”](#caching) Speed up CI runs by caching the Unfault binary: ```yaml - name: Cache Unfault uses: actions/cache@v4 with: path: ~/.local/bin/unfault key: unfault-${{ runner.os }}-latest - name: Install Unfault if: steps.cache.outputs.cache-hit != 'true' 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 ``` ## With Observability Enrichment [Section titled “With Observability Enrichment”](#with-observability-enrichment) If you want SLO/trace enrichment in CI, set the relevant credentials as secrets and drop `--offline`: ```yaml - name: Run Unfault review env: # GCP: configure GOOGLE_APPLICATION_CREDENTIALS or workload identity DD_API_KEY: ${{ secrets.DD_API_KEY }} DD_APP_KEY: ${{ secrets.DD_APP_KEY }} run: unfault review --output sarif > results.sarif ``` See [SLO Discovery](/docs/guides/slo-discovery/) for details on observability integrations. # CLI Usage > Getting started with the Unfault command-line interface. The Unfault CLI analyzes code, explores dependencies, and queries your project’s graph from the terminal. Everything runs locally. No account, no network required. ## Installation [Section titled “Installation”](#installation) * Prebuilt binary (Linux x86\_64) ```bash 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 ``` Pick the right artifact for your OS/CPU from the [releases page](https://github.com/unfault/cli/releases/latest). * Cargo ```bash cargo install unfault ``` * From source ```bash git clone https://github.com/unfault/unfault cd unfault cargo build --release ``` ## Core Commands [Section titled “Core Commands”](#core-commands) ### Review Code [Section titled “Review Code”](#review-code) Analyze your codebase for production-readiness: ```bash unfault review ``` Filter by dimension: ```bash unfault review --dimension stability ``` Get detailed output with suggested fixes: ```bash unfault review --output full ``` Auto-apply suggested fixes: ```bash unfault review --fix ``` Skip SLO/trace fetching (useful in CI without observability credentials): ```bash unfault review --offline ``` ### Lint [Section titled “Lint”](#lint) Show all findings grouped by severity and rule, useful for a detailed linter-style view: ```bash unfault lint ``` ### File Discovery and Ignores [Section titled “File Discovery and Ignores”](#file-discovery-and-ignores) When Unfault scans a workspace, it respects the same ignore conventions you already use: * `.gitignore` (including global gitignore and `.git/info/exclude`) * `.ignore` * `.dockerignore` It also skips common dependency/build directories even if they aren’t explicitly ignored (e.g. `node_modules`, `target`, `dist`, `build`, `.venv`). If Unfault reports `0` files, the usual causes are: * running from the wrong directory * source files are matched by one of the above ignore files ### Explore Dependencies [Section titled “Explore Dependencies”](#explore-dependencies) Check what depends on a file before changing it: ```bash unfault graph impact src/api/auth.py ``` Find the most critical files in your codebase: ```bash unfault graph critical ``` See which files use a library: ```bash unfault graph library requests ``` Analyze what depends on a specific function: ```bash unfault graph function-impact src/api/auth.py:validate_token ``` ## Exit Codes [Section titled “Exit Codes”](#exit-codes) For CI/CD integration: | Code | Meaning | | --------- | ----------------------------------------------------------------- | | 0 | Success, no findings | | 5 | Findings detected | | 1-4, 6-10 | Various errors (see [reference](/docs/reference/cli/#exit-codes)) | Example CI usage: ```bash unfault review if [ $? -eq 5 ]; then echo "Findings detected" unfault review --output full fi ``` ## Detailed Guides [Section titled “Detailed Guides”](#detailed-guides) Explore the Code Graph Understand dependencies, impact, and critical files. [Read more](/docs/guides/code-graph/) SLO Discovery Link cloud SLOs to your routes. [Read more](/docs/guides/slo-discovery/) Use with AI Agents Integrate with Claude, Cursor, and others. [Read more](/docs/guides/agents/) CI/CD Pipeline Add Unfault to your build process. [Read more](/docs/guides/cicd/) ## Full Reference [Section titled “Full Reference”](#full-reference) For complete command options, flags, and examples, see the [CLI Reference](/docs/reference/cli/). # Explore the Code Graph > Use the graph command to understand dependencies, impact, and critical files. Unfault builds a graph of your codebase: which files import which, which functions call which, which libraries are used where. The `unfault graph` command lets you query this graph to understand your code’s structure and plan changes safely. ## Why This Matters [Section titled “Why This Matters”](#why-this-matters) Before changing code, you want to know what might break. The graph answers questions like: * “If I refactor `auth.py`, what else needs to change?” * “Which files use the `requests` library?” * “What are the most interconnected files in this codebase?” These questions are hard to answer by grepping. The graph understands imports, function calls, and transitive dependencies. ## Check What Depends on a File [Section titled “Check What Depends on a File”](#check-what-depends-on-a-file) Before refactoring a file, see what depends on it: ```bash unfault graph impact src/api/auth.py ``` Output: ```plaintext 🔍 Impact Analysis: src/api/auth.py → This file is used by 15 file(s) 10 direct, 5 through transitive dependencies Direct Dependencies Files that import this directly - changes here affect them first ──────────────────────────────────────────────────────────── • src/api/routes/users.py [Python] • src/api/routes/admin.py [Python] • src/api/middleware/auth.py [Python] • src/services/payments.py [Python] ... Transitive Dependencies Files that depend on this indirectly - ripple effects ──────────────────────────────────────────────────────────── →→ src/api/routes/orders.py [Python] (2 hops) →→ src/api/routes/checkout.py [Python] (2 hops) ... 💡 This is a hub file. Consider extra care when modifying. ``` The direct dependents are files that import from `auth.py`. Transitive dependencies show files that depend on those dependents. Changes ripple outward. ### Control the Depth [Section titled “Control the Depth”](#control-the-depth) By default, transitive analysis goes 5 levels deep. For large codebases, you might want to limit this: ```bash unfault graph impact --max-depth 2 src/core/models.py ``` Or expand it for a fuller picture: ```bash unfault graph impact --max-depth 10 src/core/models.py ``` ## Check What Depends on a Function [Section titled “Check What Depends on a Function”](#check-what-depends-on-a-function) Sometimes you’re changing a single function, not a whole file. The graph can be more precise: ```bash unfault graph function-impact src/api/auth.py:validate_token ``` This shows only the files and functions that call `validate_token`, not everything that imports `auth.py`. ## Find Files Using a Library [Section titled “Find Files Using a Library”](#find-files-using-a-library) Auditing a dependency? See where it’s used: ```bash unfault graph library requests ``` Output: ```plaintext 📚 Library Usage: requests → 'requests' is used in 7 file(s) Usage Locations These files import this library directly ──────────────────────────────────────────────────────────── • src/clients/payment.py [Python] • src/clients/shipping.py [Python] • src/scripts/migrate.py [Python] • src/services/notifications.py [Python] ... 💡 This library is used widely. Consider its stability and versioning. ``` This is useful when: * Migrating from one library to another (`requests` to `httpx`) * Auditing security vulnerabilities in a dependency * Understanding how broadly a library is used ## Find a File’s Dependencies [Section titled “Find a File’s Dependencies”](#find-a-files-dependencies) The inverse question: what does this file depend on? ```bash unfault graph deps src/api/routes/payments.py ``` Output: ```plaintext Dependencies for src/api/routes/payments.py Internal imports (5): ← src/api/auth.py ← src/core/models.py ← src/services/stripe.py ← src/services/notifications.py ← src/utils/logging.py External libraries (4): ← fastapi ← sqlalchemy ← pydantic ← structlog ``` This helps when: * Understanding a file before modifying it * Checking if a file has too many dependencies * Auditing what a module actually needs ## Find Critical Files [Section titled “Find Critical Files”](#find-critical-files) Every codebase has hub files: highly connected, imported by many others. These are your most critical files. Changes to them have wide impact. ```bash unfault graph critical ``` Output: ```plaintext 🎯 Hub Files Analysis Files with the most connections - changes here have the widest impact → Analyzing 185 files, showing top 10 # File In Out Libs Score ───────────────────────────────────────────────────────────────── 1 src/__init__.py 56 0 0 112 2 src/database.py 47 1 11 106 3 src/dependencies/database.py 28 1 2 59 4 src/config.py 27 0 4 58 5 src/schemas.py 19 0 3 41 ... 💡 '__init__.py' is a major hub. Consider extra review for changes. Legend: In: dependents | Out: dependencies | Libs: external packages Higher score = more central to the codebase ``` These are files to handle with care. Changes here ripple widely. ### Different Sorting Criteria [Section titled “Different Sorting Criteria”](#different-sorting-criteria) You can sort by different metrics: ```bash # Files most imported by others (default) unfault graph critical --sort-by in-degree # Files that import the most (sprawling dependencies) unfault graph critical --sort-by out-degree # Total connectivity (hubs) unfault graph critical --sort-by total-degree # Most external library usage unfault graph critical --sort-by library-usage ``` ### More or Fewer Results [Section titled “More or Fewer Results”](#more-or-fewer-results) ```bash # Top 20 critical files unfault graph critical --limit 20 # Just the top 3 unfault graph critical --limit 3 ``` ## Get Graph Statistics [Section titled “Get Graph Statistics”](#get-graph-statistics) For a high-level view of your codebase structure: ```bash unfault graph stats ``` Output: ```plaintext 🗺️ Code Graph Overview A map of your codebase structure and connections → 723 code units across 185 files Structure ───────────────────────────────────────────── 📄 Files 185 ⚙️ Functions 723 📦 Classes 0 📚 External Libraries 69 ───────────────────────────────────────────── Total nodes 978 Connections ───────────────────────────────────────────── 🔗 File imports 409 📍 File→function/class 731 📚 Library usage 656 ➡️ Function calls 628 ───────────────────────────────────────────── Total edges 2680 ``` ## JSON Output [Section titled “JSON Output”](#json-output) All graph commands support `--json` for programmatic use: ```bash unfault graph impact --json src/api/auth.py unfault graph critical --json --limit 5 ``` This is useful for: * CI/CD checks (“fail if this file has more than N dependents”) * Custom tooling * Integration with other systems ## Practical Workflows [Section titled “Practical Workflows”](#practical-workflows) ### Before a Refactor [Section titled “Before a Refactor”](#before-a-refactor) 1. Check what depends on the file you’re changing: ```bash unfault graph impact src/core/models.py ``` 2. If the impact is large, consider whether to: * Break the change into smaller pieces * Add deprecation warnings first * Update dependents in the same PR 3. After making changes, run a review to catch issues: ```bash unfault review ``` ### Auditing a Dependency [Section titled “Auditing a Dependency”](#auditing-a-dependency) 1. Find all usage of the library: ```bash unfault graph library requests ``` 2. Check each file for patterns (timeouts, retries): ```bash unfault review --dimension stability ``` 3. If migrating, you now have a complete list of files to update. ### Understanding a New Codebase [Section titled “Understanding a New Codebase”](#understanding-a-new-codebase) 1. Get the overall shape: ```bash unfault graph stats ``` 2. Find the critical files (these are the “load-bearing walls”): ```bash unfault graph critical --limit 10 ``` 3. Pick a critical file and see what depends on it: ```bash unfault graph impact src/core/models.py ``` ## Next Steps [Section titled “Next Steps”](#next-steps) CLI Reference Full option reference for graph commands. [Read more](/docs/reference/cli/#graph) Query the Codebase Structural queries for exploration and understanding. [Read more](/docs/guides/asking-questions/) # Adding External Integrations > Write reliable code when connecting to external services. You’re adding code that calls an external API, a third-party service, or a database. These integrations are where production incidents often start. A missing timeout, an unbounded retry loop, or no error handling can take down your service when the external dependency misbehaves. This guide shows how to use Unfault to catch these patterns before they ship. ## The Patterns That Cause Incidents [Section titled “The Patterns That Cause Incidents”](#the-patterns-that-cause-incidents) When code calls external services, a few patterns cause most of the problems: **Missing timeouts**: The external service hangs. Your request hangs. Your thread pool fills up. Your service stops responding. **Unbounded retries**: The external service returns errors. Your code retries forever. You amplify the problem and possibly get rate limited. **No circuit breaker**: The external service is down. Every request tries to reach it, fails, and takes the slow timeout path. Your service becomes unusably slow. **Swallowed errors**: Something fails. The error gets caught and ignored. Debugging becomes archaeology. **No correlation IDs**: A request fails somewhere in the chain. You can’t trace what happened across services. Unfault detects all of these. ## Reviewing Integration Code [Section titled “Reviewing Integration Code”](#reviewing-integration-code) After writing code that connects to an external service: ```bash unfault review --dimension stability ``` The `--dimension stability` flag focuses on the patterns most relevant to external integrations. Example output: ```plaintext High: Missing HTTP timeout src/integrations/billing.py:42 HTTP request without timeout can hang indefinitely High: Missing circuit breaker src/integrations/billing.py:38 External service call without circuit breaker Prerequisites: request classification, metrics collection Medium: No retry logic src/integrations/billing.py:42 HTTP call may fail transiently without retry ``` ## Making Decisions [Section titled “Making Decisions”](#making-decisions) Not every finding requires immediate action. Here’s how to think about them: ### Timeouts [Section titled “Timeouts”](#timeouts) Almost always worth adding. This is a low-effort, high-impact fix: ```python # Before response = requests.get(url) # After response = requests.get(url, timeout=30.0) ``` ### Retries [Section titled “Retries”](#retries) Worth adding for idempotent operations. Be careful with non-idempotent ones: ```python # Safe for GET requests @retry(stop=stop_after_attempt(3), wait=wait_exponential()) def fetch_data(): return requests.get(url, timeout=30.0) # Be careful with POST requests that aren't idempotent ``` ### Circuit Breakers [Section titled “Circuit Breakers”](#circuit-breakers) These require more infrastructure. If Unfault reports prerequisites are missing (metrics, request classification), this is a note for future work, not something to implement in this PR. ### Error Handling [Section titled “Error Handling”](#error-handling) If Unfault reports empty catch blocks or swallowed exceptions, add appropriate handling: ```python # Before try: response = client.fetch() except Exception: pass # Empty catch # After try: response = client.fetch() except RequestException as e: logger.error("Failed to fetch from billing service", error=str(e)) raise BillingServiceError("Could not reach billing service") from e ``` ## A Practical Workflow [Section titled “A Practical Workflow”](#a-practical-workflow) 1. **Write the integration code** Get it working first. 2. **Review for stability patterns** ```bash unfault review --dimension stability ``` 3. **Add timeouts** These are almost always worth adding. 4. **Add error handling** Make sure errors are logged and propagated appropriately. 5. **Consider retries** For idempotent operations on flaky services. 6. **Note infrastructure gaps** If Unfault suggests circuit breakers or rate limiting but prerequisites are missing, document this for future work. 7. **Review again** ```bash unfault review --dimension stability ``` Confirm the high-severity findings are addressed. ## With AI Agents [Section titled “With AI Agents”](#with-ai-agents) When an AI assistant writes integration code, it often forgets these operational concerns. Add to your AGENTS.md: ```text When writing code that calls external services: 1. Always include timeouts on HTTP calls 2. Consider retry logic for idempotent operations 3. Log errors with enough context to debug later 4. After writing, run: unfault review --dimension stability 5. Address high-severity findings before committing ``` See [Use with AI Agents](/docs/guides/agents/) for full setup instructions. ## Checking Existing Patterns [Section titled “Checking Existing Patterns”](#checking-existing-patterns) Before writing new integration code, see how similar integrations work in your codebase. Find files that already use HTTP clients: ```bash unfault graph library requests unfault graph library httpx ``` This helps you match existing conventions rather than inventing new patterns. ## Next Steps [Section titled “Next Steps”](#next-steps) Pre-commit Review Review all changes before committing. [Read more](/docs/guides/pre-commit-review/) Fault Injection Test how your code handles failures. [Read more](/docs/guides/fault/) Rules Catalog Browse stability rules in detail. [Read more](/docs/reference/rules/) # Fault Injection > Use fault to exercise timeouts, latency, jitter, and outages against your app. Unfault’s LSP and VS Code extension can integrate with [fault](https://fault-project.com/) to help you run targeted fault injections against your app while you’re looking at a specific route or function. The goal is not to build a perfect chaos suite on day one. It’s to answer: “What happens if this upstream is slow, flaky, or down?” while you’re still in the code. ## Install the fault CLI [Section titled “Install the fault CLI”](#install-the-fault-cli) Install `fault` from its GitHub releases or directly: ```bash # macOS brew install fault-project/tap/fault # Linux / manual install curl -Lo fault https://github.com/fault-project/fault/releases/latest/download/fault-linux-x86_64 chmod +x fault && mv fault ~/.local/bin/ ``` ## Configure the App Base URL [Section titled “Configure the App Base URL”](#configure-the-app-base-url) The VS Code extension needs to know where your app is listening. Set in your VS Code settings: * `unfault.fault.baseUrl` (default: `http://127.0.0.1:8000`) This is the target Unfault maps to the local fault proxy. ## Run a Fault Injection from VS Code [Section titled “Run a Fault Injection from VS Code”](#run-a-fault-injection-from-vs-code) When your cursor is inside a route handler (or a function reached by one), open the **Unfault: Context** sidebar and use the **Fault Injection** panel. When you click **Run**: * Unfault starts a streaming proxy with `fault run --proxy "9090=" ...`. * Unfault opens a split terminal with a ready-to-edit `curl http://127.0.0.1:9090/` command. * The curl command is not executed automatically. This makes the architecture explicit: your client talks to the fault proxy, and the proxy streams traffic to your app. Note In streaming proxy mode, Unfault doesn’t offer HTTP error injection templates (the stream is opaque). Stick to network-style faults: latency, jitter, bandwidth, packet loss, and blackhole. ## Generate Scenario Files [Section titled “Generate Scenario Files”](#generate-scenario-files) If you want repeatable checks, use the **Generate scenario file** button in the same panel. Unfault generates one scenario suite per discovered route and saves it under: * `tests/fault/` if it exists * otherwise `test/fault/` if it exists * otherwise it creates `tests/fault/` Each file contains multiple YAML documents separated by `---` (a small suite of scenarios for that route). To run a scenario file: ```bash fault scenario run --scenario tests/fault/post-payments.yaml ``` ## Running Fault Injections [Section titled “Running Fault Injections”](#running-fault-injections) Use `fault run` directly for quick, targeted injections: ```bash # Add 200ms latency for 30 seconds, proxying to your app fault run --proxy "9090=http://127.0.0.1:8000" --latency 200ms --duration 30s # Simulate a complete blackhole (service unreachable) fault run --proxy "9090=http://127.0.0.1:8000" --blackhole --duration 15s ``` Then drive traffic through the proxy port (9090) to observe how your code handles it. Note The fault integration in Unfault focuses on a small, practical subset of fault scenarios. For advanced scenarios, use `fault` directly. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) **fault not found** * The VS Code extension will prompt if `fault` isn’t installed. * You can also set `unfault.fault.executablePath` if `fault` is not on your `PATH`. **Port 9090 already in use** * The fault proxy binds to `127.0.0.1:9090`. * If you rerun with a different template, Unfault stops the previous proxy before starting a new one. # Monorepo Setup > How to configure Unfault for monorepo projects. Monorepos bring multiple services, libraries, and applications into a single repository. Unfault handles this by analyzing from whatever directory you run it in. ## Basic Usage [Section titled “Basic Usage”](#basic-usage) Unfault analyzes from the current directory down: ```bash # 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. ## Workspace Configuration [Section titled “Workspace Configuration”](#workspace-configuration) 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 ### Per-Service Configuration [Section titled “Per-Service Configuration”](#per-service-configuration) ```plaintext 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 ```toml [tool.unfault] dimensions = ["stability", "security"] # Payments cares most about these [tool.unfault.rules] exclude = ["python.http.missing_circuit_breaker"] # Handled at gateway ``` ### Shared Configuration [Section titled “Shared Configuration”](#shared-configuration) For settings that apply everywhere, put them in the root config: ```toml # 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. ## Multi-Language Monorepos [Section titled “Multi-Language Monorepos”](#multi-language-monorepos) Monorepos often contain multiple languages. Unfault detects and analyzes each: ```plaintext my-monorepo/ ├── services/ │ ├── api/ # Python (FastAPI) │ ├── worker/ # Go │ └── gateway/ # Rust ├── frontend/ # TypeScript └── scripts/ # Python ``` ```bash # 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 ``` ## CI/CD for Monorepos [Section titled “CI/CD for Monorepos”](#cicd-for-monorepos) ### Analyze Changed Services Only [Section titled “Analyze Changed Services Only”](#analyze-changed-services-only) .github/workflows/unfault.yml ```yaml 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 ``` ### Parallel Analysis [Section titled “Parallel Analysis”](#parallel-analysis) For large monorepos, analyze services in parallel using a matrix: ```yaml 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 ``` ## Graph Across Services [Section titled “Graph Across Services”](#graph-across-services) The code graph understands cross-service relationships when imports or calls cross boundaries: ```bash # 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. ## Common Patterns [Section titled “Common Patterns”](#common-patterns) ### Shared Libraries [Section titled “Shared Libraries”](#shared-libraries) ```plaintext my-monorepo/ ├── libs/ │ └── common/ # Shared utilities └── services/ ├── api/ # Uses libs/common └── worker/ # Uses libs/common ``` Analyze the library to see impact across consumers: ```bash unfault graph impact libs/common/core.py ``` ### Different Rules per Service Type [Section titled “Different Rules per Service Type”](#different-rules-per-service-type) services/public-api/pyproject.toml ```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 ``` ### Excluding Non-Production Code [Section titled “Excluding Non-Production Code”](#excluding-non-production-code) ```toml # Root pyproject.toml [tool.unfault.rules] exclude = [ "*:scripts/*", "*:tools/*", "*:examples/*", "*:**/tests/*", ] ``` ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ### ”Analysis takes too long” [Section titled “”Analysis takes too long””](#analysis-takes-too-long) Narrow the scope by running from a subdirectory: ```bash # Instead of the whole monorepo cd my-monorepo unfault review # Analyze just what you're working on cd services/api unfault review ``` ### “Different findings in CI vs local” [Section titled ““Different findings in CI vs local””](#different-findings-in-ci-vs-local) Make sure you’re running from the same directory in both environments. The working directory determines what gets analyzed. ### ”Config not being picked up” [Section titled “”Config not being picked up””](#config-not-being-picked-up) Use verbose mode to see what’s happening: ```bash unfault review -v ``` This shows which config files are being loaded and what settings are applied. ## Next Steps [Section titled “Next Steps”](#next-steps) CI/CD Integration Full CI/CD setup guide. [Read more](/docs/guides/cicd/) Configuration All configuration options. [Read more](/docs/reference/configuration/) Suppressing Rules Customize per-service rules. [Read more](/docs/guides/suppressing-rules/) # Copy/Paste Oneliners > Ready-to-run commands for common scenarios. No explanation needed. Just copy, paste, and run. ## Before You Commit [Section titled “Before You Commit”](#before-you-commit) ```bash # Summary review: header + findings narrative unfault review # All findings grouped by severity and rule (linter view) unfault lint # Detailed output with suggested fixes unfault review --output full # Machine-readable JSON unfault review --output json # Focus on stability only unfault review --dimension stability ``` ## For Coding Agents [Section titled “For Coding Agents”](#for-coding-agents) Structured output for Claude, Cursor, Copilot, and other AI assistants. ```bash # JSON output for programmatic parsing unfault review --output json # Focus on specific dimensions unfault review --dimension stability --output json unfault review --dimension correctness --output json # Impact analysis before changes unfault graph impact src/api/routes.py --json unfault graph critical --json --limit 5 ``` Note The `--output json` format includes `file_path`, `line`, `severity`, `title`, and `fix_preview` for each finding. See the [Output Reference](/docs/reference/api/) for the full schema. ### Paste Into Your Agent [Section titled “Paste Into Your Agent”](#paste-into-your-agent) Add this to your `AGENTS.md` or system prompt: ```markdown Before committing, run: \`\`\`bash unfault review --output json \`\`\` If high-severity findings are reported, address them before committing. Before changing a file, check its blast radius: \`\`\`bash unfault graph impact \`\`\` ``` ## CI/CD Pipelines [Section titled “CI/CD Pipelines”](#cicd-pipelines) ### GitHub Actions [Section titled “GitHub Actions”](#github-actions) ```bash # SARIF for GitHub Code Scanning unfault review --output sarif > results.sarif ``` .github/workflows/unfault.yml ```yaml - name: Run Unfault run: unfault review --output sarif > results.sarif - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif ``` ### Exit Code Checks [Section titled “Exit Code Checks”](#exit-code-checks) ```bash # Fail if findings detected (exit 5) unfault review && echo "Clean!" || echo "Findings found" # Explicit check unfault review --output json if [ $? -eq 5 ]; then echo "Review the findings above" exit 1 fi ``` ### JSON for Custom Processing [Section titled “JSON for Custom Processing”](#json-for-custom-processing) ```bash # Pipe to jq for filtering unfault review --output json | jq '.contexts[].findings | map(select(.severity == "High"))' # Count findings unfault review --output json | jq '[.contexts[].findings] | flatten | length' ``` ## Deep Analysis [Section titled “Deep Analysis”](#deep-analysis) ```bash # Full detailed output with suggested fixes unfault review --output full # Focus on one dimension unfault review --dimension stability --output full unfault review --dimension performance --output full unfault review --dimension correctness --output full # Preview fixes without applying unfault review --dry-run ``` ## Impact Analysis [Section titled “Impact Analysis”](#impact-analysis) ```bash # What breaks if I change this file? unfault graph impact src/core/auth.py # Find the most connected files unfault graph critical --limit 10 # What uses this library? unfault graph library requests unfault graph library httpx # External dependencies of a file unfault graph deps src/api/client.py # Graph statistics unfault graph stats ``` ## Configuration [Section titled “Configuration”](#configuration) ```bash # Configure LLM for AI-powered review summaries unfault config llm openai --model gpt-4o unfault config llm anthropic --model claude-3-5-sonnet-latest unfault config llm ollama --model llama3.2 # View current config unfault config show unfault config llm show # Check observability integrations unfault config integrations show unfault config integrations verify ``` ## Quick Reference [Section titled “Quick Reference”](#quick-reference) | Flag | What it does | | ------------------ | -------------------------------------- | | `--output full` | Detailed output with fix suggestions | | `--output json` | Machine-readable JSON | | `--output sarif` | SARIF 2.1.0 for GitHub/IDE integration | | `--output concise` | Brief statistics only | | `--dimension X` | Focus on one dimension | | `--dry-run` | Preview fixes without applying | | `--fix` | Auto-apply suggested fixes | | `--offline` | Skip SLO and trace fetching | | `--refresh-cache` | Re-fetch observability data | ## Exit Codes [Section titled “Exit Codes”](#exit-codes) | Code | Meaning | | ---- | -------------------------- | | 0 | Success, no findings | | 5 | Success, findings detected | | 1 | General error | | 2 | Config error | | 4 | Network error | | 6 | Invalid input | AI Agents Guide Full integration patterns. [Read more](/docs/guides/agents/) CI/CD Guide Pipeline integration details. [Read more](/docs/guides/cicd/) CLI Reference Complete command docs. [Read more](/docs/reference/cli/) # Pre-commit Code Review > Review code for production readiness before you commit. You’ve written code. Before you commit, you want to know if it has patterns that cause problems in production. This guide shows you how to use Unfault for that check. ## The Quick Version [Section titled “The Quick Version”](#the-quick-version) ```bash unfault review ``` For detailed output with suggested fixes: ```bash unfault review --output full ``` ## What Unfault Looks For [Section titled “What Unfault Looks For”](#what-unfault-looks-for) Unfault doesn’t check syntax or style. It looks for operational patterns: * **Missing safeguards**: HTTP calls without timeouts, retries without limits, caches without bounds * **Error handling gaps**: Empty catch blocks, swallowed exceptions, missing error propagation * **Resource issues**: Unbounded concurrency, connection leaks, blocking calls in async code * **Observability gaps**: Missing correlation IDs, no structured logging, absent tracing These are the patterns that work fine in development and cause incidents in production. ## Reading the Output [Section titled “Reading the Output”](#reading-the-output) The default output gives you a summary: ```plaintext Looks good overall, with a couple spots that deserve a closer look. At a glance · 2 calls without timeouts - could hang if a service is slow · 1 empty catch block - errors might vanish silently ──────────────────────────────────────────────────────────────────────────────── 847ms - python / fastapi - 3 files Tip: use --output full to drill into hotspots. ``` Use `--output full` to see exactly where the issues are and what to do about them: ```bash unfault review --output full ``` This shows each finding with: * File and line number * What was detected * Why it matters * A suggested fix (when applicable) ## Focusing Your Review [Section titled “Focusing Your Review”](#focusing-your-review) ### By Dimension [Section titled “By Dimension”](#by-dimension) If you’re working on a specific concern: ```bash # Only stability issues (timeouts, retries, error handling) unfault review --dimension stability # Only performance issues (blocking calls, resource usage) unfault review --dimension performance # Multiple dimensions unfault review --dimension stability --dimension correctness ``` ### By Severity [Section titled “By Severity”](#by-severity) When there are many findings, focus on high severity first. The summary groups findings by severity, and `--output full` shows severity for each finding. ## Making Decisions [Section titled “Making Decisions”](#making-decisions) Not every finding needs action. Here’s a framework: 1. **High severity in code you just wrote** Worth addressing before commit. These are patterns with real production impact. 2. **Medium severity in code you wrote** Consider the context. If the code is on a critical path, address it. If it’s a script or tool, maybe not. 3. **Low severity** Informational. Good to know, rarely blocking. 4. **Findings in files you didn’t change** Existing patterns. Note them if relevant, but they’re not your responsibility in this PR. ## Integrating with Your Workflow [Section titled “Integrating with Your Workflow”](#integrating-with-your-workflow) ### Git Hook [Section titled “Git Hook”](#git-hook) Add a pre-commit hook that runs Unfault: .git/hooks/pre-commit ```bash #!/bin/sh unfault review --output concise --offline EXIT_CODE=$? if [ $EXIT_CODE -eq 5 ]; then echo "" echo "Unfault found patterns worth reviewing." echo "Run 'unfault review --output full' for details." echo "Commit anyway with --no-verify if this is intentional." exit 1 fi exit 0 ``` Make it executable: ```bash chmod +x .git/hooks/pre-commit ``` ### With AI Agents [Section titled “With AI Agents”](#with-ai-agents) If you’re using an AI coding assistant, add to your `AGENTS.md`: ```text Before committing changes that involve external services, error handling, or resource management, run: unfault review --dimension stability --output json Address high-severity findings before committing. ``` See [Use with AI Agents](/docs/guides/agents/) for more detail. See [Use with AI Agents](/docs/guides/agents/) for more detail. ## Common Patterns [Section titled “Common Patterns”](#common-patterns) ### ”I know this is fine” [Section titled “”I know this is fine””](#i-know-this-is-fine) Sometimes Unfault flags something that’s intentional. A few options: 1. **Add a suppression comment** (see [Suppressing Rules](/docs/guides/suppressing-rules/)) 2. **Just commit.** Unfault is advisory, not mandatory. ### ”There are too many findings” [Section titled “”There are too many findings””](#there-are-too-many-findings) If Unfault reports many findings: ```bash # Focus on one dimension at a time unfault review --dimension stability ``` The goal is actionable feedback, not comprehensive audits. ### ”I want to fix everything” [Section titled “”I want to fix everything””](#i-want-to-fix-everything) Resist the urge. Pre-commit review is for catching issues in *your changes*. Broader cleanup belongs in dedicated refactoring work. ## Next Steps [Section titled “Next Steps”](#next-steps) CI/CD Integration Automate reviews in your pipeline. [Read more](/docs/guides/cicd/) Configuration Customize rules and thresholds. [Read more](/docs/reference/configuration/) Rules Catalog Browse what Unfault detects. [Read more](/docs/reference/rules/) # SLO Discovery > Link your cloud SLOs to your codebase for route-level observability. Unfault can discover Service Level Objectives (SLOs) from your cloud provider and link them to the route handlers in your codebase. This gives you visibility into which routes are covered by SLOs and surfaces gaps in observability. ## Why This Matters [Section titled “Why This Matters”](#why-this-matters) SLOs define what “healthy” means for your service. But SLOs are typically configured at the service level in your cloud provider’s console, disconnected from the code that actually handles requests. When an SLO breach happens, you need to trace from the alert back to the code. Unfault bridges this gap by linking SLOs directly to your route handlers. ## Discovery [Section titled “Discovery”](#discovery) SLO discovery runs automatically when Unfault detects valid cloud credentials. Just run a review: ```bash unfault review ``` When credentials are present, Unfault queries your cloud project for existing SLOs and presents them interactively: ```plaintext We found SLOs in your GCP project that apply to entire services. Which service does this codebase deploy to? [1] unfault-prod-cloudrun-api-slo └─ 100% Availability - 99.9% over 30d └─ 99% Error Rate - <1% over 30d └─ 99% Latency - 95th < 500ms over 7d [s] Skip (don't link SLOs) > 1 ``` Select the service that matches your codebase. Unfault links the SLOs to your route handlers: ```plaintext ✓ Linked 3 SLO(s) to 375 route handler(s). (Saved to config, won't ask again for this workspace) ``` This mapping is saved locally. Future reviews remember the link without prompting again. ## Review Output with SLOs [Section titled “Review Output with SLOs”](#review-output-with-slos) Once linked, your review output includes observability coverage: ```plaintext → Analyzing backend... 7215ms Languages: python Frameworks: fastapi Dimensions: stability · correctness · performance Reviewed: 185 files · parse 81ms · engine 1551ms Cache: 100% Trace: 52924be6 Summary Looks good overall, with a couple spots that deserve a closer look. Two themes keep showing up: resilience hardening and other cleanup. Starting point: scripts/create-plan.py (HTTP call to external service in `run` lacks circuit breaker protec...); then scripts/trigger-throttle.py (HTTP call to external service in `run` lacks circuit breaker protec...). ──────────────────────────────────────────────────────────── 📊 Observability: 3 SLO(s) linked to 125/125 routes (100% coverage) ✓ All your HTTP routes are covered by SLOs. This gives you visibility into how users are experiencing your service. Tip: use --output full to drill into hotspots. ``` The observability section shows: * How many SLOs are linked * How many routes are covered * Coverage percentage * Whether any routes lack observability ## Supported Providers [Section titled “Supported Providers”](#supported-providers) Currently supported: | Provider | SLO Source | | ---------------- | --------------------- | | **Google Cloud** | Cloud Monitoring SLOs | Note Support for AWS CloudWatch SLOs and Datadog SLOs is planned. ## Authentication [Section titled “Authentication”](#authentication) SLO discovery requires authentication with your cloud provider. Unfault uses your existing credentials: **Google Cloud**: Application Default Credentials (ADC) ```bash gcloud auth application-default login ``` If credentials aren’t available, Unfault skips discovery and continues with the review. ## Configuration [Section titled “Configuration”](#configuration) After the initial link, the SLO mapping is stored in your workspace. You can view and modify it: ```bash unfault config show ``` To re-run discovery (useful if you’ve added new SLOs), force a cache refresh: ```bash unfault review --refresh-cache ``` To unlink SLOs and start fresh, remove the mapping from `.unfault/cache/enrichment/`. ## What Gets Linked [Section titled “What Gets Linked”](#what-gets-linked) Unfault links SLOs to: * **FastAPI routes** (`@app.get`, `@app.post`, etc.) * **Flask routes** (`@app.route`) * **Express routes** (`app.get`, `router.post`, etc.) * **Go HTTP handlers** (`http.HandleFunc`, gorilla/mux, chi) The link is semantic, not syntactic. Unfault understands your routing structure and maps SLOs to the handlers that serve traffic. ## Coverage Gaps [Section titled “Coverage Gaps”](#coverage-gaps) If some routes aren’t covered by SLOs, Unfault tells you: ```plaintext 📊 Observability: 2 SLO(s) linked to 118/125 routes (94% coverage) ⚠ 7 routes have no SLO coverage: → POST /internal/sync → GET /internal/health → POST /webhooks/stripe ... Consider whether these routes need observability. ``` Not every route needs an SLO. Internal health checks and webhook endpoints might be fine without one. The report helps you make that decision consciously. ## Next Steps [Section titled “Next Steps”](#next-steps) Dimensions Understand the Observability dimension. [Read more](/docs/concepts/dimensions/) CI/CD Integration Track SLO coverage over time. [Read more](/docs/guides/cicd/) # Suppressing Rules > How to customize which rules Unfault applies and when. Not every finding needs action. Sometimes a pattern that’s generally problematic is fine in your specific context. This guide explains how to suppress rules when they don’t apply. ## When to Suppress [Section titled “When to Suppress”](#when-to-suppress) Suppress a rule when: * **The context makes it safe.** An HTTP call to localhost doesn’t need the same timeout as one to an external service. * **You have safeguards elsewhere.** Maybe retry logic exists at a different layer. * **It’s intentional.** You’ve considered the trade-off and accepted the risk. * **It’s a false positive.** The rule doesn’t understand your specific pattern. Don’t suppress just to make findings go away. Each suppression should have a reason you could explain to a teammate. ## Inline Suppression [Section titled “Inline Suppression”](#inline-suppression) Suppress a specific finding with a comment on the line above: * Python ignore\[python.http.missing\_timeout] ```python response = requests.get(internal_url) # Internal service, <1ms latency ``` * Go ignore\[go.http.missing\_timeout] ```go resp, err := http.Get(internalURL) // Internal service, <1ms latency ``` * Rust ignore\[rust.http.missing\_timeout] ```rust let response = client.get(internal_url).send()?; // Internal service ``` * TypeScript ```typescript // unfault: ignore[typescript.http.missing_timeout] const response = await fetch(internalUrl); // Internal service ``` ### Multiple Rules [Section titled “Multiple Rules”](#multiple-rules) Suppress multiple rules on the same line: ```python # unfault: ignore[python.http.missing_timeout, python.http.missing_retry] response = requests.get(cache_url) # Local cache, fast and reliable ``` ### Adding Context [Section titled “Adding Context”](#adding-context) Add a reason after the rule list (recommended): ```python # unfault: ignore[python.http.missing_timeout] -- localhost cache, sub-ms response response = requests.get("http://localhost:6379/health") ``` The text after `--` is ignored by Unfault but helps humans understand why the suppression exists. ## File-Level Suppression [Section titled “File-Level Suppression”](#file-level-suppression) Suppress rules for an entire file by adding the comment at the top: ignore-file\[python.missing\_structured\_logging] ```python # This is a CLI script, not a service. Structured logging doesn't apply. import click @click.command() def main(): print("Running migration...") ``` This is useful for: * Scripts that aren’t production services * Test files with intentionally problematic patterns * Generated code ## Project-Level Suppression [Section titled “Project-Level Suppression”](#project-level-suppression) Suppress rules across your entire project in your manifest file: * Python pyproject.toml ```toml [tool.unfault.rules] exclude = [ "python.missing_structured_logging", # We use custom logging "python.http.missing_circuit_breaker", # Circuit breakers at gateway ] ``` * Go unfault.toml ```toml [rules] exclude = [ "go.missing_structured_logging", "go.http.missing_circuit_breaker", ] ``` * Rust Cargo.toml ```toml [package.metadata.unfault.rules] exclude = [ "rust.missing_structured_logging", "rust.http.missing_circuit_breaker", ] ``` * TypeScript package.json ```json { "unfault": { "rules": { "exclude": [ "typescript.missing_structured_logging", "typescript.http.missing_circuit_breaker" ] } } } ``` ### Path-Based Exclusions [Section titled “Path-Based Exclusions”](#path-based-exclusions) Exclude rules for specific paths: pyproject.toml ```toml [tool.unfault.rules] exclude = [ "python.http.*:scripts/*", # All HTTP rules in scripts/ "python.missing_timeout:tests/*", # Timeouts in tests ] ``` The pattern is `rule:path-glob`. ## Severity Overrides [Section titled “Severity Overrides”](#severity-overrides) Instead of suppressing entirely, you can lower a rule’s severity: pyproject.toml ```toml [tool.unfault.rules.severity] "python.bare_except" = "low" # Demote to low "python.http.missing_retry" = "medium" # Demote from high to medium ``` This keeps the finding visible but changes how it’s prioritized. Tip Severity overrides are good for rules you want to track but not gate on. The finding still appears; it just doesn’t block CI. ## Checking Suppressions [Section titled “Checking Suppressions”](#checking-suppressions) To see what’s being suppressed, review your configuration files and inline comments. Suppressions are documented where they’re defined: * **Inline**: Search for `unfault: ignore` in your code * **File-level**: Look for `unfault: ignore-file` at the top of files * **Project-level**: Check the `exclude` array in your `pyproject.toml`, `Cargo.toml`, or `package.json` Running `unfault review --output full` shows the rules that matched, making it easier to understand what’s being checked. ## Common Suppression Patterns [Section titled “Common Suppression Patterns”](#common-suppression-patterns) ### Internal Services [Section titled “Internal Services”](#internal-services) ```python # unfault: ignore[python.http.missing_timeout] -- internal service mesh, handled by sidecar response = requests.get(f"http://user-service/users/{user_id}") ``` ### Test Code [Section titled “Test Code”](#test-code) ```toml [tool.unfault.rules] exclude = [ "*:tests/*", # Exclude all rules in tests/ "*:*_test.py", # Exclude all rules in test files ] ``` ### Generated Code [Section titled “Generated Code”](#generated-code) ```python # unfault: ignore-file[*] # AUTO-GENERATED FILE - DO NOT EDIT # Generated by protoc-gen-python ``` ### Legacy Code [Section titled “Legacy Code”](#legacy-code) pyproject.toml ```toml [tool.unfault.rules] exclude = [ "*:legacy/*", # Legacy code, being migrated ] ``` Caution Blanket exclusions for legacy code should be temporary. Consider setting a reminder to revisit. ### Scripts vs Services [Section titled “Scripts vs Services”](#scripts-vs-services) pyproject.toml ```toml [tool.unfault.rules] exclude = [ "python.fastapi.*:scripts/*", # Scripts aren't FastAPI services "python.missing_structured_logging:scripts/*", # CLI output, not structured logs ] ``` ## Best Practices [Section titled “Best Practices”](#best-practices) ### Do [Section titled “Do”](#do) * Add a reason to inline suppressions * Use the narrowest suppression that works (inline > file > project) * Review project-level suppressions periodically * Use severity overrides instead of full suppression when possible ### Don’t [Section titled “Don’t”](#dont) * Suppress rules just to get a clean report * Use blanket `*` suppressions without good reason * Forget why something was suppressed (always add context) * Suppress in production code without team discussion ## Suppression vs Configuration [Section titled “Suppression vs Configuration”](#suppression-vs-configuration) | Approach | Use When | | ---------------------- | ---------------------------------------------------- | | **Inline suppression** | Specific line has valid reason to skip the rule | | **File suppression** | Entire file is different (script, test, generated) | | **Project exclusion** | Rule doesn’t fit your architecture at all | | **Severity override** | Rule applies but isn’t as important for your context | | **Dimension filter** | You want to focus on specific areas | ## Next Steps [Section titled “Next Steps”](#next-steps) Configuration Full configuration reference. [Read more](/docs/reference/configuration/) Rules Catalog See all available rules. [Browse rules](/docs/reference/rules/) CI/CD Integration Gate on specific severities. [Read more](/docs/guides/cicd/) # Troubleshooting > Common issues and how to resolve them. When something doesn’t work as expected, this guide helps you figure out why. ## Analysis Issues [Section titled “Analysis Issues”](#analysis-issues) ### ”No files found” [Section titled “”No files found””](#no-files-found) ```plaintext → Analyzing project... Found 0 matching source files ``` **Causes:** * Wrong directory * No supported language files * Files excluded by ignore rules (like `.gitignore`) **Solutions:** Check you’re in the right place: ```bash ls -la # Should see your source files ``` Check supported extensions exist: ```bash find . -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.ts" | head ``` Use verbose mode for more details: ```bash unfault review -v ``` Tip If your source files exist but Unfault still finds `0` files, check whether they’re ignored. Unfault respects `.gitignore` (including global gitignore and `.git/info/exclude`), `.ignore`, and `.dockerignore`. ### ”Unknown language” or “Unsupported framework” [Section titled “”Unknown language” or “Unsupported framework””](#unknown-language-or-unsupported-framework) Unfault supports Python, Go, Rust, and TypeScript. If you’re using a different language, analysis won’t run. For supported languages with unrecognized frameworks, analysis still works but framework-specific rules won’t apply. ### Analysis is slow [Section titled “Analysis is slow”](#analysis-is-slow) Large codebases take time, but if analysis seems stuck: **Check for symlink loops:** ```bash find . -type l -exec test -e {} \; -print ``` **Narrow the scope by running from a subdirectory:** ```bash # Instead of the whole repo cd src unfault review ``` **Use verbose mode to see progress:** ```bash unfault review -v ``` ### Different results locally vs CI [Section titled “Different results locally vs CI”](#different-results-locally-vs-ci) Common causes: 1. **Different working directories** Make sure you’re running from the same directory in both environments. 2. **Different git state** ```bash # CI might have uncommitted changes excluded git status ``` 3. **Configuration differences** Check which config is loaded with verbose mode: ```bash unfault review -v ``` ## Output Issues [Section titled “Output Issues”](#output-issues) ### JSON output is malformed [Section titled “JSON output is malformed”](#json-output-is-malformed) If JSON output appears truncated or malformed: ```bash # Ensure nothing else writes to stdout unfault review --output json 2>/dev/null > results.json ``` ### “Finding not actionable” [Section titled ““Finding not actionable””](#finding-not-actionable) If a finding doesn’t make sense for your code: 1. Check if it’s a false positive specific to your pattern 2. Use inline suppression with a comment explaining why: ```python # unfault: ignore[rule.id] -- reason this doesn't apply ``` 3. Report it if you think it’s a bug in the rule ## LLM Issues [Section titled “LLM Issues”](#llm-issues) ### ”LLM provider not configured” [Section titled “”LLM provider not configured””](#llm-provider-not-configured) The `unfault review` LLM summary feature requires a configured LLM provider. Configure one with: ```bash # OpenAI unfault config llm openai --model gpt-4o # Anthropic unfault config llm anthropic --model claude-3-5-sonnet-latest # Ollama (local) unfault config llm ollama --model llama3.2 ``` Check current config: ```bash unfault config show unfault config llm show ``` ### “ANTHROPIC\_API\_KEY not set” / “OPENAI\_API\_KEY not set” [Section titled ““ANTHROPIC\_API\_KEY not set” / “OPENAI\_API\_KEY not set””](#anthropic_api_key-not-set--openai_api_key-not-set) The LLM provider needs an API key. Set the relevant environment variable: ```bash export ANTHROPIC_API_KEY="..." export OPENAI_API_KEY="sk-..." ``` Or pass the key directly when configuring: ```bash unfault config llm anthropic --api-key "..." ``` ## Observability Integration Issues [Section titled “Observability Integration Issues”](#observability-integration-issues) ### SLO data not appearing [Section titled “SLO data not appearing”](#slo-data-not-appearing) If you expect SLO enrichment but it’s not showing: 1. Check integration credentials are present: ```bash unfault config integrations show ``` 2. Verify connectivity: ```bash unfault config integrations verify ``` 3. Run without `--offline` (if you were using it): ```bash unfault review # Without --offline ``` 4. If cache may be stale, refresh it: ```bash unfault review --refresh-cache ``` ### Credentials not detected [Section titled “Credentials not detected”](#credentials-not-detected) Unfault reads credentials from environment variables. Check the relevant provider: | Provider | Variables | | --------- | --------------------------------------------------------------------------- | | GCP | Application Default Credentials via `gcloud auth application-default login` | | Datadog | `DD_API_KEY`, `DD_APP_KEY` | | Dynatrace | `DT_API_TOKEN`, `DT_ENVIRONMENT_URL` | Run `unfault config integrations show` to see what Unfault detects. ## Configuration Issues [Section titled “Configuration Issues”](#configuration-issues) ### ”Config not found” or wrong config loaded [Section titled “”Config not found” or wrong config loaded”](#config-not-found-or-wrong-config-loaded) Use verbose mode to see which config Unfault is loading: ```bash unfault review -v ``` **Common issues:** * Config is in wrong file (should be `pyproject.toml`, `Cargo.toml`, `package.json`, or `.unfault.toml`) * TOML/JSON syntax errors in config * Config key is misspelled **Validate your config:** ```bash # For TOML python -c "import tomllib; tomllib.load(open('pyproject.toml', 'rb'))" # For JSON python -c "import json; json.load(open('package.json'))" ``` ### Rules not being excluded [Section titled “Rules not being excluded”](#rules-not-being-excluded) If rules you’ve excluded still appear: 1. Check the path pattern matches: ```toml # This excludes the rule everywhere exclude = ["python.http.missing_timeout"] # This excludes it only in tests/ exclude = ["python.http.missing_timeout:tests/*"] ``` 2. Check for typos in rule IDs by looking at the finding output ## LSP / Editor Issues [Section titled “LSP / Editor Issues”](#lsp--editor-issues) ### No diagnostics appearing in editor [Section titled “No diagnostics appearing in editor”](#no-diagnostics-appearing-in-editor) 1. Check the language server output: run `unfault lsp --verbose` manually and look for errors. 2. Verify `unfault` is on your `PATH` and runs without errors. 3. Check that your project root has a recognizable manifest file (`pyproject.toml`, `Cargo.toml`, `go.mod`, `package.json`, etc.). ### Slow first analysis in editor [Section titled “Slow first analysis in editor”](#slow-first-analysis-in-editor) The first run builds the full semantic graph for your project. Subsequent interactions use a cache and should be fast. ## Verbose Mode [Section titled “Verbose Mode”](#verbose-mode) For detailed diagnostics: ```bash unfault review -v ``` This shows: * Which config files are loaded * Workspace detection details * Files found and ignored Tip When reporting issues, include the verbose output. It helps narrow down what’s happening. ## Getting Help [Section titled “Getting Help”](#getting-help) ### Check your configuration [Section titled “Check your configuration”](#check-your-configuration) ```bash unfault config show ``` This shows your LLM and integration configuration. ### Report an issue [Section titled “Report an issue”](#report-an-issue) If you’ve found a bug or unexpected behavior: 1. Check existing issues: [github.com/unfault/unfault/issues](https://github.com/unfault/unfault/issues) 2. If new, open an issue with: * Unfault version (`unfault --version`) * OS and architecture * Verbose output (`unfault review -v`) * Minimal reproduction steps ## Quick Reference [Section titled “Quick Reference”](#quick-reference) | Symptom | Likely Cause | First Step | | ----------------------- | --------------------------- | ---------------------------------- | | ”No files found” | Wrong directory | Check `ls` and `pwd` | | Slow analysis | Large codebase | Run from subdirectory | | Different results in CI | Different working directory | Ensure same path | | LLM not working | No provider configured | `unfault config llm show` | | Rules not excluded | Path pattern mismatch | Check TOML syntax | | SLO data missing | Missing credentials | `unfault config integrations show` | # Understanding Unfamiliar Code > Use Unfault to navigate and understand codebases you didn't write. You’re working in a codebase you didn’t write. Maybe you just joined the team, or you’re fixing a bug in a service you’ve never touched. You need to understand how things connect before you start changing them. Unfault’s code graph helps with this. ## Finding What Depends on a File [Section titled “Finding What Depends on a File”](#finding-what-depends-on-a-file) Before changing a file, see what else depends on it: ```bash unfault graph impact src/api/auth.py ``` Output: ```plaintext Impact analysis for src/api/auth.py: Direct dependents (3): → api/routes/users.py (imports validate_token) → api/routes/admin.py (imports validate_token, get_permissions) → tests/test_auth.py (imports validate_token) Transitive impact (7 files total) ``` This tells you the blast radius. If you change `validate_token`, those three files are directly affected. Seven files total might see different behavior. ## Finding What Depends on a Function [Section titled “Finding What Depends on a Function”](#finding-what-depends-on-a-function) For more precision, check a specific function: ```bash unfault graph function-impact src/api/auth.py:validate_token ``` This shows only what depends on that function, not everything in the file. ## Finding the Most Important Files [Section titled “Finding the Most Important Files”](#finding-the-most-important-files) When you’re new to a codebase, start with the most connected files: ```bash unfault graph critical ``` Output: ```plaintext Most critical files (by dependents): 1. src/core/models.py (47 dependents) 2. src/utils/helpers.py (31 dependents) 3. src/api/auth.py (23 dependents) 4. src/db/connection.py (19 dependents) 5. src/config/settings.py (18 dependents) ``` These are the files where changes have the most impact. They’re also often the best starting point for understanding how the system fits together. ## Finding What a File Depends On [Section titled “Finding What a File Depends On”](#finding-what-a-file-depends-on) To see what external libraries or internal modules a file uses: ```bash unfault graph deps src/api/client.py ``` Output: ```plaintext Dependencies for src/api/client.py: External: · requests · tenacity · structlog Internal: · src/config/settings.py · src/utils/retry.py ``` This tells you what you need to understand to work on this file. ## Finding Files That Use a Library [Section titled “Finding Files That Use a Library”](#finding-files-that-use-a-library) To see everywhere a library is used: ```bash unfault graph library requests ``` Output: ```plaintext Files using 'requests': · src/api/client.py:12 · src/integrations/billing.py:8 · src/integrations/notifications.py:15 · scripts/health_check.py:3 ``` Useful when you’re updating a dependency or changing how it’s used across the codebase. ## Querying Patterns [Section titled “Querying Patterns”](#querying-patterns) For understanding how something is handled across a codebase, combine review and graph commands: ```bash # See where a library is used to understand conventions unfault graph library requests # Find files with stability issues to see what's been addressed elsewhere unfault review --dimension stability --output json | jq '.contexts[].findings[] | .file_path' | sort -u ``` Run a full review first to understand the overall picture: ```bash unfault review ``` ## Before Making Changes [Section titled “Before Making Changes”](#before-making-changes) A practical workflow for changing unfamiliar code: 1. **Check impact first** ```bash unfault graph impact path/to/file.py ``` 2. **Understand what the file depends on** ```bash unfault graph deps path/to/file.py ``` 3. **Find how similar things are done** ```bash unfault graph library ``` 4. **Make your changes** 5. **Review before committing** ```bash unfault review ``` ## With AI Agents [Section titled “With AI Agents”](#with-ai-agents) If you’re using an AI assistant to modify unfamiliar code, add to your AGENTS.md: ```text Before modifying code in unfamiliar areas: 1. Run: unfault graph impact 2. If impact is large (>10 files), mention this before proceeding 3. Run: unfault graph deps to understand what it relies on 4. After changes, run: unfault review ``` This gives the agent structural context it wouldn’t otherwise have. ## Next Steps [Section titled “Next Steps”](#next-steps) Code Graph Reference All graph commands and options. [Read more](/docs/guides/code-graph/) Pre-commit Review Review your changes before committing. [Read more](/docs/guides/pre-commit-review/) Querying the Codebase Graph and review commands for exploration. [Read more](/docs/guides/asking-questions/) Use with AI Agents Integrate with AI coding assistants. [Read more](/docs/guides/agents/) # LSP / Editor Integration > Using the Unfault language server with your editor. Caution A packaged VS Code extension is not currently distributed. This page describes the LSP server that ships with the CLI. It can be connected to editors that support LSP, but requires manual configuration. ## The LSP Server [Section titled “The LSP Server”](#the-lsp-server) The Unfault CLI includes a full Language Server Protocol server. Start it with: ```bash unfault lsp ``` It communicates over stdio, which is the standard transport for most LSP clients. The LSP server runs the same local analysis pipeline as `unfault review` (Tree-sitter parsing, semantic graph construction, and rule analysis) and publishes diagnostics in real time as you work. ## What the LSP Provides [Section titled “What the LSP Provides”](#what-the-lsp-provides) * **Diagnostics**: Inline squiggles for findings (Error for critical/high, Warning for medium, Info for low) * **Code actions**: Quick-fix suggestions from structured patches or unified diffs * **File centrality**: Custom notification `unfault/fileCentrality` (how central the current file is in the dependency graph) * **File dependencies**: Custom notification `unfault/fileDependencies` (files that depend on the current one) * **Hover**: Function impact analysis using call graph traversal ## Connecting an Editor [Section titled “Connecting an Editor”](#connecting-an-editor) ### Neovim (nvim-lspconfig) [Section titled “Neovim (nvim-lspconfig)”](#neovim-nvim-lspconfig) ```lua local lspconfig = require('lspconfig') local configs = require('lspconfig.configs') if not configs.unfault then configs.unfault = { default_config = { cmd = { 'unfault', 'lsp' }, filetypes = { 'python', 'go', 'rust', 'typescript', 'javascript' }, root_dir = lspconfig.util.root_pattern( 'pyproject.toml', 'Cargo.toml', 'go.mod', 'package.json' ), }, } end lspconfig.unfault.setup {} ``` ### VS Code (manual) [Section titled “VS Code (manual)”](#vs-code-manual) Create `.vscode/settings.json` with a custom LSP client extension, or use a generic LSP client extension that accepts a command. Point it at `unfault lsp --stdio`. ## Supported Languages [Section titled “Supported Languages”](#supported-languages) * Python * Go * Rust * TypeScript / JavaScript ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) **No diagnostics appearing** 1. Check the language server output: run `unfault lsp --verbose` manually and look for errors. 2. Verify `unfault` is on your `PATH` and runs without errors. 3. Check that your project root has a recognizable manifest file (`pyproject.toml`, `Cargo.toml`, `go.mod`, `package.json`, etc.). **Slow first analysis** The first run builds the full semantic graph for your project. Subsequent interactions use a cache. # Installation > How to install the Unfault CLI. Unfault is an open-source CLI. No account required. Download the binary and run it against your code. ## Install the CLI [Section titled “Install the CLI”](#install-the-cli) * macOS (Apple Silicon) ```bash mkdir -p ~/.local/bin curl -L -o ~/.local/bin/unfault https://github.com/unfault/cli/releases/latest/download/unfault-aarch64-apple-darwin chmod +x ~/.local/bin/unfault ``` * macOS (Intel) ```bash mkdir -p ~/.local/bin curl -L -o ~/.local/bin/unfault https://github.com/unfault/cli/releases/latest/download/unfault-x86_64-apple-darwin chmod +x ~/.local/bin/unfault ``` * Linux (x86\_64) ```bash 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 ``` * Linux (aarch64) ```bash mkdir -p ~/.local/bin curl -L -o ~/.local/bin/unfault https://github.com/unfault/cli/releases/latest/download/unfault-aarch64-unknown-linux-gnu chmod +x ~/.local/bin/unfault ``` * Windows (PowerShell) ```powershell $dest = "$env:USERPROFILE\bin\unfault.exe" New-Item -ItemType Directory -Force (Split-Path $dest) | Out-Null Invoke-WebRequest -Uri "https://github.com/unfault/cli/releases/latest/download/unfault-x86_64-pc-windows-msvc.exe" -OutFile $dest ``` * Cargo (from source) ```bash cargo install unfault ``` Downloads go to the [latest release on GitHub](https://github.com/unfault/cli/releases/latest). Put the binary somewhere on your `PATH` (e.g. `~/.local/bin` on Linux/macOS). Verify it works: ```bash unfault --version ``` ## Optional: Configure an LLM [Section titled “Optional: Configure an LLM”](#optional-configure-an-llm) Some features (like AI-powered review summaries) require a configured LLM provider. This is optional. The core `review` and `graph` commands work without it. Supported providers: OpenAI, Anthropic, Ollama, any OpenAI-compatible endpoint. ```bash # OpenAI unfault config llm openai --model gpt-4o # Anthropic unfault config llm anthropic --model claude-3-5-sonnet-latest # Local Ollama unfault config llm ollama --model llama3.2 ``` See [Configuration](/docs/reference/configuration/) for the full reference. ## Optional: Configure Observability Integrations [Section titled “Optional: Configure Observability Integrations”](#optional-configure-observability-integrations) To enrich reviews with SLO data from your cloud platform, set the relevant credentials before running `unfault review`: | Provider | Required Variables | | -------------------- | ------------------------------------------------------------------------- | | GCP Cloud Monitoring | Application Default Credentials (`gcloud auth application-default login`) | | Datadog | `DD_API_KEY`, `DD_APP_KEY` | | Dynatrace | `DT_API_TOKEN`, `DT_ENVIRONMENT_URL` | Run `unfault config integrations show` to check what’s detected, or `unfault config integrations verify` to test connectivity. ## Next Steps [Section titled “Next Steps”](#next-steps) Quick Start Run your first review and understand the output. [Get started](/docs/quick-start) How It Works Understand what happens when you run a review. [Learn more](/docs/concepts/how-it-works) # Quick Start > Run your first Unfault review in under a minute. This guide walks you through running your first review and understanding the results. ## Run Your First Review [Section titled “Run Your First Review”](#run-your-first-review) Navigate to your project directory and run: ```bash unfault review ``` Unfault parses your code locally and shows what it finds: ```plaintext Worth looking out for 🟡 main.py:12 · The Slow Death FastAPI app `app` has no request timeout middleware ↳ puts App A Availability SLO at risk (100%) A downstream dependency slows down and your service holds threads/connections until it saturates and dies. Tradeoff ↳ Simplicity no timeout means less code and fewer configuration decisions at call time. ↳ Systemic Availability a single slow dependency can exhaust the thread pool and take down the entire service. 🟡 main.py:36 · The Retry Storm HTTP call via `httpx`.AsyncClient has no retry policy ↳ puts App A Availability SLO at risk (100%) During an outage your service retries failures instantly, preventing the downstream service from ever recovering. Tradeoff ↳ Local Availability retries transparently mask transient failures from the caller, improving perceived reliability. ↳ Systemic Metastability synchronized retries with no backoff create thunderstorms that prevent downstream services from ever recovering. app-a python · fastapi · 1 file [ parse 5ms engine 0ms fetch 7849ms ] ``` Each finding names the failure mode, links it to any discovered SLOs at risk, explains the tradeoff, and gives a file and line number. ## Drilling Into Details [Section titled “Drilling Into Details”](#drilling-into-details) To see full findings with suggested fixes: ```bash unfault review --output full ``` This shows each finding with its location, explanation, and a suggested code change. ## Output Formats [Section titled “Output Formats”](#output-formats) Choose the format that fits your workflow: * Default (basic) ```bash # Header + summary (good for the terminal) unfault review ``` * Concise ```bash # Statistics only unfault review --output concise ``` * Full ```bash # Full findings with diffs unfault review --output full ``` * JSON ```bash # Machine-readable for CI/CD or scripting unfault review --output json ``` * SARIF ```bash # GitHub Code Scanning unfault review --output sarif ``` ## Filtering by Dimension [Section titled “Filtering by Dimension”](#filtering-by-dimension) Focus on specific types of issues: ```bash # Only stability issues unfault review --dimension stability # Only performance issues unfault review --dimension performance # Multiple dimensions unfault review --dimension stability --dimension correctness ``` Available dimensions: `stability`, `correctness`, `performance`, `scalability`. ## CI/CD Integration [Section titled “CI/CD Integration”](#cicd-integration) Use exit codes to gate deployments: ```bash unfault review if [ $? -eq 5 ]; then echo "Findings detected. Blocking deployment." exit 1 fi ``` Tip Exit code `5` means findings were detected. See the [CLI reference](/docs/reference/cli/#exit-codes) for all exit codes. ## Explore the Code Graph [Section titled “Explore the Code Graph”](#explore-the-code-graph) Before a refactor, check what depends on a file: ```bash unfault graph impact src/api/auth.py ``` Find the most heavily-connected files: ```bash unfault graph critical ``` See all files using a library: ```bash unfault graph library requests ``` ## Next Steps [Section titled “Next Steps”](#next-steps) CLI Commands Learn all CLI commands in detail. [Read more](/docs/guides/cli/) Code Graph Explore dependencies and blast radius. [Read more](/docs/guides/code-graph/) CI/CD Pipeline Add Unfault to your build process. [Read more](/docs/guides/cicd/) AI Agents Give your coding assistant codebase context. [Read more](/docs/guides/agents/) # Output Reference > JSON schemas for unfault review output. This page documents the JSON output schema for `unfault review --output json`. Useful when parsing CLI output programmatically or building integrations. Tip For background on core concepts, see [Dimensions](/docs/concepts/dimensions/). ## Review JSON Output [Section titled “Review JSON Output”](#review-json-output) When you run `unfault review --output json`, the CLI prints a JSON object with the analysis results. ### Top-Level Schema [Section titled “Top-Level Schema”](#top-level-schema) ```json { "meta": { ... }, "contexts": [ ... ], "elapsed_ms": 142 } ``` ### Meta Object [Section titled “Meta Object”](#meta-object) ```json { "label": "payments-service", "languages": ["python"], "framework_guesses": ["fastapi"], "requested_dimensions": ["stability", "correctness"] } ``` | Field | Type | Description | | ---------------------- | ------ | ------------------------------------------------ | | `label` | string | Workspace label (usually directory name) | | `languages` | array | Detected programming languages | | `framework_guesses` | array | Detected frameworks (e.g., `fastapi`, `express`) | | `requested_dimensions` | array | Dimensions that were analyzed | ### Context Object [Section titled “Context Object”](#context-object) Each context groups findings together. Currently there is one context per workspace: ```json { "context_id": "ctx_workspace", "label": "Workspace", "findings": [ ... ] } ``` ### Finding Object [Section titled “Finding Object”](#finding-object) A finding is a pattern detected by a rule: ```json { "id": "python.http.missing_timeout:api/client.py:42", "rule_id": "python.http.missing_timeout", "kind": "StabilityRisk", "title": "HTTP call has no timeout", "description": "This HTTP call via requests.get has no timeout specified...", "severity": "High", "confidence": 0.9, "dimension": "stability", "file_path": "api/client.py", "line": 42, "column": 5, "end_line": 42, "end_column": 45, "fix_preview": "Add timeout parameter: requests.get(url, timeout=30)" } ``` | Field | Type | Description | | ------------- | --------------- | ---------------------------------------------------- | | `id` | string | Unique finding identifier (`rule_id:file_path:line`) | | `rule_id` | string | Rule that produced this finding | | `kind` | enum | Finding category (see below) | | `title` | string | Short title | | `description` | string | Detailed explanation | | `severity` | enum | `Critical`, `High`, `Medium`, `Low`, `Info` | | `confidence` | float | Confidence score (0.0 to 1.0) | | `dimension` | string | Which dimension this finding relates to | | `file_path` | string | File path where the finding was detected | | `line` | integer | Line number (1-based) | | `column` | integer \| null | Column number (1-based) | | `end_line` | integer \| null | End line number | | `end_column` | integer \| null | End column number | | `fix_preview` | string \| null | Human-readable summary of suggested fix | ### Finding Kinds [Section titled “Finding Kinds”](#finding-kinds) | Kind | Description | | ----------------------- | --------------------------------------------- | | `StabilityRisk` | Could cause service instability or outages | | `PerformanceSmell` | May impact performance | | `BehaviorThreat` | Unexpected behavior under certain conditions | | `AntiPattern` | Code pattern that typically leads to problems | | `ResourceLeak` | Potential resource leak (connections, memory) | | `ReliabilityRisk` | Could affect service reliability | | `SecurityVulnerability` | Security concern | ### Severity Levels [Section titled “Severity Levels”](#severity-levels) | Severity | Description | | ---------- | ------------------------- | | `Critical` | Immediate action required | | `High` | Should be addressed soon | | `Medium` | Worth addressing | | `Low` | Minor improvement | | `Info` | Informational | ### Dimensions [Section titled “Dimensions”](#dimensions) | Dimension | Description | | ----------------- | ---------------------------------- | | `stability` | Service stability and resilience | | `performance` | Execution speed and resource usage | | `correctness` | Logic and behavior correctness | | `scalability` | Ability to handle increased load | | `observability` | Logging, tracing, and monitoring | | `reliability` | Consistent operation over time | | `security` | Security posture | | `maintainability` | Code maintainability | ### Complete Example [Section titled “Complete Example”](#complete-example) ```json { "meta": { "label": "payments-service", "languages": ["python"], "framework_guesses": ["fastapi"], "requested_dimensions": ["stability", "correctness"] }, "contexts": [ { "context_id": "ctx_workspace", "label": "Workspace", "findings": [ { "id": "python.http.missing_timeout:src/client.py:42", "rule_id": "python.http.missing_timeout", "kind": "StabilityRisk", "title": "HTTP call has no timeout", "description": "This HTTP call via requests.get has no timeout specified. If the remote service is slow or unresponsive, this call will block indefinitely.", "severity": "High", "confidence": 0.9, "dimension": "stability", "file_path": "src/client.py", "line": 42, "column": 5, "fix_preview": "Add timeout parameter: requests.get(url, timeout=30)" } ] } ], "elapsed_ms": 142 } ``` *** ## Exit Codes [Section titled “Exit Codes”](#exit-codes) | Code | Meaning | | ---- | -------------------- | | 0 | Success, no findings | | 1 | General error | | 2 | Configuration error | | 4 | Network error | | 5 | Findings detected | | 6 | Invalid input | Tip Exit code 5 indicates findings were detected. This is not an error. Use it in CI to gate deployments. # CLI Commands > Complete reference for the Unfault command-line interface. Complete reference for all Unfault CLI commands. ## Commands [Section titled “Commands”](#commands) | Command | Purpose | | ------------------- | ---------------------------------------------- | | [`review`](#review) | Analyze code for production-readiness | | [`lint`](#lint) | Show all findings grouped by severity and rule | | [`graph`](#graph) | Explore code dependencies and impact | | [`info`](#info) | Look up SRE glossary entries | | [`config`](#config) | Manage CLI settings | | [`lsp`](#lsp) | Start the language server for IDE integration | *** ## `review` [Section titled “review”](#review) Analyze code and get recommendations. Runs entirely locally. ```bash unfault review [OPTIONS] ``` ### Options [Section titled “Options”](#options) | Option | Description | Default | | ----------------------- | ----------------------------------------------------------------------------------------------------- | ---------------- | | `--output ` | Output format: `basic`, `concise`, `full`, `json`, `sarif` | `basic` | | `-v, --verbose` | Enable verbose output | Disabled | | `--profile ` | Override the detected profile (e.g., `python_fastapi_backend`) | Auto-detected | | `-d, --dimension ` | Dimensions to analyze (repeatable). Options: `stability`, `correctness`, `performance`, `scalability` | All from profile | | `--fix` | Auto-apply all suggested fixes | Disabled | | `--dry-run` | Show fixes without applying them | Disabled | | `--all` | Show all findings in full (equivalent to `unfault lint`) | Disabled | | `--refresh-cache` | Discard enrichment cache and re-fetch SLOs and traces | Disabled | | `--offline` | Skip SLO and trace fetching entirely (useful in CI) | Disabled | ### File Discovery and Ignores [Section titled “File Discovery and Ignores”](#file-discovery-and-ignores) Unfault respects common ignore conventions: * `.gitignore` (including global gitignore and `.git/info/exclude`) * `.ignore` * `.dockerignore` It also skips common dependency/build directories (`node_modules`, `target`, `dist`, `build`, `.venv`) even if they are not explicitly ignored. ### Output Formats [Section titled “Output Formats”](#output-formats) | Format | Use Case | | --------- | ---------------------------------------------------------- | | `basic` | Default. Header, summary, and guidance on next steps. | | `concise` | Brief statistics only. Good for dashboards. | | `full` | Detailed findings with file locations and suggested fixes. | | `json` | Machine-readable. Use for CI/CD parsing or custom tooling. | | `sarif` | GitHub Code Scanning and IDE integration. | ### Examples [Section titled “Examples”](#examples) ```bash # Standard review unfault review # Focus on stability issues only unfault review --dimension stability # Full details with suggested fixes unfault review --output full # JSON for CI parsing unfault review --output json # Skip observability enrichment unfault review --offline # Preview fixes without applying unfault review --dry-run ``` *** ## `lint` [Section titled “lint”](#lint) Show all findings grouped by severity and rule, the detailed linter view. ```bash unfault lint [OPTIONS] ``` ### Options [Section titled “Options”](#options-1) | Option | Description | Default | | ----------------------- | ---------------------------------- | ---------------- | | `--output ` | Output format: `basic`, `json` | `basic` | | `-v, --verbose` | Enable verbose output | Disabled | | `--profile ` | Override detected profile | Auto-detected | | `-d, --dimension ` | Dimensions to analyze (repeatable) | All from profile | | `--fix` | Auto-apply all suggested fixes | Disabled | | `--dry-run` | Show fixes without applying them | Disabled | *** ## `graph` [Section titled “graph”](#graph) Query the code graph for dependencies, impact analysis, and critical files. Runs locally. ```bash unfault graph ``` ### Subcommands [Section titled “Subcommands”](#subcommands) | Subcommand | Purpose | | ------------------------------------------- | -------------------------------------- | | [`impact`](#graph-impact) | What breaks if I change this file? | | [`function-impact`](#graph-function-impact) | What breaks if I change this function? | | [`library`](#graph-library) | Which files use a specific library? | | [`deps`](#graph-deps) | What does this file depend on? | | [`critical`](#graph-critical) | Which files are most critical? | | [`stats`](#graph-stats) | Code graph statistics | | [`dump`](#graph-dump) | Dump graph for debugging | *** ### `graph impact` [Section titled “graph impact”](#graph-impact) Analyze what depends on a file. Useful before refactoring. ```bash unfault graph impact [OPTIONS] ``` | Option | Description | Default | | ------------------------ | -------------------------------- | ----------------- | | `` | File path to analyze | Required | | `-w, --workspace ` | Workspace path | Current directory | | `--max-depth ` | Transitive analysis depth (1-10) | 5 | | `--json` | Output as JSON | Disabled | | `-v, --verbose` | Verbose output | Disabled | ```bash unfault graph impact src/api/auth.py unfault graph impact --max-depth 3 src/core/models.py ``` *** ### `graph function-impact` [Section titled “graph function-impact”](#graph-function-impact) Analyze what depends on a specific function. ```bash unfault graph function-impact [OPTIONS] ``` | Option | Description | Default | | ------------------------ | ---------------------------------- | ----------------- | | `` | Function in format `file:function` | Required | | `-w, --workspace ` | Workspace path | Current directory | | `--max-depth ` | Transitive analysis depth (1-10) | 5 | | `--json` | Output as JSON | Disabled | ```bash unfault graph function-impact src/api/auth.py:validate_token ``` *** ### `graph library` [Section titled “graph library”](#graph-library) Find files that use a specific library. ```bash unfault graph library [OPTIONS] ``` | Option | Description | Default | | ------------------------ | ------------------------------------------ | ----------------- | | `` | Library name (e.g., `requests`, `fastapi`) | Required | | `-w, --workspace ` | Workspace path | Current directory | | `--json` | Output as JSON | Disabled | ```bash unfault graph library requests unfault graph library sqlalchemy --json ``` *** ### `graph deps` [Section titled “graph deps”](#graph-deps) Find external dependencies of a file. ```bash unfault graph deps [OPTIONS] ``` | Option | Description | Default | | ------------------------ | -------------------- | ----------------- | | `` | File path to analyze | Required | | `-w, --workspace ` | Workspace path | Current directory | | `--json` | Output as JSON | Disabled | ```bash unfault graph deps src/api/routes.py ``` *** ### `graph critical` [Section titled “graph critical”](#graph-critical) Find the most critical files in the codebase (high connectivity, many dependents). ```bash unfault graph critical [OPTIONS] ``` | Option | Description | Default | | ------------------------ | -------------------------------- | ----------------- | | `-n, --limit ` | Number of files to return (1-50) | 10 | | `--sort-by ` | Sort metric (see below) | `in-degree` | | `-w, --workspace ` | Workspace path | Current directory | | `--json` | Output as JSON | Disabled | **Sort metrics:** | Metric | Meaning | | ------------------ | ----------------------------- | | `in-degree` | Files most imported by others | | `out-degree` | Files that import the most | | `total-degree` | Total connectivity | | `library-usage` | External libraries used | | `importance-score` | Weighted importance | ```bash unfault graph critical unfault graph critical --limit 20 --sort-by total-degree ``` *** ### `graph stats` [Section titled “graph stats”](#graph-stats) Get code graph statistics. ```bash unfault graph stats [OPTIONS] ``` | Option | Description | Default | | ------------------------ | -------------- | ----------------- | | `-w, --workspace ` | Workspace path | Current directory | | `--json` | Output as JSON | Disabled | *** ### `graph dump` [Section titled “graph dump”](#graph-dump) Dump the local code graph for debugging. ```bash unfault graph dump [OPTIONS] ``` | Option | Description | Default | | ------------------------ | ----------------------- | ----------------- | | `-w, --workspace ` | Workspace path | Current directory | | `--calls-only` | Output only call edges | Disabled | | `--file ` | Dump only specific file | All files | *** ## `info` [Section titled “info”](#info) Look up SRE glossary entries for failure modes. ```bash unfault info ``` Available IDs: `SLO-001` through `SLO-006`. ```bash unfault info SLO-001 # Slow Death unfault info SLO-002 # Retry Storm unfault info SLO-003 # Zombie Process unfault info SLO-004 # Thundering Herd unfault info SLO-005 # Blackhole unfault info SLO-006 # Cascade ``` *** ## `config` [Section titled “config”](#config) Manage CLI configuration. ```bash unfault config ``` ### Subcommands [Section titled “Subcommands”](#subcommands-1) | Subcommand | Purpose | | -------------- | ------------------------------------------------------ | | `show` | Display current configuration | | `llm` | Manage LLM provider configuration | | `integrations` | Inspect and verify observability integrations | | `agent` | Generate agent skill files for Claude Code or OpenCode | *** ### `config show` [Section titled “config show”](#config-show) Display current configuration (secrets masked by default). ```bash unfault config show [--show-secrets] ``` *** ### `config llm` [Section titled “config llm”](#config-llm) Configure an LLM provider for AI-powered review summaries. ```bash unfault config llm ``` #### OpenAI [Section titled “OpenAI”](#openai) ```bash unfault config llm openai [OPTIONS] ``` | Option | Description | Default | | --------------------- | ------------------------------------------ | -------- | | `-m, --model ` | Model name | `gpt-4` | | `-k, --api-key ` | API key (prefers `OPENAI_API_KEY` env var) | From env | ```bash unfault config llm openai --model gpt-4o ``` #### Anthropic [Section titled “Anthropic”](#anthropic) ```bash unfault config llm anthropic [OPTIONS] ``` | Option | Description | Default | | --------------------- | --------------------------------------------- | -------------------------- | | `-m, --model ` | Model name | `claude-3-5-sonnet-latest` | | `-k, --api-key ` | API key (prefers `ANTHROPIC_API_KEY` env var) | From env | ```bash unfault config llm anthropic --model claude-sonnet-4-5 ``` #### Ollama [Section titled “Ollama”](#ollama) ```bash unfault config llm ollama [OPTIONS] ``` | Option | Description | Default | | ---------------------- | ------------------- | ------------------------ | | `-e, --endpoint ` | Ollama API endpoint | `http://localhost:11434` | | `-m, --model ` | Model name | `llama3.2` | ```bash unfault config llm ollama --model mistral ``` #### Custom (OpenAI-compatible) [Section titled “Custom (OpenAI-compatible)”](#custom-openai-compatible) ```bash unfault config llm custom --endpoint --model [OPTIONS] ``` | Option | Description | Default | | ---------------------- | ------------ | -------- | | `-e, --endpoint ` | API endpoint | Required | | `-m, --model ` | Model name | Required | | `-k, --api-key ` | API key | None | #### Other LLM commands [Section titled “Other LLM commands”](#other-llm-commands) ```bash unfault config llm show # Show current LLM config unfault config llm show --show-secrets unfault config llm remove # Remove LLM config ``` *** ### `config integrations` [Section titled “config integrations”](#config-integrations) Inspect and verify observability provider credentials. ```bash unfault config integrations show # Show detected integrations (no network calls) unfault config integrations verify # Verify by making live API calls ``` Supported providers: GCP Cloud Monitoring, Datadog, Dynatrace. *** ### `config agent` [Section titled “config agent”](#config-agent) Generate agent skill files so Claude Code or OpenCode can use Unfault as a structured tool. ```bash unfault config agent claude [--global] [--dry-run] unfault config agent opencode [--global] [--dry-run] ``` | Option | Description | | ----------- | --------------------------------------------------------------------------------------------- | | `--global` | Write to `~/.claude/skills/` or `~/.config/opencode/skills/` instead of the project directory | | `--dry-run` | Print what would be created without writing files | *** ## `lsp` [Section titled “lsp”](#lsp) Start the Language Server Protocol server for IDE integration (stdio transport). ```bash unfault lsp [OPTIONS] ``` | Option | Description | Default | | --------------- | -------------------------------- | -------- | | `-v, --verbose` | Enable verbose logging to stderr | Disabled | Note The LSP server is intended to be started by a compatible editor, not invoked directly. It communicates over stdio. *** ## Exit Codes [Section titled “Exit Codes”](#exit-codes) | Code | Meaning | Action | | ---- | -------------------- | ------------------- | | 0 | Success, no findings | Proceed | | 1 | General error | Check error message | | 2 | Configuration error | Check config | | 4 | Network error | Check connectivity | | 5 | Findings detected | Review findings | | 6 | Invalid input | Check arguments | Tip Exit code 5 indicates findings were detected. This is not an error. Use it in CI to gate deployments. *** ## Environment Variables [Section titled “Environment Variables”](#environment-variables) | Variable | Description | | ------------------------------------- | ---------------------------------------- | | `OPENAI_API_KEY` | OpenAI API key for LLM features | | `ANTHROPIC_API_KEY` | Anthropic API key for LLM features | | `DD_API_KEY` / `DD_APP_KEY` | Datadog credentials for SLO enrichment | | `DT_API_TOKEN` / `DT_ENVIRONMENT_URL` | Dynatrace credentials for SLO enrichment | *** ## Configuration File [Section titled “Configuration File”](#configuration-file) The CLI stores configuration at: | Platform | Location | | ------------- | ------------------------------------------- | | Linux / macOS | `~/.config/unfault/config.json` | | Windows | `%USERPROFILE%\.config\unfault\config.json` | # Configuration > Configuration options for Unfault CLI and workspaces. Unfault can be configured at the user level and per-workspace. ## User Configuration [Section titled “User Configuration”](#user-configuration) The configuration file location depends on your operating system: | Platform | Location | | ----------- | ------------------------------------------------------------------------- | | **Linux** | `~/.config/unfault/config.json` or `$XDG_CONFIG_HOME/unfault/config.json` | | **macOS** | `~/.config/unfault/config.json` or `$XDG_CONFIG_HOME/unfault/config.json` | | **Windows** | `%USERPROFILE%\.config\unfault\config.json` | Note All platforms use the same `.config` directory pattern for consistency, rather than platform-specific locations like `~/Library/Application Support` (macOS) or `%APPDATA%` (Windows). Example configuration: ```json { "llm": { "provider": "openai", "model": "gpt-4o", "api_key": "sk-..." } } ``` ### LLM Configuration [Section titled “LLM Configuration”](#llm-configuration) To enable AI-powered review summaries, configure an LLM provider: * OpenAI ```bash unfault config llm openai --model gpt-5.1 ``` * Anthropic ```bash unfault config llm anthropic --model claude-4-5-sonnet-latest ``` * Ollama ```bash unfault config llm ollama --model llama3.2 ``` ## Workspace Configuration [Section titled “Workspace Configuration”](#workspace-configuration) Unfault reads configuration from your project’s manifest file, avoiding the need for a dedicated config file. The configuration location depends on your language: * Python (pyproject.toml) ```toml [tool.unfault] # Override auto-detected profile profile = "python_fastapi_backend" # Limit analysis to specific dimensions dimensions = ["stability", "correctness", "performance"] [tool.unfault.rules] # Rules to exclude (supports glob patterns) exclude = [ "python.missing_structured_logging", # We use custom logging "python.http.*", # All HTTP rules ] # Additional rules to include include = ["python.security.*"] # Severity overrides [tool.unfault.rules.severity] "python.bare_except" = "low" ``` * Rust (Cargo.toml) ```toml [package.metadata.unfault] profile = "rust_axum_service" dimensions = ["stability", "correctness"] [package.metadata.unfault.rules] exclude = ["rust.println_in_lib"] [package.metadata.unfault.rules.severity] "rust.unsafe_unwrap" = "critical" ``` * JavaScript/TypeScript (package.json) ```json { "name": "my-app", "unfault": { "profile": "typescript_express_backend", "dimensions": ["stability", "security"], "rules": { "exclude": ["typescript.console_in_production"], "include": ["typescript.security.*"], "severity": { "typescript.empty_catch": "critical" } } } } ``` * Standalone (.unfault.toml) ```toml # For Go projects, multi-language repos, or any project # where you prefer a dedicated config file # Override auto-detected profile (optional) profile = "go_gin_service" # Limit analysis to specific dimensions (optional) # Available: stability, correctness, performance, scalability, security, maintainability dimensions = ["stability", "performance"] [rules] # Rules to exclude - supports exact IDs and glob patterns exclude = [ "go.missing_structured_logging", "go.http.*", # All HTTP-related rules ] # Additional rules to include beyond profile defaults include = ["go.security.*"] # Severity overrides: low, medium, high, critical [rules.severity] "go.unchecked_error" = "critical" "go.defer_in_loop" = "low" ``` Tip Use `.unfault.toml` when your project doesn’t have a `pyproject.toml`, `Cargo.toml`, or `package.json`, or when you prefer to keep Unfault configuration separate from your package manifest. ### Configuration Priority [Section titled “Configuration Priority”](#configuration-priority) When multiple configuration sources exist: 1. **Manifest files are checked first** - `pyproject.toml`, `Cargo.toml`, then `package.json` 2. **`.unfault.toml` is the fallback** - Used when no manifest contains unfault configuration 3. **No merging** - Only one source is used per project (the first one found with unfault config) ### Rule Patterns [Section titled “Rule Patterns”](#rule-patterns) Patterns for `exclude` and `include` use glob syntax: | Pattern | Matches | Does Not Match | | ----------------------------- | ---------------------------------------------------------- | ----------------------------- | | `python.http.missing_timeout` | Exact match only | Any other rule | | `python.http.*` | `python.http.missing_timeout`, `python.http.missing_retry` | `python.http.client.timeout` | | `*.missing_timeout` | `python.missing_timeout`, `go.missing_timeout` | `python.http.missing_timeout` | | `python.**` | All rules starting with `python.` | Rules from other languages | ### Disabling Rules [Section titled “Disabling Rules”](#disabling-rules) To disable a rule project-wide, add it to the `exclude` list in your config. You can also disable rules inline in your code: * Go ```go // unfault:disable http_client_missing_timeout client := http.Client{} ``` * Python ```python # unfault:disable-next-line n_plus_one_query for user in users: orders = get_orders(user.id) ``` ## Environment Variables [Section titled “Environment Variables”](#environment-variables) ### LLM Providers [Section titled “LLM Providers”](#llm-providers) Used by the AI-powered review summary feature: | Variable | Description | | ------------------- | ---------------------------------------------------------------------- | | `OPENAI_API_KEY` | OpenAI API key. Required when using `unfault config llm openai`. | | `ANTHROPIC_API_KEY` | Anthropic API key. Required when using `unfault config llm anthropic`. | ### SLO Discovery (GCP) [Section titled “SLO Discovery (GCP)”](#slo-discovery-gcp) Used when running `unfault review` with GCP credentials configured: | Variable | Description | | -------------------------------- | ------------------------------------------------------------- | | `GOOGLE_APPLICATION_CREDENTIALS` | Path to service account JSON file. Takes precedence over ADC. | | `GOOGLE_CLOUD_PROJECT` | GCP project ID. Overrides auto-detection from credentials. | | `GCP_PROJECT` | Alternative to `GOOGLE_CLOUD_PROJECT`. | | `GCLOUD_PROJECT` | Alternative to `GOOGLE_CLOUD_PROJECT`. | Tip For local development, run `gcloud auth application-default login` instead of setting credentials manually. Unfault reads Application Default Credentials from `~/.config/gcloud/application_default_credentials.json`. ### SLO Discovery (Datadog) [Section titled “SLO Discovery (Datadog)”](#slo-discovery-datadog) Used when Datadog credentials are present: | Variable | Description | | ------------ | ------------------------------------------------------------------------ | | `DD_API_KEY` | Datadog API key. Required for Datadog integration. | | `DD_APP_KEY` | Datadog application key. Required for Datadog integration. | | `DD_SITE` | Datadog site (e.g., `datadoghq.eu` for EU). Defaults to `datadoghq.com`. | ### SLO Discovery (Dynatrace) [Section titled “SLO Discovery (Dynatrace)”](#slo-discovery-dynatrace) Used when Dynatrace credentials are present: | Variable | Description | | -------------------- | ------------------------------------------------------------------------ | | `DT_API_TOKEN` | Dynatrace API token with SLO read permissions. | | `DT_ENVIRONMENT_URL` | Dynatrace environment URL (e.g., `https://abc12345.live.dynatrace.com`). | ### Configuration Paths [Section titled “Configuration Paths”](#configuration-paths) | Variable | Description | | ----------------- | ----------------------------------------------------------------------- | | `XDG_CONFIG_HOME` | Override config directory. Defaults to `~/.config`. | | `HOME` | User home directory (Linux/macOS). Used to locate `~/.config/unfault/`. | | `USERPROFILE` | User home directory (Windows). Used to locate `.config\unfault\`. | ## Configuration Precedence [Section titled “Configuration Precedence”](#configuration-precedence) Settings are applied in this order (later overrides earlier): 1. Default values 2. User config (see [User Configuration](#user-configuration) for platform-specific paths) 3. Workspace config (`pyproject.toml`, `Cargo.toml`, `package.json`, or `.unfault.toml`) 4. Environment variables 5. Command-line flags # RAG Query Reference > Reference for the Unfault RAG query system. Caution This page describes functionality that is not yet available in the current open-source CLI release. RAG-based query capabilities (natural language questions over your codebase) are planned for a future release. This page will be updated when the feature ships. In the meantime, for exploring your codebase: * Use `unfault graph impact ` to understand blast radius * Use `unfault graph critical` to find hub files * Use `unfault graph library ` to find where a dependency is used * Use `unfault graph deps ` to see what a file depends on See the [CLI Reference](/docs/reference/cli/#graph) for the full graph command reference. # Rules Reference > Complete reference for all Unfault detection rules organized by language. Unfault analyzes your code across **195 production-readiness rules** in Python, Go, Rust, and TypeScript. Each rule targets patterns that cause real incidents in production systems. ## Rules by Language [Section titled “Rules by Language”](#rules-by-language) [Python Rules ](/docs/reference/rules/python/)60 rules covering stability, correctness, performance, and more. [Go Rules ](/docs/reference/rules/go/)56 rules for goroutine safety, error handling, and production patterns. [Rust Rules ](/docs/reference/rules/rust/)44 rules for memory safety, async correctness, and panic prevention. [TypeScript Rules ](/docs/reference/rules/typescript/)35 rules for promise handling, type safety, and Node.js patterns. ## Rules by Dimension [Section titled “Rules by Dimension”](#rules-by-dimension) Unfault organizes rules into seven dimensions that map to the qualities that keep systems running reliably: | Dimension | Focus | Example Rules | | ------------------- | ------------------------------------------ | ------------------------------------------------- | | **Stability** | Preventing crashes and service degradation | Timeouts, graceful shutdown, bounded retries | | **Correctness** | Preventing bugs and data corruption | SQL injection, error handling, type safety | | **Performance** | Preventing slowdowns | N+1 queries, blocking in async, CPU in event loop | | **Scalability** | Ensuring systems handle growth | Bounded concurrency, resource limits | | **Observability** | Improving monitoring and debugging | Structured logging, correlation IDs, tracing | | **Security** | Preventing vulnerabilities | Hardcoded secrets, unsafe eval, input validation | | **Maintainability** | Ensuring code quality | Halstead complexity, code duplication | ## Severity Levels [Section titled “Severity Levels”](#severity-levels) Each rule is assigned a severity based on its potential impact: * Critical - Security vulnerabilities or data corruption risks * High - Can cause outages or significant bugs * Medium - May cause issues under load or edge cases * Low - Best practices and code quality ## Auto-Fix Support [Section titled “Auto-Fix Support”](#auto-fix-support) Most Unfault rules include auto-fix patches. When Unfault detects a violation, it can generate a diff showing exactly how to fix the issue. Apply patches with: ```bash unfault review --fix ``` # Go Rules > All Unfault detection rules for Go code. Unfault includes **55 rules** for Go, covering core language patterns, goroutine safety, and popular frameworks like Gin, GORM, Echo, gRPC, and Redis. ## Core Rules (43 rules) [Section titled “Core Rules (43 rules)”](#core-rules-43-rules) | Rule | Dimension | Severity | | ------------------------------------------------------------------------------------ | --------------- | -------- | | [unchecked\_error](/docs/reference/rules/go/unchecked-error/) | Correctness | Medium | | [defer\_in\_loop](/docs/reference/rules/go/defer-in-loop/) | Performance | Medium | | [goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) | Stability | High | | [sql\_injection](/docs/reference/rules/go/sql-injection/) | Security | Critical | | [http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) | Stability | High | | [missing\_structured\_logging](/docs/reference/rules/go/missing-structured-logging/) | Observability | Low | | [unbounded\_goroutines](/docs/reference/rules/go/unbounded-goroutines/) | Scalability | High | | [race\_condition](/docs/reference/rules/go/race-condition/) | Correctness | High | | [hardcoded\_secrets](/docs/reference/rules/go/hardcoded-secrets/) | Security | Critical | | [type\_assertion\_no\_ok](/docs/reference/rules/go/type-assertion-no-ok/) | Stability | Medium | | [context\_background](/docs/reference/rules/go/context-background/) | Stability | Medium | | [halstead\_complexity](/docs/reference/rules/go/halstead-complexity/) | Maintainability | Low | | [bare\_recover](/docs/reference/rules/go/bare-recover/) | Stability | Medium | | [channel\_never\_closed](/docs/reference/rules/go/channel-never-closed/) | Stability | High | | [circuit\_breaker](/docs/reference/rules/go/circuit-breaker/) | Stability | Medium | | [concurrent\_map\_access](/docs/reference/rules/go/concurrent-map-access/) | Correctness | High | | [cpu\_in\_hot\_path](/docs/reference/rules/go/cpu-in-hot-path/) | Performance | Medium | | [empty\_critical\_section](/docs/reference/rules/go/empty-critical-section/) | Correctness | Medium | | [ephemeral\_filesystem\_write](/docs/reference/rules/go/ephemeral-filesystem-write/) | Stability | Medium | | [error\_type\_assertion](/docs/reference/rules/go/error-type-assertion/) | Correctness | Medium | | [global\_mutable\_state](/docs/reference/rules/go/global-mutable-state/) | Correctness | High | | [graceful\_shutdown](/docs/reference/rules/go/graceful-shutdown/) | Stability | High | | [http\_retry](/docs/reference/rules/go/http-retry/) | Stability | Medium | | [idempotency\_key](/docs/reference/rules/go/idempotency-key/) | Correctness | Medium | | [large\_response\_memory](/docs/reference/rules/go/large-response-memory/) | Scalability | High | | [map\_without\_size\_hint](/docs/reference/rules/go/map-without-size-hint/) | Performance | Low | | [missing\_correlation\_id](/docs/reference/rules/go/missing-correlation-id/) | Observability | Medium | | [missing\_tracing](/docs/reference/rules/go/missing-tracing/) | Observability | Low | | [panic\_in\_library](/docs/reference/rules/go/panic-in-library/) | Stability | High | | [rate\_limiting](/docs/reference/rules/go/rate-limiting/) | Scalability | Medium | | [reflect\_in\_hot\_path](/docs/reference/rules/go/reflect-in-hot-path/) | Performance | Medium | | [regex\_compile](/docs/reference/rules/go/regex-compile/) | Performance | Low | | [sentinel\_error\_comparison](/docs/reference/rules/go/sentinel-error-comparison/) | Correctness | Medium | | [slice\_append\_in\_loop](/docs/reference/rules/go/slice-append-in-loop/) | Performance | Medium | | [slice\_memory\_leak](/docs/reference/rules/go/slice-memory-leak/) | Stability | High | | [sync\_dns\_lookup](/docs/reference/rules/go/sync-dns-lookup/) | Performance | Medium | | [transaction\_boundary](/docs/reference/rules/go/transaction-boundary/) | Correctness | High | | [unbounded\_cache](/docs/reference/rules/go/unbounded-cache/) | Scalability | High | | [unbounded\_memory](/docs/reference/rules/go/unbounded-memory/) | Scalability | High | | [unbounded\_retry](/docs/reference/rules/go/unbounded-retry/) | Stability | High | | [uncancelled\_context](/docs/reference/rules/go/uncancelled-context/) | Stability | Medium | | [unhandled\_error\_goroutine](/docs/reference/rules/go/unhandled-error-goroutine/) | Stability | High | | [unsafe\_template](/docs/reference/rules/go/unsafe-template/) | Security | High | ## Framework Rules [Section titled “Framework Rules”](#framework-rules) ### Gin (2 rules) [Section titled “Gin (2 rules)”](#gin-2-rules) | Rule | Dimension | Severity | | ----------------------------------------------------------------------- | ----------- | -------- | | [missing\_validation](/docs/reference/rules/go/gin-missing-validation/) | Correctness | Medium | | [request\_validation](/docs/reference/rules/go/gin-request-validation/) | Correctness | High | ### Echo (2 rules) [Section titled “Echo (2 rules)”](#echo-2-rules) | Rule | Dimension | Severity | | ------------------------------------------------------------------------ | ----------- | -------- | | [request\_validation](/docs/reference/rules/go/echo-request-validation/) | Correctness | High | | [missing\_middleware](/docs/reference/rules/go/echo-missing-middleware/) | Stability | Medium | ### GORM (4 rules) [Section titled “GORM (4 rules)”](#gorm-4-rules) | Rule | Dimension | Severity | | ------------------------------------------------------------------------ | ----------- | -------- | | [n\_plus\_one](/docs/reference/rules/go/gorm-n-plus-one/) | Performance | High | | [session\_management](/docs/reference/rules/go/gorm-session-management/) | Stability | High | | [connection\_pool](/docs/reference/rules/go/gorm-connection-pool/) | Scalability | High | | [query\_timeout](/docs/reference/rules/go/gorm-query-timeout/) | Stability | High | ### gRPC (1 rule) [Section titled “gRPC (1 rule)”](#grpc-1-rule) | Rule | Dimension | Severity | | -------------------------------------------------------------------- | --------- | -------- | | [missing\_deadline](/docs/reference/rules/go/grpc-missing-deadline/) | Stability | High | ### Redis (2 rules) [Section titled “Redis (2 rules)”](#redis-2-rules) | Rule | Dimension | Severity | | ------------------------------------------------------------------- | ----------- | -------- | | [missing\_ttl](/docs/reference/rules/go/redis-missing-ttl/) | Scalability | High | | [connection\_pool](/docs/reference/rules/go/redis-connection-pool/) | Scalability | High | ### net/http (1 rule) [Section titled “net/http (1 rule)”](#nethttp-1-rule) | Rule | Dimension | Severity | | --------------------------------------------------------------------- | --------- | -------- | | [missing\_timeout](/docs/reference/rules/go/nethttp-missing-timeout/) | Stability | Critical | # go.bare_recover > Detects bare recover() calls that swallow all panics without logging. Correctness High Detects `recover()` calls that catch panics without logging or re-panicking, silently hiding errors. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Bare recover calls: * **Hide bugs** - Panics indicate serious problems that need attention * **Lose context** - No logs means no way to debug issues * **Mask failures** - Operations silently fail without notification * **Delay fixes** - Problems go unnoticed until they cascade ## Example [Section titled “Example”](#example) ```go // ❌ Before (swallows panic) func handler() { defer func() { recover() // Panic is silently ignored }() riskyOperation() } ``` ```go // ✅ After (logs and handles) func handler() { defer func() { if r := recover(); r != nil { log.Error("panic recovered", "error", r, "stack", string(debug.Stack())) // Optionally re-panic or return error } }() riskyOperation() } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `recover()` without checking return value * `recover()` without logging * Naked `defer recover()` patterns * Silent panic suppression ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates patches that add proper panic handling: ```go defer func() { if r := recover(); r != nil { // Log with stack trace log.Error("panic recovered", "error", r, "stack", string(debug.Stack())) // Convert to error if possible err = fmt.Errorf("panic: %v", r) } }() ``` Caution Recovering from panics should be rare. Most panics indicate bugs that should be fixed, not suppressed. Use recover only at API boundaries. ## When to Recover [Section titled “When to Recover”](#when-to-recover) ```go // HTTP handler - prevent one bad request from crashing server func panicMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { log.Error("HTTP handler panic", "path", r.URL.Path, "error", r) http.Error(w, "Internal Server Error", 500) } }() next.ServeHTTP(w, r) }) } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) * [go.panic\_in\_library](/docs/reference/rules/go/panic-in-library/) * [rust.panic\_in\_library](/docs/reference/rules/rust/panic-in-library/) # go.channel_never_closed > Detects channels that are created but never closed. Stability Medium Detects channels that are created but never closed, causing goroutines waiting on them to leak. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unclosed channels cause: * **Goroutine leaks** - Receivers block forever waiting * **Memory leaks** - Blocked goroutines hold references * **Deadlocks** - Range loops never terminate * **Resource exhaustion** - Slow accumulation of leaked goroutines ## Example [Section titled “Example”](#example) ```go // ❌ Before (channel never closed) func producer(ch chan int) { for i := 0; i < 10; i++ { ch <- i } // Channel never closed - consumers block forever! } func consumer(ch chan int) { for v := range ch { // Blocks forever after producer done process(v) } } ``` ```go // ✅ After (channel closed) func producer(ch chan int) { defer close(ch) for i := 0; i < 10; i++ { ch <- i } } func consumer(ch chan int) { for v := range ch { // Terminates when channel closed process(v) } } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `make(chan T)` without corresponding `close()` * Channels passed to functions without closing * Range loops over channels that are never closed ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds `defer close(ch)`: ```go func produce(ch chan<- int) { defer close(ch) // ... produce values } ``` Tip Only close channels from the sender side, never from the receiver. Closing a closed channel panics. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) * [go.unbounded\_goroutines](/docs/reference/rules/go/unbounded-goroutines/) # go.missing_circuit_breaker > Detects HTTP client calls without circuit breaker protection. Stability High Detects HTTP client calls to external services without circuit breaker protection, which can cause cascading failures. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without circuit breakers: * **Cascading failures** - One slow service brings down everything * **Resource exhaustion** - Goroutines pile up waiting for responses * **Extended outages** - Failing service never gets time to recover * **Poor user experience** - All requests slow down, not just affected ones ## Example [Section titled “Example”](#example) ```go // ❌ Before (no circuit breaker) func fetchData(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, err } return io.ReadAll(resp.Body) } ``` ```go // ✅ After (with circuit breaker) import "github.com/sony/gobreaker" var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "api", MaxRequests: 5, Interval: 10 * time.Second, Timeout: 30 * time.Second, ReadyToTrip: func(counts gobreaker.Counts) bool { return counts.ConsecutiveFailures > 5 }, }) func fetchData(url string) ([]byte, error) { result, err := cb.Execute(func() (interface{}, error) { resp, err := http.Get(url) if err != nil { return nil, err } return io.ReadAll(resp.Body) }) if err != nil { return nil, err } return result.([]byte), nil } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * HTTP calls without circuit breaker wrapper * gRPC client calls without breakers * External service integrations without failure isolation * Database connections without circuit protection ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates patches using `github.com/sony/gobreaker`: ```go import "github.com/sony/gobreaker" var circuitBreaker = gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "external-api", MaxRequests: 3, Timeout: 60 * time.Second, }) ``` Tip Circuit breakers have three states: Closed (normal), Open (failing fast), Half-Open (testing recovery). Configure thresholds based on your service’s SLOs. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) * [go.unbounded\_retry](/docs/reference/rules/go/unbounded-retry/) * [python.circuit\_breaker](/docs/reference/rules/python/circuit-breaker/) # go.concurrent_map_access > Detects concurrent map access without synchronization. Correctness Critical Detects concurrent access to maps without proper synchronization, which causes fatal runtime panics. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Concurrent map access causes: * **Fatal panics** - Concurrent map read/write is a fatal error in Go * **Service crashes** - No recovery possible from this panic * **Intermittent failures** - Only manifests under load * **Hard to reproduce** - Depends on goroutine timing ## Example [Section titled “Example”](#example) ```go // ❌ Before (concurrent access) var cache = make(map[string]interface{}) func handler(w http.ResponseWriter, r *http.Request) { key := r.URL.Query().Get("key") cache[key] = "value" // Fatal error if concurrent! } ``` ```go // ✅ After (sync.Map) var cache sync.Map func handler(w http.ResponseWriter, r *http.Request) { key := r.URL.Query().Get("key") cache.Store(key, "value") // Thread-safe } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Map writes in goroutines * Map access in HTTP handlers (implicitly concurrent) * Missing mutex protection on maps * Maps shared across goroutines ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates thread-safe alternatives: ```go // Option 1: sync.Map (best for many goroutines, infrequent writes) var cache sync.Map // Option 2: RWMutex (best for frequent reads, rare writes) type SafeMap struct { mu sync.RWMutex data map[string]interface{} } func (m *SafeMap) Get(key string) interface{} { m.mu.RLock() defer m.mu.RUnlock() return m.data[key] } func (m *SafeMap) Set(key string, val interface{}) { m.mu.Lock() defer m.mu.Unlock() m.data[key] = val } ``` Caution Use `go run -race` or `go test -race` to detect data races during development. The race detector catches issues that static analysis may miss. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.race\_condition](/docs/reference/rules/go/race-condition/) * [go.global\_mutable\_state](/docs/reference/rules/go/global-mutable-state/) # go.context_background > Detects inappropriate use of context.Background() where proper context should be passed. Stability Medium Detects inappropriate use of `context.Background()` where a proper context should be passed. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Context carries cancellation, deadlines, and request-scoped values: * **Lost cancellation** - Parent cancellation doesn’t propagate * **Leaked resources** - Operations continue after request ended * **No deadline propagation** - Request deadline ignored * **Missing tracing** - Request tracing lost Using `context.Background()` breaks the context chain that enables Go’s graceful shutdown and timeout patterns. ## Example [Section titled “Example”](#example) ```go // ❌ Before func (s *Service) GetUser(ctx context.Context, id string) (*User, error) { // ctx ignored, context.Background() used instead return s.db.QueryContext(context.Background(), "SELECT...", id) } ``` If the caller’s context is cancelled, the database query continues running. ```go // ✅ After func (s *Service) GetUser(ctx context.Context, id string) (*User, error) { return s.db.QueryContext(ctx, "SELECT...", id) } ``` Now cancellation and deadlines propagate correctly. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `context.Background()` when a context parameter is available * `context.TODO()` in production code (meant for development) * HTTP handlers not passing request context * gRPC handlers ignoring the context parameter ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault replaces `context.Background()` with the available context parameter. ## When Background Is Acceptable [Section titled “When Background Is Acceptable”](#when-background-is-acceptable) ```go // Main function initialization func main() { ctx := context.Background() server.Start(ctx) } // Long-running background jobs func StartBackgroundWorker() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() runWorker(ctx) } // Tests func TestSomething(t *testing.T) { ctx := context.Background() // ... } ``` ## Best Practices [Section titled “Best Practices”](#best-practices) ```go // HTTP handlers: use request context func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { user, err := h.service.GetUser(r.Context(), userID) } // gRPC handlers: context is first parameter func (s *server) GetUser(ctx context.Context, req *pb.Request) (*pb.Response, error) { return s.service.GetUser(ctx, req.Id) } // Chain context through all calls func (s *Service) GetUser(ctx context.Context, id string) (*User, error) { user, err := s.cache.Get(ctx, id) if err != nil { user, err = s.db.Query(ctx, id) } return user, err } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) * [go.http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) # go.cpu_in_hot_path > Detects CPU-intensive operations in hot code paths. Performance Medium Detects CPU-intensive operations (reflection, regex, JSON marshaling) in frequently-called code paths. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) CPU work in hot paths causes: * **Increased latency** - P99 latency spikes * **Reduced throughput** - Less requests per second * **Higher costs** - Need more instances to handle load * **Poor scaling** - Performance degrades under load ## Example [Section titled “Example”](#example) ```go // ❌ Before (expensive in loop) func processItems(items []Item) { for _, item := range items { json.Marshal(item) // Reflection on every iteration reflect.TypeOf(item) // Even more expensive } } ``` ```go // ✅ After (optimized) func processItems(items []Item) { // Batch marshal if needed data, _ := json.Marshal(items) // Or pre-compute type information itemType := reflect.TypeOf(Item{}) for _, item := range items { // Use cached type info } } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `reflect` package usage in loops * JSON marshal/unmarshal in hot paths * Regex operations in loops (use pre-compiled patterns) * Hash computations in tight loops ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault suggests moving expensive operations out of loops or pre-computing values. Tip Use code generation tools like `easyjson` or `ffjson` to avoid runtime reflection for JSON marshaling in performance-critical paths. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.regex\_compile](/docs/reference/rules/go/regex-compile/) * [go.reflect\_in\_hot\_path](/docs/reference/rules/go/reflect-in-hot-path/) * [python.cpu\_in\_event\_loop](/docs/reference/rules/python/cpu-in-event-loop/) # go.defer_in_loop > Detects defer statements inside loops. Performance Medium Detects `defer` statements inside loops. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Deferred calls accumulate until the function returns, not until the loop iteration ends: * **Resource exhaustion** - File handles accumulate until function exits * **Memory leak** - Deferred closures hold references * **Crash under load** - Works fine with 10 items, crashes with 10,000 * **Counter-intuitive** - Common misconception even among experienced Go devs This is one of Go’s most common gotchas. ## Example [Section titled “Example”](#example) ```go // ❌ Before for _, file := range files { f, err := os.Open(file) if err != nil { return err } defer f.Close() // Doesn't close until function returns! // Process file... } ``` If you have 10,000 files, you open 10,000 file handles before closing any. ```go // ✅ After (closure) for _, file := range files { func() { f, err := os.Open(file) if err != nil { return } defer f.Close() // Process file... }() } ``` ```go // ✅ After (explicit close) for _, file := range files { f, err := os.Open(file) if err != nil { return err } // Process file... f.Close() } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `defer` inside `for` loops * `defer` inside `range` loops * Nested loop defer patterns ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault wraps the loop body in an immediately-invoked function literal (IIFE). ## Best Practices [Section titled “Best Practices”](#best-practices) ```go // Extract to helper function func processFile(path string) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() // Process... return nil } for _, file := range files { if err := processFile(file); err != nil { return err } } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) # go.echo.missing_middleware > Detects Echo apps without essential middleware. Stability Medium Detects Echo apps without essential middleware. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing middleware: * **No error handling** - Unhandled panics crash server * **No request logging** - Can’t debug issues * **No request timeout** - Slow requests block server ## Example [Section titled “Example”](#example) ```go // ❌ Before (no middleware) e := echo.New() e.GET("/", handler) ``` ```go // ✅ After (with essential middleware) e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{ Timeout: 30 * time.Second, })) e.GET("/", handler) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Missing Recover middleware * Missing Logger middleware * Missing Timeout middleware ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.graceful\_shutdown](/docs/reference/rules/go/graceful-shutdown/) # go.echo.request_validation > Detects Echo handlers without request validation. Correctness Medium Detects Echo handlers without request validation. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing validation: * **Invalid data accepted** - Bad input reaches business logic * **Security vulnerabilities** - Unvalidated input exploitable * **Runtime errors** - Type mismatches cause panics ## Example [Section titled “Example”](#example) ```go // ❌ Before (no validation) func CreateUser(c echo.Context) error { var req UserRequest c.Bind(&req) // No validation! return createUser(req) } ``` ```go // ✅ After (with validation) func CreateUser(c echo.Context) error { var req UserRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return createUser(req) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Bind() without Validate() * Missing validation on request body * No error handling on bind ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.gin.missing\_validation](/docs/reference/rules/go/gin-missing-validation/) # go.empty_critical_section > Detects mutex locks with empty or trivial critical sections. Performance Medium Detects mutex locks protecting empty or trivial critical sections, causing unnecessary contention. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Empty critical sections cause: * **Unnecessary contention** - Goroutines block for nothing * **Deadlock risk** - Complex lock patterns with no benefit * **Performance degradation** - Lock overhead without protection * **Code smell** - Often indicates incomplete implementations ## Example [Section titled “Example”](#example) ```go // ❌ Before (pointless locking) var mu sync.Mutex func process() { mu.Lock() defer mu.Unlock() // Nothing protected! } // Also problematic func update() { mu.Lock() mu.Unlock() // Immediate unlock doWork() // Work happens outside lock } ``` ```go // ✅ After (meaningful critical section) var mu sync.Mutex var counter int func process() { mu.Lock() defer mu.Unlock() counter++ // Actually protected } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `Lock()`/`Unlock()` with nothing between them * `defer mu.Unlock()` with no protected operations * Critical sections with only logging * Lock/unlock without shared state access ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault flags these for manual review since the fix depends on intent: ```go // Option 1: Remove unnecessary lock func process() { doWork() // If no shared state } // Option 2: Add protected operations func process() { mu.Lock() sharedData = newValue mu.Unlock() doWork() } ``` Tip If you’re using locks for synchronization (not data protection), consider using `sync.WaitGroup`, channels, or `sync.Cond` instead. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.race\_condition](/docs/reference/rules/go/race-condition/) * [go.concurrent\_map\_access](/docs/reference/rules/go/concurrent-map-access/) # go.ephemeral_filesystem_write > Detects filesystem writes that may be lost in containers. Stability Medium Detects filesystem writes to ephemeral locations in containerized environments. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Ephemeral writes: * **Lost on restart** - Container restarts lose local files * **Not shared** - Multiple instances don’t share files * **Lost on scale** - New pods don’t have the data ## Example [Section titled “Example”](#example) ```go // ❌ Before (ephemeral) func saveFile(data []byte) error { return os.WriteFile("/tmp/data.json", data, 0644) } ``` ```go // ✅ After (persistent storage) func saveFile(ctx context.Context, data []byte) error { // Use S3 or mounted volume _, err := s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String("my-bucket"), Key: aws.String("data.json"), Body: bytes.NewReader(data), }) return err } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `os.WriteFile()` to local paths * `os.Create()` for persistent data * `ioutil.WriteFile()` in containerized apps ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.ephemeral\_filesystem\_write](/docs/reference/rules/python/ephemeral-filesystem-write/) # go.error_type_assertion > Detects error type assertions without using errors.As(). Correctness Medium Detects error type assertions using direct type assertion instead of `errors.As()`. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Direct type assertions on errors: * **Miss wrapped errors** - Wrapped errors don’t match direct type checks * **Break error chains** - Go 1.13+ error wrapping is ignored * **Lose error context** - Miss errors wrapped with `fmt.Errorf("%w")` ## Example [Section titled “Example”](#example) ```go // ❌ Before (misses wrapped errors) if e, ok := err.(*MyError); ok { handleMyError(e) } ``` ```go // ✅ After (handles wrapped errors) var myErr *MyError if errors.As(err, &myErr) { handleMyError(myErr) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `err.(*Type)` type assertions * `switch err.(type)` without unwrapping * Missing `errors.As()` for custom error types ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault replaces type assertions with `errors.As()`: ```go var e *MyError if errors.As(err, &e) { // ... } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.sentinel\_error\_comparison](/docs/reference/rules/go/sentinel-error-comparison/) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) # go.gin.missing_validation > Detects Gin handlers that bind request data without validation tags. Correctness Medium Detects Gin handlers that bind request data without validation tags. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unvalidated input causes problems: * **Invalid data accepted** - Garbage in, garbage out * **Null pointer panics** - Missing required fields cause crashes * **Business logic errors** - Constraints violated downstream * **Security vulnerabilities** - Unexpected input exploits assumptions Gin has built-in validation through struct tags - use it. ## Example [Section titled “Example”](#example) ```go // ❌ Before type CreateUserRequest struct { Email string `json:"email"` Password string `json:"password"` Age int `json:"age"` } func CreateUser(c *gin.Context) { var req CreateUserRequest c.ShouldBindJSON(&req) // No validation // Empty email? Negative age? Anything goes. } ``` ```go // ✅ After type CreateUserRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=8"` Age int `json:"age" binding:"gte=0,lte=150"` } func CreateUser(c *gin.Context) { var req CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Email is valid, password has 8+ chars, age is reasonable } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Struct bindings without `binding:` tags * Missing `required` on essential fields * ShouldBind without error checking ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds common validation tags based on field names and types. ## Common Validation Tags [Section titled “Common Validation Tags”](#common-validation-tags) ```go type Request struct { // Required fields Name string `binding:"required"` // String constraints Email string `binding:"required,email"` URL string `binding:"url"` UUID string `binding:"uuid"` // Numeric constraints Age int `binding:"gte=0,lte=150"` Count int `binding:"min=1,max=100"` // Length constraints Password string `binding:"min=8,max=128"` Code string `binding:"len=6"` // Enums Status string `binding:"oneof=pending active done"` } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) # go.gin.request_validation > Detects Gin handlers without request validation. Correctness High Detects Gin handlers without request validation. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing request validation: * **Invalid data** - Malformed input causes errors * **Security risks** - Unvalidated input enables attacks * **Poor UX** - Users get cryptic errors ## Example [Section titled “Example”](#example) ```go // ❌ Before (no validation) func CreateUser(c *gin.Context) { var user User c.BindJSON(&user) // No error handling! // process user... } ``` ```go // ✅ After (with validation) type CreateUserRequest struct { Name string `json:"name" binding:"required,min=1,max=100"` Email string `json:"email" binding:"required,email"` } func CreateUser(c *gin.Context) { var req CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } // process validated request... } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * BindJSON without error handling * Missing struct validation tags * Handlers without request binding ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add proper ShouldBind patterns with error handling. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.echo.request\_validation](/docs/reference/rules/go/echo-request-validation/) # go.global_mutable_state > Detects global mutable variables that can cause race conditions. Correctness High Detects global mutable state that can cause race conditions in concurrent code. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Global mutable state causes: * **Race conditions** - Concurrent access without synchronization * **Testing difficulties** - Tests affect each other * **Hidden dependencies** - Functions have implicit state * **Unpredictable behavior** - State changes unexpectedly ## Example [Section titled “Example”](#example) ```go // ❌ Before (unsafe global state) var cache = make(map[string]interface{}) func Get(key string) interface{} { return cache[key] // Race condition! } func Set(key string, value interface{}) { cache[key] = value // Race condition! } ``` ```go // ✅ After (thread-safe) var cache sync.Map func Get(key string) (interface{}, bool) { return cache.Load(key) } func Set(key string, value interface{}) { cache.Store(key, value) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Package-level `var` with mutable types (maps, slices) * Global variables accessed from multiple goroutines * Missing mutex protection on shared state ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates thread-safe alternatives: ```go // Option 1: sync.Map for concurrent maps var cache sync.Map // Option 2: Mutex-protected struct type SafeCache struct { mu sync.RWMutex data map[string]interface{} } func (c *SafeCache) Get(key string) interface{} { c.mu.RLock() defer c.mu.RUnlock() return c.data[key] } ``` Tip Prefer dependency injection over global state. Pass state explicitly to make dependencies clear and testing easier. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.race\_condition](/docs/reference/rules/go/race-condition/) * [go.concurrent\_map\_access](/docs/reference/rules/go/concurrent-map-access/) * [python.global\_mutable\_state](/docs/reference/rules/python/global-mutable-state/) # go.gorm.connection_pool > Detects missing or misconfigured GORM connection pool settings. Scalability High Detects missing or misconfigured GORM connection pool settings. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing connection pool configuration: * **Connection exhaustion** - Too many connections overwhelm database * **Resource waste** - Idle connections consume memory * **Timeouts** - No connection reuse leads to slow queries ## Example [Section titled “Example”](#example) ```go // ❌ Before (no pool configuration) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) ``` ```go // ✅ After (with connection pool settings) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal(err) } sqlDB, err := db.DB() if err != nil { log.Fatal(err) } sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) sqlDB.SetConnMaxIdleTime(10 * time.Minute) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Missing SetMaxOpenConns * Missing SetMaxIdleConns * Missing SetConnMaxLifetime * Unreasonable pool size values ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add appropriate connection pool configuration based on common best practices. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.gorm.session\_management](/docs/reference/rules/go/gorm-session-management/) * [go.gorm.query\_timeout](/docs/reference/rules/go/gorm-query-timeout/) # go.gorm.n_plus_one > Detects N+1 query patterns in GORM code. Performance High Detects N+1 query patterns in GORM code. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) N+1 queries: * **Performance degradation** - Linear query growth per record * **Database overload** - Excessive roundtrips saturate connections * **Latency spikes** - Each query adds network overhead ## Example [Section titled “Example”](#example) ```go // ❌ Before (N+1 queries) var users []User db.Find(&users) for _, user := range users { var orders []Order db.Where("user_id = ?", user.ID).Find(&orders) // Query per user! user.Orders = orders } ``` ```go // ✅ After (eager loading with Preload) var users []User db.Preload("Orders").Find(&users) ``` ```go // ✅ Alternative (using Joins) var users []User db.Joins("Orders").Find(&users) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Queries inside loops accessing related data * Missing Preload for associations * Repeated queries with only ID varying ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can suggest Preload patterns for detected N+1 queries. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.gorm.query\_timeout](/docs/reference/rules/go/gorm-query-timeout/) * [go.transaction\_boundary](/docs/reference/rules/go/transaction-boundary/) # go.gorm.query_timeout > Detects GORM queries without timeout configuration. Stability High Detects GORM queries without timeout configuration. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Queries without timeouts: * **Resource blocking** - Slow queries hold connections indefinitely * **Cascading failures** - Database issues propagate to application * **Poor user experience** - Requests hang without feedback ## Example [Section titled “Example”](#example) ```go // ❌ Before (no query timeout) func GetUser(id uint) (*User, error) { var user User err := db.First(&user, id).Error return &user, err } ``` ```go // ✅ After (with context timeout) func GetUser(ctx context.Context, id uint) (*User, error) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() var user User err := db.WithContext(ctx).First(&user, id).Error return &user, err } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * GORM queries without context * Missing WithContext calls * Hardcoded long timeout values ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can wrap queries with context timeout patterns. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_timeout](/docs/reference/rules/go/http-timeout/) * [go.gorm.connection\_pool](/docs/reference/rules/go/gorm-connection-pool/) # go.gorm.session_management > Detects improper GORM session management patterns. Stability High Detects improper GORM session management patterns. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Improper session management: * **Connection leaks** - Sessions not closed properly * **Stale data** - Reusing cached session state * **Race conditions** - Sharing sessions across goroutines ## Example [Section titled “Example”](#example) ```go // ❌ Before (reusing global db instance unsafely) var db *gorm.DB func GetUser(id uint) User { var user User db.First(&user, id) // May have stale session state return user } ``` ```go // ✅ After (using fresh session) var db *gorm.DB func GetUser(id uint) User { var user User db.Session(&gorm.Session{}).First(&user, id) return user } // Or with context func GetUserWithContext(ctx context.Context, id uint) User { var user User db.WithContext(ctx).First(&user, id) return user } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Sharing DB instances across goroutines without session isolation * Missing WithContext in request handlers * Stale session reuse patterns ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.gorm.connection\_pool](/docs/reference/rules/go/gorm-connection-pool/) * [go.transaction\_boundary](/docs/reference/rules/go/transaction-boundary/) # go.goroutine_leak > Detects goroutines that may never terminate. Stability High Causes Production Outages Detects goroutines that may never terminate (blocking on channels without cancellation). ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Leaked goroutines accumulate over time: * **Memory growth** - Each goroutine uses \~2KB stack minimum * **Resource exhaustion** - Eventually crashes the application * **Hard to detect** - No obvious symptoms until it’s too late * **Slow degradation** - Performance degrades gradually A goroutine leak is like a memory leak, but worse because goroutines also hold references to other resources. ## Example [Section titled “Example”](#example) ```go // ❌ Before func startWorker() { go func() { for { msg := <-messages // Blocks forever if channel closes process(msg) } }() } ``` If `messages` is never closed and nothing sends, this goroutine lives forever. ```go // ✅ After func startWorker(ctx context.Context) { go func() { for { select { case msg := <-messages: process(msg) case <-ctx.Done(): return } } }() } ``` The goroutine exits when context is cancelled. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Goroutines blocking on channel receive without `select` * Goroutines without cancellation mechanism * Channel sends without corresponding receives * Forever loops without exit conditions ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add context-based cancellation to goroutine patterns when the blocking pattern is clearly identified. Note Unfault focuses on common patterns like channel receives without select and infinite loops. Complex concurrency patterns may require manual review. ## Common Leak Patterns [Section titled “Common Leak Patterns”](#common-leak-patterns) ```go // LEAK: Unbuffered channel with no receiver ch := make(chan int) go func() { ch <- 1 // Blocks forever }() // LEAK: Range over channel never closed go func() { for v := range ch { // Blocks forever at end process(v) } }() // LEAK: Select without done case go func() { select { case v := <-ch: process(v) } // Only processes once, then exits - but what if ch never sends? }() ``` ## Safe Patterns [Section titled “Safe Patterns”](#safe-patterns) ```go // Timeout select { case v := <-ch: process(v) case <-time.After(30 * time.Second): return // Give up after timeout } // Context cancellation select { case v := <-ch: process(v) case <-ctx.Done(): return // Cancelled by parent } // Buffered channels for fire-and-forget ch := make(chan int, 1) ch <- 1 // Doesn't block ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_goroutines](/docs/reference/rules/go/unbounded-goroutines/) * [go.context\_background](/docs/reference/rules/go/context-background/) # go.missing_graceful_shutdown > Detects HTTP servers without graceful shutdown handling. Stability High Detects HTTP servers that don’t handle SIGTERM for graceful shutdown, causing dropped requests during deployments. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without graceful shutdown: * **Dropped requests** - In-flight requests are terminated mid-processing * **Data loss** - Partial writes, uncommitted transactions * **Connection errors** - Clients receive connection reset errors * **Deployment failures** - Rolling updates cause user-visible errors ## Example [Section titled “Example”](#example) ```go // ❌ Before (no graceful shutdown) func main() { http.ListenAndServe(":8080", handler) } ``` ```go // ✅ After (graceful shutdown) func main() { server := &http.Server{ Addr: ":8080", Handler: handler, } // Start server in goroutine go func() { if err := server.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit // Graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } log.Println("Server exited gracefully") } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `http.ListenAndServe()` without signal handling * Missing `server.Shutdown()` calls * `log.Fatal()` in signal handlers (prevents cleanup) * Hardcoded exits without cleanup ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates patches that add signal handling and graceful shutdown: ```go import ( "context" "os" "os/signal" "syscall" ) // Graceful shutdown setup quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) ``` Tip Set Kubernetes `terminationGracePeriodSeconds` to match or exceed your server’s shutdown timeout to avoid SIGKILL during deployments. ## Kubernetes Configuration [Section titled “Kubernetes Configuration”](#kubernetes-configuration) ```yaml spec: terminationGracePeriodSeconds: 30 containers: - name: app lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 5"] ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) * [python.graceful\_shutdown](/docs/reference/rules/python/graceful-shutdown/) * [rust.tokio.missing\_graceful\_shutdown](/docs/reference/rules/rust/tokio-missing-graceful-shutdown/) # go.grpc.missing_deadline > Detects gRPC calls without deadline or timeout. Stability Critical Detects gRPC calls without deadline or timeout. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) gRPC calls without deadlines: * **Resource exhaustion** - Hanging calls consume connections * **Cascading failures** - Slow services block callers indefinitely * **No failure feedback** - Clients wait forever ## Example [Section titled “Example”](#example) ```go // ❌ Before (no deadline) resp, err := client.GetUser(context.Background(), req) ``` ```go // ✅ After (with deadline) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := client.GetUser(ctx, req) if err != nil { if status.Code(err) == codes.DeadlineExceeded { // Handle timeout } return nil, err } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * gRPC client calls with context.Background() * Missing context.WithTimeout or WithDeadline * Unreasonably long timeout values ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can wrap gRPC calls with appropriate timeout contexts. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_timeout](/docs/reference/rules/go/http-timeout/) * [go.context\_background](/docs/reference/rules/go/context-background/) # go.halstead_complexity > Analyzes Halstead complexity metrics to identify hard-to-maintain functions. Maintainability Low Analyzes Halstead complexity metrics to identify hard-to-maintain functions. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) High complexity functions have measurable consequences: * **More bugs** - Studies show defect density increases with complexity * **Slower development** - Takes longer to understand and modify * **Testing burden** - More paths require more test cases * **Review difficulty** - Code reviewers miss issues in complex code Halstead metrics provide objective measures that correlate with maintenance cost. ## Halstead Metrics [Section titled “Halstead Metrics”](#halstead-metrics) * **Difficulty (D)** - How hard to write or understand * **Effort (E)** - Mental effort required * **Volume (V)** - Information content * **Vocabulary (n)** - Unique operators and operands * **Length (N)** - Total operators and operands ## Example [Section titled “Example”](#example) ```go // ❌ Before (high complexity) func processOrder(order *Order, user *User, inv *Inventory, pay *Payment) error { if order.Status != "pending" { return errors.New("invalid status") } if !user.Verified { return errors.New("user not verified") } if user.Balance < order.Total { return errors.New("insufficient balance") } for _, item := range order.Items { stock, ok := inv.Stock[item.ID] if !ok || stock < item.Qty { return fmt.Errorf("item %s out of stock", item.ID) } inv.Stock[item.ID] -= item.Qty if inv.Stock[item.ID] < 10 { notifyRestock(item.ID) } } if err := pay.Charge(user.ID, order.Total); err != nil { for _, item := range order.Items { inv.Stock[item.ID] += item.Qty // Rollback } return err } order.Status = "complete" return nil } ``` ```go // ✅ After (decomposed) func processOrder(ctx *OrderContext) error { if err := validateOrder(ctx.Order, ctx.User); err != nil { return err } reserved, err := reserveInventory(ctx.Order, ctx.Inventory) if err != nil { return err } if err := processPayment(ctx.User, ctx.Order.Total, ctx.Payment); err != nil { releaseInventory(reserved, ctx.Inventory) return err } ctx.Order.Status = "complete" return nil } func validateOrder(order *Order, user *User) error { if order.Status != "pending" { return errors.New("invalid status") } if !user.Verified { return errors.New("user not verified") } if user.Balance < order.Total { return errors.New("insufficient balance") } return nil } ``` Each function has a single responsibility and lower complexity. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Functions exceeding difficulty threshold * High effort scores * Deeply nested control flow * Long parameter lists ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault flags high-complexity functions. Refactoring is manual but guided. ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.halstead\_complexity](/docs/reference/rules/python/halstead-complexity/) * [rust.halstead\_complexity](/docs/reference/rules/rust/halstead-complexity/) * [typescript.halstead\_complexity](/docs/reference/rules/typescript/halstead-complexity/) # go.hardcoded_secrets > Detects hardcoded API keys, passwords, and secrets in source code. Security Critical Common in Incidents Detects hardcoded API keys, passwords, and other secrets in source code. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Hardcoded secrets are a severe security risk: * **Version control exposure** - Secrets committed to git are visible to everyone with access * **Cannot rotate** - Changing a secret requires code changes and deployment * **Audit impossible** - No way to track secret access * **Breach amplification** - One compromised repo exposes all services Secrets in code get leaked through backups, logs, error messages, and repository access. ## Example [Section titled “Example”](#example) ```go // ❌ Before const ( APIKey = "sk_live_abc123xyz" Password = "supersecret" DatabaseURL = "postgres://user:pass@host/db" ) client := stripe.NewClient("sk_live_abc123xyz") ``` ```go // ✅ After import "os" var ( APIKey = os.Getenv("STRIPE_API_KEY") Password = os.Getenv("DB_PASSWORD") DatabaseURL = os.Getenv("DATABASE_URL") ) client := stripe.NewClient(os.Getenv("STRIPE_API_KEY")) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Variables named `password`, `secret`, `key`, `token`, etc. * Strings matching API key patterns (AWS, Stripe, GitHub, etc.) * Database connection strings with credentials * JWT secrets and signing keys ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can replace hardcoded values with `os.Getenv()` calls when the pattern is recognized. Note Unfault uses pattern matching for known API key formats and sensitive variable names. Test data and clearly marked example values may still be flagged for review. ## Best Practices [Section titled “Best Practices”](#best-practices) ```go // Use environment variables apiKey := os.Getenv("API_KEY") if apiKey == "" { log.Fatal("API_KEY environment variable required") } // Or use a secrets manager import "github.com/aws/aws-sdk-go/service/secretsmanager" secret, err := sm.GetSecretValue(&secretsmanager.GetSecretValueInput{ SecretId: aws.String("my-secret"), }) // Configuration libraries import "github.com/spf13/viper" viper.SetEnvPrefix("MYAPP") viper.AutomaticEnv() apiKey := viper.GetString("API_KEY") ``` ## Common Secret Patterns [Section titled “Common Secret Patterns”](#common-secret-patterns) | Pattern | Example | | -------------- | ---------------------------- | | AWS Access Key | `AKIA...` | | AWS Secret Key | 40-char base64 | | Stripe Key | `sk_live_...`, `pk_live_...` | | GitHub Token | `ghp_...`, `github_pat_...` | | JWT Secret | Long random strings | | Database URL | `postgres://user:pass@...` | ## Related Rules [Section titled “Related Rules”](#related-rules) * [rust.hardcoded\_secrets](/docs/reference/rules/rust/hardcoded-secrets/) * [typescript.hardcoded\_secrets](/docs/reference/rules/typescript/hardcoded-secrets/) # go.http_missing_timeout > Detects HTTP client usage without timeout configuration. Stability High Common in Incidents Detects HTTP client usage without explicit timeout configuration. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) HTTP requests without timeouts can hang indefinitely: * **Connection pool exhaustion** - Stuck requests hold connections * **Goroutine leaks** - Waiting goroutines accumulate * **Cascade failures** - One slow upstream brings down your service * **Unresponsive service** - All workers blocked waiting The default `http.Client` has no timeout. This is a dangerous default. ## Example [Section titled “Example”](#example) ```go // ❌ Before client := &http.Client{} resp, err := client.Get(url) // Also bad: using http.Get directly resp, err := http.Get(url) ``` If the server never responds, these calls wait forever. ```go // ✅ After client := &http.Client{ Timeout: 30 * time.Second, } resp, err := client.Get(url) ``` After 30 seconds, the request fails with a timeout error. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `&http.Client{}` without Timeout field * `http.Get()`, `http.Post()`, etc. (use default client) * Client with Transport but no timeout * Missing context deadline on requests ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add `Timeout: 30 * time.Second` to HTTP client initialization when configuring a default client. Note Unfault detects `&http.Client{}` without Timeout and direct usage of `http.Get()`. Clients using custom Transport configurations are flagged but may need manual timeout tuning. ## Best Practices [Section titled “Best Practices”](#best-practices) ```go // Overall request timeout client := &http.Client{ Timeout: 30 * time.Second, } // Fine-grained control with Transport client := &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 5 * time.Second, // Connection timeout KeepAlive: 30 * time.Second, }).DialContext, TLSHandshakeTimeout: 5 * time.Second, ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 90 * time.Second, }, } // Context-based timeout (per-request) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) resp, err := client.Do(req) ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.http.missing\_timeout](/docs/reference/rules/python/http-missing-timeout/) * [typescript.http\_missing\_timeout](/docs/reference/rules/typescript/http-missing-timeout/) * [go.grpc.missing\_deadline](/docs/reference/rules/go/grpc-missing-deadline/) # go.http_retry > Detects HTTP calls without retry logic for transient failures. Stability Medium Detects HTTP client calls without retry logic, which fail on transient network issues. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without retries: * **Transient failures become permanent** - Network blips cause request failures * **Poor user experience** - Users see errors for recoverable issues * **Reduced reliability** - 99.9% uptime requires handling temporary failures * **Cascading issues** - One failed request may abort entire workflows ## Example [Section titled “Example”](#example) ```go // ❌ Before (no retry) func fetchUser(id string) (*User, error) { resp, err := http.Get(fmt.Sprintf("/users/%s", id)) if err != nil { return nil, err // Fails on first transient error } // ... } ``` ```go // ✅ After (with retry) import "github.com/hashicorp/go-retryablehttp" var client = retryablehttp.NewClient() func fetchUser(id string) (*User, error) { resp, err := client.Get(fmt.Sprintf("/users/%s", id)) if err != nil { return nil, err } // ... } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * HTTP GET/POST without retry wrapper * Missing retry on 5xx responses * Missing retry on connection errors * Direct `http.Client` usage without retry policy ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates patches using `go-retryablehttp`: ```go import "github.com/hashicorp/go-retryablehttp" client := retryablehttp.NewClient() client.RetryMax = 3 client.RetryWaitMin = 1 * time.Second client.RetryWaitMax = 30 * time.Second ``` Caution Only retry idempotent operations (GET, PUT, DELETE). Retrying POST without idempotency keys can cause duplicate operations. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_retry](/docs/reference/rules/go/unbounded-retry/) * [go.circuit\_breaker](/docs/reference/rules/go/circuit-breaker/) * [go.http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) # go.idempotency_key > Detects POST/PUT endpoints lacking idempotency key handling. Correctness Medium Detects state-modifying HTTP endpoints without idempotency key handling. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without idempotency keys: * **Duplicate operations** - Retries create duplicate records * **Double charges** - Payment retries charge multiple times * **Data inconsistency** - Same request processed multiple times ## Example [Section titled “Example”](#example) ```go // ❌ Before (no idempotency) func createOrder(w http.ResponseWriter, r *http.Request) { order := parseOrder(r) db.Create(&order) // Duplicate on retry! } ``` ```go // ✅ After (with idempotency key) func createOrder(w http.ResponseWriter, r *http.Request) { idempotencyKey := r.Header.Get("Idempotency-Key") if idempotencyKey == "" { http.Error(w, "Idempotency-Key required", 400) return } // Check if already processed if result, ok := cache.Get(idempotencyKey); ok { json.NewEncoder(w).Encode(result) return } order := parseOrder(r) db.Create(&order) cache.Set(idempotencyKey, order, 24*time.Hour) json.NewEncoder(w).Encode(order) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * POST/PUT handlers without idempotency key checks * Payment processing without idempotency * Order creation without duplicate protection ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates idempotency middleware: ```go func IdempotencyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" || r.Method == "PUT" { key := r.Header.Get("Idempotency-Key") // Check/store key... } next.ServeHTTP(w, r) }) } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.idempotency\_key](/docs/reference/rules/python/idempotency-key/) # go.large_response_memory > Detects unbounded response body reads that can exhaust memory. Stability Medium Detects HTTP response body reads without size limits, which can exhaust memory. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unbounded response reads cause: * **OOM crashes** - Malicious or broken servers return huge responses * **DoS vulnerability** - Attackers control response size * **Resource exhaustion** - Memory spikes affect other requests ## Example [Section titled “Example”](#example) ```go // ❌ Before (unbounded) resp, _ := http.Get(url) body, _ := io.ReadAll(resp.Body) // Could be gigabytes! ``` ```go // ✅ After (bounded) resp, _ := http.Get(url) body, _ := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) // 10MB max ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `io.ReadAll(resp.Body)` without limits * `ioutil.ReadAll()` without Content-Length check * JSON decoding without size limits ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds `io.LimitReader`: ```go const maxResponseSize = 10 * 1024 * 1024 // 10MB body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_memory](/docs/reference/rules/go/unbounded-memory/) * [python.large\_response\_memory](/docs/reference/rules/python/large-response-memory/) # go.map_without_size_hint > Detects map initialization without size hint when size is known. Performance Low Detects map initialization without capacity hint when the expected size is known. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Maps without size hints: * **Repeated rehashing** - Map grows and rehashes as items added * **Memory copying** - Each grow copies all entries * **Fragmented memory** - Multiple allocations instead of one ## Example [Section titled “Example”](#example) ```go // ❌ Before (no hint) result := make(map[string]int) for _, item := range items { result[item.Key] = item.Value } ``` ```go // ✅ After (with hint) result := make(map[string]int, len(items)) for _, item := range items { result[item.Key] = item.Value } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `make(map[K]V)` in loops where size is known * Map initialization followed by loop filling it ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds size hints: ```go result := make(map[string]int, len(items)) ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.slice\_append\_in\_loop](/docs/reference/rules/go/slice-append-in-loop/) # go.missing_correlation_id > Detects HTTP handlers without correlation ID propagation. Observability Low Detects HTTP handlers that don’t propagate correlation IDs for distributed tracing. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without correlation IDs: * **Can’t trace requests** - Logs across services can’t be correlated * **Debugging nightmare** - Finding related events is manual work * **Slow incident response** - More time spent correlating logs ## Example [Section titled “Example”](#example) ```go // ❌ Before (no correlation ID) func handler(w http.ResponseWriter, r *http.Request) { log.Println("Processing request") } ``` ```go // ✅ After (with correlation ID) func handler(w http.ResponseWriter, r *http.Request) { correlationID := r.Header.Get("X-Correlation-ID") if correlationID == "" { correlationID = uuid.New().String() } ctx := context.WithValue(r.Context(), "correlation_id", correlationID) log.Printf("Processing request correlation_id=%s", correlationID) w.Header().Set("X-Correlation-ID", correlationID) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * HTTP handlers without correlation ID extraction * Log statements without correlation IDs * Outgoing requests without ID forwarding ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.missing\_structured\_logging](/docs/reference/rules/go/missing-structured-logging/) * [go.missing\_tracing](/docs/reference/rules/go/missing-tracing/) * [python.missing\_correlation\_id](/docs/reference/rules/python/missing-correlation-id/) # go.missing_structured_logging > Detects usage of fmt.Println or log.Print instead of structured logging. Observability Low Detects usage of `fmt.Println`/`log.Print` instead of structured logging (zerolog/zap/slog). ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unstructured logs are hard to work with: * **Not queryable** - Can’t search for specific fields * **Not aggregatable** - Can’t count or group events * **Not alertable** - Can’t set conditions on values * **Parsing hell** - Regex extraction is fragile Modern observability requires structured data with consistent fields. ## Example [Section titled “Example”](#example) ```go // ❌ Before fmt.Printf("User %s logged in\n", userID) log.Printf("Error: %v", err) ``` These are impossible to filter or aggregate in your logging system. ```go // ✅ After (zerolog) import "github.com/rs/zerolog/log" log.Info().Str("user_id", userID).Msg("user logged in") log.Error().Err(err).Msg("operation failed") ``` Now you can query `user_id:123` or count login events by user. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `fmt.Print`, `fmt.Printf`, `fmt.Println` * `log.Print`, `log.Printf`, `log.Println` * `log.Fatal`, `log.Panic` (also replace with structured) ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault converts print statements to zerolog calls. ## Recommended Libraries [Section titled “Recommended Libraries”](#recommended-libraries) ```go // zerolog - Fast, structured, zero allocation import "github.com/rs/zerolog/log" log.Info().Str("key", "value").Msg("message") // zap - Uber's high-performance logger import "go.uber.org/zap" logger, _ := zap.NewProduction() logger.Info("message", zap.String("key", "value")) // slog - Standard library (Go 1.21+) import "log/slog" slog.Info("message", "key", "value") ``` ## Configuration [Section titled “Configuration”](#configuration) ```go // zerolog with pretty console for dev zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) // Production JSON output log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.missing\_structured\_logging](/docs/reference/rules/python/missing-structured-logging/) * [typescript.console\_in\_production](/docs/reference/rules/typescript/console-in-production/) # go.missing_tracing > Detects code without distributed tracing instrumentation. Observability Low Detects code without distributed tracing instrumentation for observability. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without tracing: * **No visibility** - Can’t see request flow across services * **Slow debugging** - Can’t identify bottlenecks * **Missing metrics** - No latency data per operation ## Example [Section titled “Example”](#example) ```go // ❌ Before (no tracing) func handleRequest(ctx context.Context) error { data := fetchData(ctx) return processData(data) } ``` ```go // ✅ After (with OpenTelemetry) import "go.opentelemetry.io/otel" var tracer = otel.Tracer("my-service") func handleRequest(ctx context.Context) error { ctx, span := tracer.Start(ctx, "handleRequest") defer span.End() data := fetchData(ctx) return processData(ctx, data) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * HTTP handlers without trace spans * Database operations without spans * Service calls without trace propagation ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.missing\_structured\_logging](/docs/reference/rules/go/missing-structured-logging/) * [go.missing\_correlation\_id](/docs/reference/rules/go/missing-correlation-id/) * [python.missing\_tracing](/docs/reference/rules/python/missing-tracing/) # go.nethttp.missing_timeout > Detects net/http clients and servers without timeout configuration. Stability Critical Detects net/http clients and servers without timeout configuration. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing HTTP timeouts: * **Resource exhaustion** - Hanging connections consume resources * **Slowloris attacks** - Servers vulnerable to slow clients * **Cascading failures** - Slow dependencies block callers ## Example [Section titled “Example”](#example) ```go // ❌ Before (no timeout - client) client := &http.Client{} resp, err := client.Get("https://api.example.com/data") // ❌ Before (no timeout - server) server := &http.Server{ Addr: ":8080", } ``` ```go // ✅ After (with timeout - client) client := &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 5 * time.Second, }).DialContext, ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 90 * time.Second, }, } // ✅ After (with timeout - server) server := &http.Server{ Addr: ":8080", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * http.Client without Timeout * http.Server without ReadTimeout/WriteTimeout * http.Transport without timeouts * Use of http.DefaultClient ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add appropriate timeout configuration to HTTP clients and servers. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_timeout](/docs/reference/rules/go/http-timeout/) * [go.graceful\_shutdown](/docs/reference/rules/go/graceful-shutdown/) # go.panic_in_library > Detects panic() calls in library code that should return errors. Stability High Detects `panic()` calls in library code that should return errors instead. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Panics in libraries: * **Crash calling applications** - Library panic terminates the app * **Remove caller control** - Callers can’t handle errors gracefully * **Violate Go conventions** - Libraries should return errors * **Cause production outages** - One bad input crashes everything ## Example [Section titled “Example”](#example) ```go // ❌ Before (panic in library) func Parse(data []byte) Config { if len(data) == 0 { panic("empty data") // Crashes caller! } // ... } ``` ```go // ✅ After (returns error) func Parse(data []byte) (Config, error) { if len(data) == 0 { return Config{}, errors.New("empty data") } // ... } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `panic()` in exported functions * `panic()` in package without `main` * `panic()` for recoverable errors ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault converts panic to error return: ```go func Process(input string) (Result, error) { if input == "" { return Result{}, errors.New("empty input") } // ... } ``` Tip Panic is acceptable for truly unrecoverable situations (programmer errors, invariant violations) but not for user input or external data. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.bare\_recover](/docs/reference/rules/go/bare-recover/) * [rust.panic\_in\_library](/docs/reference/rules/rust/panic-in-library/) # go.race_condition > Detects potential race conditions from concurrent access to shared state. Correctness High Common in Incidents Detects potential race conditions from concurrent access to shared state without synchronization. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Race conditions cause some of the hardest bugs to find: * **Intermittent failures** - May only happen under specific timing * **Data corruption** - Concurrent writes produce garbage * **Security vulnerabilities** - TOCTOU attacks exploit race windows * **Impossible to reproduce** - Works in testing, fails in production Go’s race detector catches some at runtime, but static analysis catches them earlier. ## Example [Section titled “Example”](#example) ```go // ❌ Before var counter int func incrementUnsafe() { go func() { counter++ // Race: concurrent read-modify-write }() } ``` Two goroutines incrementing simultaneously might both read the same value and write the same result. ```go // ✅ After (mutex) var ( counter int mu sync.Mutex ) func incrementSafe() { go func() { mu.Lock() counter++ mu.Unlock() }() } // ✅ After (atomic) var counter int64 func incrementAtomic() { go func() { atomic.AddInt64(&counter, 1) }() } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Shared variables accessed in goroutines without mutex * Map access from multiple goroutines * Struct field access without synchronization * Closure capturing variables modified concurrently ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add mutex protection around shared variable access when the pattern allows safe transformation. Note Static race detection has inherent limitations. Unfault catches common patterns but recommends running `go test -race` for comprehensive coverage. Some advanced synchronization patterns may be flagged conservatively. ## Common Patterns [Section titled “Common Patterns”](#common-patterns) ```go // Atomic for simple counters var count int64 atomic.AddInt64(&count, 1) val := atomic.LoadInt64(&count) // Mutex for complex state type SafeCache struct { mu sync.RWMutex data map[string]string } func (c *SafeCache) Get(key string) string { c.mu.RLock() defer c.mu.RUnlock() return c.data[key] } func (c *SafeCache) Set(key, value string) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value } // sync.Map for simple concurrent maps var cache sync.Map cache.Store("key", "value") val, ok := cache.Load("key") ``` ## Testing for Races [Section titled “Testing for Races”](#testing-for-races) ```bash # Run tests with race detector go test -race ./... # Build with race detector go build -race ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.race\_condition](/docs/reference/rules/python/race-condition/) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) # go.rate_limiting > Detects API endpoints without rate limiting protection. Scalability Medium Detects HTTP endpoints without rate limiting, which can be abused or overwhelmed. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without rate limiting: * **DoS vulnerability** - Anyone can overwhelm your service * **Resource exhaustion** - Uncontrolled traffic consumes all capacity * **Unfair access** - One client can starve others * **Cost explosion** - Cloud costs spike with traffic bursts ## Example [Section titled “Example”](#example) ```go // ❌ Before (no rate limiting) func handler(w http.ResponseWriter, r *http.Request) { // Any client can call this unlimited times processRequest(w, r) } ``` ```go // ✅ After (with rate limiting) import "golang.org/x/time/rate" var limiter = rate.NewLimiter(100, 10) // 100 req/sec, burst 10 func handler(w http.ResponseWriter, r *http.Request) { if !limiter.Allow() { http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) return } processRequest(w, r) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * HTTP handlers without rate limit middleware * Missing per-client rate limiting * Expensive operations without throttling ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates rate limiting middleware: ```go import "golang.org/x/time/rate" // Per-client rate limiting type ClientLimiter struct { limiters map[string]*rate.Limiter mu sync.RWMutex rate rate.Limit burst int } func (cl *ClientLimiter) GetLimiter(clientID string) *rate.Limiter { cl.mu.RLock() limiter, exists := cl.limiters[clientID] cl.mu.RUnlock() if exists { return limiter } cl.mu.Lock() limiter = rate.NewLimiter(cl.rate, cl.burst) cl.limiters[clientID] = limiter cl.mu.Unlock() return limiter } ``` Tip Use Redis-based rate limiting for distributed systems where multiple instances share rate limit state. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_goroutines](/docs/reference/rules/go/unbounded-goroutines/) * [typescript.rate\_limiting](/docs/reference/rules/typescript/rate-limiting/) # go.redis.connection_pool > Detects missing or misconfigured Redis connection pool settings. Scalability High Detects missing or misconfigured Redis connection pool settings. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Missing connection pool configuration: * **Connection exhaustion** - Too many connections overwhelm Redis * **Resource waste** - Idle connections consume memory * **Timeouts** - No pool limits lead to starvation ## Example [Section titled “Example”](#example) ```go // ❌ Before (no pool configuration) client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) ``` ```go // ✅ After (with connection pool settings) client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", PoolSize: 100, MinIdleConns: 10, PoolTimeout: 30 * time.Second, MaxRetries: 3, }) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Missing PoolSize configuration * Missing MinIdleConns * Missing PoolTimeout * Unreasonable pool size values ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add appropriate connection pool configuration based on common best practices. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.redis.missing\_ttl](/docs/reference/rules/go/redis-missing-ttl/) * [go.unbounded\_cache](/docs/reference/rules/go/unbounded-cache/) # go.redis.missing_ttl > Detects Redis SET operations without TTL. Scalability High Detects Redis SET operations without TTL. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Redis keys without TTL: * **Memory growth** - Keys accumulate indefinitely * **OOM risk** - Redis runs out of memory * **Stale data** - Old values never expire ## Example [Section titled “Example”](#example) ```go // ❌ Before (no TTL) err := client.Set(ctx, "user:123", userData, 0).Err() ``` ```go // ✅ After (with TTL) err := client.Set(ctx, "user:123", userData, 24*time.Hour).Err() ``` ```go // ✅ Alternative (SetEX for explicit expiration) err := client.SetEx(ctx, "session:abc", sessionData, 30*time.Minute).Err() ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Set with duration 0 (no expiry) * Missing expiration on cache keys * SetNX without subsequent Expire ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add TTL parameters to Redis SET operations. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.redis.connection\_pool](/docs/reference/rules/go/redis-connection-pool/) * [go.unbounded\_cache](/docs/reference/rules/go/unbounded-cache/) # go.reflect_in_hot_path > Detects reflection usage in performance-critical code paths. Performance Medium Detects `reflect` package usage in frequently-called code paths. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Reflection in hot paths: * **10-100x slower** - Reflection bypasses compile-time optimizations * **More allocations** - Reflect creates temporary objects * **No inlining** - Defeats compiler optimizations ## Example [Section titled “Example”](#example) ```go // ❌ Before (reflection in loop) func processItems(items []interface{}) { for _, item := range items { v := reflect.ValueOf(item) // Reflection on every iteration } } ``` ```go // ✅ After (type assertion) func processItems(items []Item) { for _, item := range items { // Direct field access, no reflection process(item.Name) } } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `reflect.ValueOf()` in loops * `reflect.TypeOf()` in handlers * Reflection in hot code paths ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault suggests type assertions or interfaces: ```go // Use type switch instead of reflection switch v := item.(type) { case string: processString(v) case int: processInt(v) } ``` Tip If you need reflection, cache type information outside loops. Use code generation for serialization (e.g., `easyjson`). ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.cpu\_in\_hot\_path](/docs/reference/rules/go/cpu-in-hot-path/) * [go.regex\_compile](/docs/reference/rules/go/regex-compile/) # go.regex_compile > Detects regex patterns compiled inside loops or hot paths. Performance Medium Detects regex patterns compiled repeatedly inside loops or frequently-called functions. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Compiling regex in hot paths: * **Wastes CPU** - Regex compilation is expensive * **Increases latency** - Each request pays compilation cost * **Scales poorly** - Impact grows with traffic * **Triggers GC pressure** - Allocates memory repeatedly ## Example [Section titled “Example”](#example) ```go // ❌ Before (compiled every call) func validateEmail(email string) bool { pattern := regexp.MustCompile(`^[\w.-]+@[\w.-]+\.\w+$`) return pattern.MatchString(email) } ``` ```go // ✅ After (compiled once) var emailPattern = regexp.MustCompile(`^[\w.-]+@[\w.-]+\.\w+$`) func validateEmail(email string) bool { return emailPattern.MatchString(email) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `regexp.Compile()` inside functions * `regexp.MustCompile()` inside loops * Repeated pattern compilation in handlers ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault moves regex compilation to package level: ```go // Moved to package level var _pattern = regexp.MustCompile(`\d{4}-\d{2}-\d{2}`) ``` Tip Use `regexp.MustCompile()` at package level - it panics on invalid patterns, catching errors at startup rather than runtime. ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.regex\_compile](/docs/reference/rules/python/regex-compile/) * [rust.regex\_compile](/docs/reference/rules/rust/regex-compile/) # go.sentinel_error_comparison > Detects direct error string comparison instead of errors.Is(). Correctness Medium Detects error comparisons using `==` or string matching instead of `errors.Is()`. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Direct error comparison: * **Misses wrapped errors** - `fmt.Errorf("%w", err)` won’t match * **Breaks error chains** - Go 1.13+ error wrapping is ignored * **Fragile** - String comparisons break on message changes ## Example [Section titled “Example”](#example) ```go // ❌ Before (broken with wrapped errors) if err == sql.ErrNoRows { return nil } if err.Error() == "not found" { return nil } ``` ```go // ✅ After (handles wrapped errors) if errors.Is(err, sql.ErrNoRows) { return nil } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `err == sentinel` comparisons * `err.Error() == "..."` string comparisons * Missing `errors.Is()` usage ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault replaces direct comparison with `errors.Is()`: ```go if errors.Is(err, sql.ErrNoRows) { // ... } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) * [go.error\_type\_assertion](/docs/reference/rules/go/error-type-assertion/) # go.slice_append_in_loop > Detects inefficient slice append patterns in loops. Performance Low Detects slice appends in loops without pre-allocation, causing repeated reallocations. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Appending without pre-allocation: * **Multiple reallocations** - Slice grows exponentially, copying each time * **Memory churn** - Old backing arrays become garbage * **GC pressure** - More work for garbage collector ## Example [Section titled “Example”](#example) ```go // ❌ Before (grows slice repeatedly) var result []Item for _, id := range ids { result = append(result, fetchItem(id)) } ``` ```go // ✅ After (pre-allocated) result := make([]Item, 0, len(ids)) for _, id := range ids { result = append(result, fetchItem(id)) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `append()` in loops without `make([]T, 0, n)` initialization * Growing slices without capacity hint when size is known ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds slice pre-allocation: ```go result := make([]Item, 0, len(ids)) ``` Tip If the exact size is unknown, estimate a reasonable upper bound. Over-allocating slightly is better than multiple reallocations. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.slice\_memory\_leak](/docs/reference/rules/go/slice-memory-leak/) * [go.map\_without\_size\_hint](/docs/reference/rules/go/map-without-size-hint/) # go.slice_memory_leak > Detects slice operations that can cause memory leaks. Performance Medium Detects slice operations that retain references to underlying arrays, causing memory leaks. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Slice memory leaks cause: * **Hidden memory retention** - Small slice keeps large array alive * **Gradual memory growth** - Leaks accumulate over time * **Hard to diagnose** - Memory profiler shows slices, not retained arrays ## Example [Section titled “Example”](#example) ```go // ❌ Before (retains entire array) func getPrefix(data []byte) []byte { return data[:10] // Keeps entire backing array! } ``` ```go // ✅ After (copies to new slice) func getPrefix(data []byte) []byte { prefix := make([]byte, 10) copy(prefix, data[:10]) return prefix // Only 10 bytes retained } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Returning slice of large input * Storing sub-slices in long-lived structures * Appending without copying when source is discarded ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates copy patterns: ```go result := make([]T, len(subslice)) copy(result, subslice) ``` Tip Use `slices.Clone()` (Go 1.21+) for cleaner copy syntax: `result := slices.Clone(data[:10])` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.slice\_append\_in\_loop](/docs/reference/rules/go/slice-append-in-loop/) * [go.unbounded\_memory](/docs/reference/rules/go/unbounded-memory/) # go.sql_injection > Detects SQL queries built with string concatenation. Correctness Critical Common in Incidents Detects SQL queries built with string concatenation instead of parameterized queries. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) SQL injection is a critical security vulnerability: * **Data theft** - Attackers can read your entire database * **Data destruction** - DROP TABLE, DELETE, UPDATE at will * **Authentication bypass** - Log in as any user * **Privilege escalation** - Gain admin access One unparameterized query can compromise your entire system. ## Example [Section titled “Example”](#example) ```go // ❌ Before query := "SELECT * FROM users WHERE id = " + userID db.Query(query) // Also bad query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name) ``` If `userID` is `"1 OR 1=1"`, all users are returned. If `name` is `"'; DROP TABLE users; --"`, your data is gone. ```go // ✅ After db.Query("SELECT * FROM users WHERE id = ?", userID) // Or with named parameters (sqlx) db.NamedQuery("SELECT * FROM users WHERE name = :name", map[string]interface{}{"name": name}) ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * String concatenation (`+`) in SQL query strings * `fmt.Sprintf` with SQL keywords * `strings.Replace` on query templates * Variable interpolation in queries ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can convert concatenated queries to parameterized form when the query structure is unambiguous. Note Unfault avoids flagging known-safe patterns like constant queries and query builder methods. Dynamic table names may be flagged but require manual review. ## Database Driver Placeholders [Section titled “Database Driver Placeholders”](#database-driver-placeholders) ```go // PostgreSQL (lib/pq, pgx) db.Query("SELECT * FROM users WHERE id = $1", id) // MySQL db.Query("SELECT * FROM users WHERE id = ?", id) // SQLite db.Query("SELECT * FROM users WHERE id = ?", id) // Multiple parameters - PostgreSQL db.Query("SELECT * FROM users WHERE id = $1 AND status = $2", id, status) // Multiple parameters - MySQL db.Query("SELECT * FROM users WHERE id = ? AND status = ?", id, status) ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.sql\_injection](/docs/reference/rules/python/sql-injection/) * [rust.sql\_injection](/docs/reference/rules/rust/sql-injection/) * [typescript.sql\_injection](/docs/reference/rules/typescript/sql-injection/) # go.sync_dns_lookup > Detects synchronous DNS lookups that can block goroutines. Performance Medium Detects synchronous DNS lookups that can block for seconds during resolution failures. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Synchronous DNS: * **Blocks goroutines** - DNS can take seconds to timeout * **Exhausts workers** - Blocked goroutines can’t serve requests * **Cascades failures** - DNS issues affect all requests ## Example [Section titled “Example”](#example) ```go // ❌ Before (synchronous) func connect(host string) error { addrs, err := net.LookupHost(host) // Can block for seconds if err != nil { return err } // ... } ``` ```go // ✅ After (with timeout) func connect(ctx context.Context, host string) error { resolver := &net.Resolver{} addrs, err := resolver.LookupHost(ctx, host) // Respects context timeout if err != nil { return err } // ... } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `net.LookupHost()` without context * `net.LookupAddr()` without timeout * DNS lookups in critical paths ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds context-aware DNS resolution: ```go ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() resolver := &net.Resolver{} addrs, err := resolver.LookupHost(ctx, host) ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.http\_missing\_timeout](/docs/reference/rules/go/http-missing-timeout/) * [python.sync\_dns\_lookup](/docs/reference/rules/python/sync-dns-lookup/) # go.transaction_boundary > Detects database operations without proper transaction boundaries. Correctness High Detects database operations spanning multiple queries without transaction boundaries. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Without transactions: * **Partial updates** - Some changes commit, others fail * **Data inconsistency** - Concurrent reads see intermediate states * **Lost writes** - Overwrites happen between read and update * **Recovery issues** - Can’t rollback partial operations ## Example [Section titled “Example”](#example) ```go // ❌ Before (no transaction) func transfer(db *sql.DB, from, to int, amount float64) error { _, err := db.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from) if err != nil { return err } // If this fails, money is lost! _, err = db.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to) return err } ``` ```go // ✅ After (with transaction) func transfer(db *sql.DB, from, to int, amount float64) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from) if err != nil { return err } _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to) if err != nil { return err } return tx.Commit() } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Multiple `db.Exec()` calls without `db.Begin()` * Missing `tx.Commit()` or `tx.Rollback()` * Related writes without transaction wrapper ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault wraps operations in transactions: ```go tx, err := db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() // ... operations return tx.Commit() ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.sql\_injection](/docs/reference/rules/go/sql-injection/) * [python.transaction\_boundary](/docs/reference/rules/python/transaction-boundary/) # go.type_assertion_no_ok > Detects type assertions without the ok check. Stability Medium Detects type assertions without the `ok` check. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Type assertions without the ok check panic on failure: * **Runtime panics** - Application crashes on unexpected types * **No graceful handling** - No way to recover or provide fallback * **Hidden assumptions** - Type expectations not visible in code * **Testing blind spots** - Works until it receives unexpected data A single unchecked type assertion can bring down your entire service. ## Example [Section titled “Example”](#example) ```go // ❌ Before value := data.(string) // Panics if not string ``` If `data` is anything other than a string, this panics. ```go // ✅ After value, ok := data.(string) if !ok { return errors.New("expected string") } ``` Now you handle the error gracefully. ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Type assertions without the second `ok` value * Interface type assertions without checking * Type switches with fallthrough to assertion ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault converts single-value assertions to two-value form with error handling. ## Common Patterns [Section titled “Common Patterns”](#common-patterns) ```go // Two-value assertion (safe) str, ok := v.(string) if !ok { return fmt.Errorf("expected string, got %T", v) } // Type switch (safest for multiple types) switch v := data.(type) { case string: return processString(v) case int: return processInt(v) default: return fmt.Errorf("unexpected type: %T", v) } // Assertion with default value str, ok := v.(string) if !ok { str = "default" } ``` ## When Single-Value Is Safe [Section titled “When Single-Value Is Safe”](#when-single-value-is-safe) ```go // After a type check (still not recommended) if _, ok := v.(string); ok { str := v.(string) // Safe but redundant } // Better: use type switch if str, ok := v.(string); ok { // Use str directly } ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) * [rust.unsafe\_unwrap](/docs/reference/rules/rust/unsafe-unwrap/) # go.unbounded_cache > Detects in-memory caches without size limits. Stability Medium Detects in-memory caches without size limits that can grow indefinitely. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unbounded caches cause: * **Memory exhaustion** - Cache grows until OOM * **GC pressure** - Large heaps slow garbage collection * **Unpredictable scaling** - Memory grows with traffic * **Silent degradation** - No errors until crash ## Example [Section titled “Example”](#example) ```go // ❌ Before (unbounded) var cache = make(map[string]interface{}) func Get(key string) interface{} { return cache[key] } func Set(key string, val interface{}) { cache[key] = val // Grows forever! } ``` ```go // ✅ After (bounded LRU) import "github.com/hashicorp/golang-lru/v2" var cache, _ = lru.New[string, interface{}](10000) func Get(key string) (interface{}, bool) { return cache.Get(key) } func Set(key string, val interface{}) { cache.Add(key, val) // Evicts oldest when full } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Maps used as caches without eviction * `sync.Map` without size limits * Growing collections without bounds ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault suggests LRU cache libraries: ```go import "github.com/hashicorp/golang-lru/v2" // With TTL import "github.com/hashicorp/golang-lru/v2/expirable" cache := expirable.NewLRU[string, interface{}]( 10000, // max entries nil, // on evict callback 5 * time.Minute, // TTL ) ``` Tip For distributed systems, consider Redis or Memcached instead of in-memory caches to share state across instances. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_memory](/docs/reference/rules/go/unbounded-memory/) * [python.unbounded\_cache](/docs/reference/rules/python/unbounded-cache/) # go.unbounded_goroutines > Detects goroutine spawning without bounds on concurrency. Scalability High Causes Production Outages Detects `go func()` calls without bounds on concurrent goroutines. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unbounded goroutine spawning exhausts resources: * **Memory exhaustion** - Each goroutine uses \~2KB+ stack * **CPU thrashing** - Too many goroutines competing for CPU * **Downstream collapse** - Thousands of requests hit your database simultaneously * **OOM crash** - Eventually the process is killed Input-controlled fan-out is especially dangerous-attackers can DoS your service. ## Example [Section titled “Example”](#example) ```go // ❌ Before for _, item := range items { go process(item) // Spawns len(items) goroutines } ``` If `items` has 100,000 elements, you spawn 100,000 goroutines simultaneously. ```go // ✅ After sem := make(chan struct{}, 100) // Limit to 100 concurrent for _, item := range items { sem <- struct{}{} // Acquire go func(item Item) { defer func() { <-sem }() // Release process(item) }(item) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `go func()` inside loops without semaphore * Goroutine spawning driven by external input * Missing worker pool patterns ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can wrap loop-spawned goroutines with semaphore-based limiting when the transformation is straightforward. Note Unfault detects goroutines spawned in loops without visible bounds. If you’re using errgroup.SetLimit() or a worker pool pattern elsewhere, Unfault may still flag the loop for review. ## Best Practices [Section titled “Best Practices”](#best-practices) ```go // Worker pool pattern func processItems(items []Item, workers int) { ch := make(chan Item, len(items)) var wg sync.WaitGroup // Fixed number of workers for i := 0; i < workers; i++ { wg.Add(1) go func() { defer wg.Done() for item := range ch { process(item) } }() } // Feed items to workers for _, item := range items { ch <- item } close(ch) wg.Wait() } // errgroup with limit import "golang.org/x/sync/errgroup" g, ctx := errgroup.WithContext(ctx) g.SetLimit(100) for _, item := range items { item := item g.Go(func() error { return process(ctx, item) }) } return g.Wait() ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [python.unbounded\_concurrency](/docs/reference/rules/python/unbounded-concurrency/) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) # go.unbounded_memory > Detects operations that can consume unbounded memory. Stability High Detects operations that can consume unbounded memory, leading to OOM crashes. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unbounded memory operations cause: * **OOM crashes** - Process killed by kernel * **Pod evictions** - Kubernetes kills memory-heavy pods * **Performance degradation** - GC pressure increases * **Cascading failures** - Memory pressure affects other services ## Example [Section titled “Example”](#example) ```go // ❌ Before (unbounded) func readAll(r io.Reader) ([]byte, error) { return io.ReadAll(r) // Could be gigabytes! } func collectAll(items <-chan Item) []Item { var result []Item for item := range items { result = append(result, item) // Unbounded growth } return result } ``` ```go // ✅ After (bounded) func readLimited(r io.Reader, maxSize int64) ([]byte, error) { return io.ReadAll(io.LimitReader(r, maxSize)) } func collectBounded(items <-chan Item, maxItems int) []Item { result := make([]Item, 0, min(maxItems, 1000)) for item := range items { result = append(result, item) if len(result) >= maxItems { break } } return result } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `io.ReadAll()` without size limits * Unbounded slice appends in loops * Growing maps without limits * Collecting all results without pagination ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds size limits: ```go // Limited reader io.LimitReader(r, 10*1024*1024) // 10MB max ``` Tip Set memory limits in Kubernetes with `resources.limits.memory` to prevent unbounded growth from affecting other pods. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unbounded\_cache](/docs/reference/rules/go/unbounded-cache/) * [go.large\_response\_memory](/docs/reference/rules/go/large-response-memory/) * [python.unbounded\_memory](/docs/reference/rules/python/unbounded-memory/) # go.unbounded_retry > Detects retry loops without proper bounds or backoff. Stability High Detects retry patterns that don’t have proper bounds, which can cause infinite loops on permanent failures. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unbounded retries cause: * **Infinite loops** - Permanent failures never stop retrying * **Resource exhaustion** - CPU and connections consumed by retries * **DoS on dependencies** - Overwhelming already-struggling services * **Extended outages** - Retries prevent recovery ## Example [Section titled “Example”](#example) ```go // ❌ Before (unbounded) func fetchData() error { for { if err := callAPI(); err == nil { return nil } time.Sleep(time.Second) } } ``` ```go // ✅ After (bounded with backoff) func fetchData() error { maxRetries := 5 backoff := time.Second for i := 0; i < maxRetries; i++ { if err := callAPI(); err == nil { return nil } time.Sleep(backoff) backoff *= 2 // Exponential backoff if backoff > time.Minute { backoff = time.Minute } } return errors.New("max retries exceeded") } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `for {}` loops with retry patterns * Missing max retry count * Missing backoff delays * Retries without jitter ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault generates patches using retry libraries: ```go import "github.com/cenkalti/backoff/v4" func fetchData() error { operation := func() error { return callAPI() } b := backoff.NewExponentialBackOff() b.MaxElapsedTime = 2 * time.Minute b.MaxInterval = 30 * time.Second return backoff.Retry(operation, b) } ``` Tip Add jitter to backoff to prevent thundering herd when multiple clients retry simultaneously. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.circuit\_breaker](/docs/reference/rules/go/circuit-breaker/) * [go.http\_retry](/docs/reference/rules/go/http-retry/) * [python.unbounded\_retry](/docs/reference/rules/python/unbounded-retry/) # go.uncancelled_context > Detects context.WithCancel/WithTimeout without cancellation. Stability Medium Detects `context.WithCancel()` and `context.WithTimeout()` without calling the cancel function, leaking resources. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Uncancelled contexts cause: * **Memory leaks** - Context goroutines never terminate * **Resource exhaustion** - Over time, leaks accumulate * **Timer leaks** - WithTimeout/WithDeadline leak timers * **Goroutine leaks** - Background goroutines never stop ## Example [Section titled “Example”](#example) ```go // ❌ Before (cancel never called) func process() { ctx, _ := context.WithTimeout(context.Background(), time.Second) doWork(ctx) // Timer goroutine leaked! } ``` ```go // ✅ After (cancel called) func process() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // Always call cancel! doWork(ctx) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * `context.WithCancel()` with unused cancel func * `context.WithTimeout()` without defer cancel * `context.WithDeadline()` without cleanup * Ignored cancel functions (`_`) ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds `defer cancel()`: ```go ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() ``` Tip Always call cancel even if the operation completes successfully. It releases resources immediately rather than waiting for timeout. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.context\_background](/docs/reference/rules/go/context-background/) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) # go.unchecked_error > Detects Go code that ignores error return values. Correctness Medium Detects Go code that ignores error return values. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Go’s error handling philosophy requires explicit handling: * **Silent failures** - Operations fail but code continues as if successful * **Data corruption** - Writes fail, reads return garbage, code proceeds * **Debugging nightmare** - Errors surface far from their origin * **Unexpected panics** - Nil results used without checking Go makes errors explicit for a reason. Ignoring them defeats the language’s safety design. ## Example [Section titled “Example”](#example) ```go // ❌ Before os.ReadFile("config.json") // Error ignored json.Unmarshal(data, &config) // Error ignored ``` If the file doesn’t exist or JSON is invalid, you’ll get mysterious failures later. ```go // ✅ After data, err := os.ReadFile("config.json") if err != nil { return fmt.Errorf("reading config: %w", err) } if err := json.Unmarshal(data, &config); err != nil { return fmt.Errorf("parsing config: %w", err) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Function calls with error return values that are discarded * `_` used to explicitly ignore errors * Error variables declared but never checked ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault adds error handling with appropriate error wrapping. ## Common Patterns [Section titled “Common Patterns”](#common-patterns) ```go // Return early on error data, err := fetch() if err != nil { return nil, err } // Wrap with context if err := save(data); err != nil { return fmt.Errorf("saving data: %w", err) } // Log and continue (when recovery is possible) if err := sendMetric(m); err != nil { log.Warn().Err(err).Msg("failed to send metric") // Continue - metrics are not critical } // Explicit ignore (rare, document why) _ = conn.Close() // Best effort, already handling error ``` ## Related Rules [Section titled “Related Rules”](#related-rules) * [rust.ignored\_result](/docs/reference/rules/rust/ignored-result/) * [typescript.empty\_catch](/docs/reference/rules/typescript/empty-catch/) # go.unhandled_error_goroutine > Detects errors not handled in goroutines. Stability High Detects errors not handled in goroutines. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unhandled errors in goroutines: * **Silent failures** - Errors are lost without logging * **Debugging difficulty** - No trace of what went wrong * **Inconsistent state** - Failed operations go unnoticed ## Example [Section titled “Example”](#example) ```go // ❌ Before (error silently ignored) go func() { result, err := processData(data) if err != nil { return // Error is lost! } // use result }() ``` ```go // ✅ After (error properly handled) go func() { result, err := processData(data) if err != nil { log.Printf("processData failed: %v", err) errorChan <- err return } resultChan <- result }() ``` ```go // ✅ Alternative (with error group) g, ctx := errgroup.WithContext(ctx) g.Go(func() error { return processData(ctx, data) }) if err := g.Wait(); err != nil { log.Printf("goroutine failed: %v", err) } ``` ## What Unfault Detects [Section titled “What Unfault Detects”](#what-unfault-detects) * Error returns ignored in goroutines * Missing error logging in goroutines * Goroutines swallowing errors silently ## Auto-Fix [Section titled “Auto-Fix”](#auto-fix) Unfault can add error logging or channel-based error propagation patterns. ## Related Rules [Section titled “Related Rules”](#related-rules) * [go.unchecked\_error](/docs/reference/rules/go/unchecked-error/) * [go.goroutine\_leak](/docs/reference/rules/go/goroutine-leak/) # go.unsafe_template > Detects unsafe HTML template usage that can lead to XSS vulnerabilities. Security Critical Detects unsafe HTML template usage that can lead to cross-site scripting (XSS) vulnerabilities. ## Why It Matters [Section titled “Why It Matters”](#why-it-matters) Unsafe templates enable: * **XSS attacks** - Attackers inject malicious scripts * **Session hijacking** - Stolen cookies and tokens * **Data theft** - Access to sensitive page content * **Malware distribution** - Redirects to malicious sites ## Example [Section titled “Example”](#example) ```go // ❌ Before (unsafe - uses text/template) import "text/template" func handler(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") t := template.New("page") t.Parse(`

Hello {{.}}

`) t.Execute(w, name) // XSS if name contains