# Ruff User Guide

Ruff is an extremely fast Python linter and code formatter, written in Rust. This
repository is a single Cargo workspace that ships **two separate command-line tools**:

| Tool | Binary | What it does |
|------|--------|--------------|
| **Ruff** | `ruff` | Lints and formats Python source code. A drop-in replacement for many tools (Flake8, isort, pyupgrade, pydocstyle, Black, and more) behind one fast binary. |
| **ty** | `ty` | An extremely fast Python type checker and language server. (Newer; ships from the same repository.) |

This guide is organized **by tool**. If you only want linting and formatting, read the
[Ruff](#part-1-ruff-the-linter-and-formatter) sections. If you want type checking, jump to
[ty](#part-2-ty-the-type-checker). Both tools share the same configuration philosophy
(TOML config files, gitignore-aware file discovery, `--config` overrides), so the concepts
transfer between them.

> [!NOTE]
> Ruff bundles the behavior of dozens of upstream Python tools. Rather than listing every
> one of its 800+ lint rules here, this guide shows you how to **discover, select, and
> configure** rules using Ruff's own commands (`ruff linter`, `ruff rule`, `ruff config`).
> Those commands always reflect the exact rules available in your installed version.

---

## Table of Contents

- [Part 1: Ruff — the linter and formatter](#part-1-ruff-the-linter-and-formatter)
  - [Quick Start](#quick-start)
  - [Installation](#installation)
  - [Linting your code (`ruff check`)](#linting-your-code-ruff-check)
  - [Fixing violations automatically](#fixing-violations-automatically)
  - [Formatting your code (`ruff format`)](#formatting-your-code-ruff-format)
  - [Selecting and ignoring rules](#selecting-and-ignoring-rules)
  - [Suppressing violations with `# noqa`](#suppressing-violations-with--noqa)
  - [Discovering rules and settings](#discovering-rules-and-settings)
  - [Configuration file](#configuration-file)
  - [Other `ruff` commands](#other-ruff-commands)
  - [Editor integration (`ruff server`)](#editor-integration-ruff-server)
- [Part 2: ty — the type checker](#part-2-ty-the-type-checker)
  - [Quick Start (ty)](#quick-start-ty)
  - [Checking a project (`ty check`)](#checking-a-project-ty-check)
  - [Pointing ty at your environment](#pointing-ty-at-your-environment)
  - [Selecting rule severities](#selecting-rule-severities)
  - [Configuring ty](#configuring-ty)
  - [Other `ty` commands](#other-ty-commands)
- [Common Workflows](#common-workflows)
- [Environment Variables](#environment-variables)
- [Exit Codes](#exit-codes)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)

---

# Part 1: Ruff — the linter and formatter

## Quick Start

```bash
# 1. Install Ruff (see Installation for more methods)
pip install ruff

# 2. Lint the current directory
ruff check

# 3. Lint and automatically fix what can be fixed safely
ruff check --fix

# 4. Format the current directory (Black-compatible style)
ruff format
```

A typical first run looks like this:

```text
example.py:1:8: F401 [*] `os` imported but unused
example.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 2 errors.
[*] 2 fixable with the `--fix` option.
```

Each diagnostic line is `path:line:column: CODE message`. A `[*]` marker means Ruff can
fix that violation for you.

> [!TIP]
> `ruff check` lints; `ruff format` formats. They are independent commands — running one
> never runs the other. A common project setup runs both (`ruff check --fix` then
> `ruff format`).

---

## Installation

Ruff is distributed as a self-contained binary with no Python dependency at runtime.

=== "pip"

    ```bash
    pip install ruff
    ```

=== "uv"

    ```bash
    # Add Ruff as a development dependency to your project
    uv add --dev ruff

    # Or run it once without installing
    uvx ruff check
    ```

=== "pipx"

    ```bash
    pipx install ruff
    ```

=== "From source (Cargo)"

    ```bash
    # Requires a Rust toolchain
    cargo install --git https://github.com/astral-sh/ruff ruff
    ```

Verify the installation:

```bash
ruff version
```

```text
ruff 0.15.15
```

> [!NOTE]
> The Python package (`pip install ruff`) is a thin wrapper that ships the compiled Rust
> binary for your platform — there is no Python interpreter involved when Ruff runs.

---

## Linting your code (`ruff check`)

`ruff check` is the linting command. Point it at files, directories, or `-` (stdin).
With no path, it defaults to the current directory (`.`).

```bash
# Lint the whole project
ruff check

# Lint specific paths
ruff check src/ tests/

# Lint a single file
ruff check app.py

# Lint code piped in on stdin
cat app.py | ruff check -
```

### Frequently used `ruff check` options

| Flag | Description |
|------|-------------|
| `--fix` | Apply fixes to resolve lint violations. Use `--no-fix` to disable. |
| `--unsafe-fixes` | Also apply fixes that may not preserve the original intent. Use `--no-unsafe-fixes` to disable. |
| `--fix-only` | Apply fixes but do not report on (or fail for) leftover violations. Implies `--fix`. |
| `--diff` | Don't write changes; print a diff of what `--fix` would do, and exit 0 if there are no diffs. |
| `--show-fixes` | List every fix that was applied. Use `--no-show-fixes` to disable. |
| `--watch`, `-w` | Re-run automatically whenever files change. |
| `--select <CODES>` | Comma-separated rule codes to enable (or `ALL`). |
| `--ignore <CODES>` | Comma-separated rule codes to disable. |
| `--extend-select <CODES>` | Enable extra codes on top of the configured selection. |
| `--per-file-ignores <PAIRS>` | Map file patterns to codes to ignore (e.g. `tests/*:S101`). |
| `--exclude <PATTERNS>` | Omit files/directories from analysis. |
| `--extend-exclude <PATTERNS>` | Add to the configured excludes. |
| `--target-version <VER>` | Minimum Python version to support (e.g. `py39`). |
| `--preview` | Enable unstable rules and fixes. Use `--no-preview` to disable. |
| `--output-format <FORMAT>` | Serialization format for violations (default: `full`). |
| `--output-file <PATH>`, `-o` | Write output to a file instead of stdout. |
| `--statistics` | Print a count for every rule that had at least one violation. |
| `--add-noqa` | Insert `# noqa` directives on every failing line instead of reporting. |
| `--ignore-noqa` | Ignore all existing `# noqa` comments. |
| `--no-cache`, `-n` | Disable cache reads. |
| `--cache-dir <PATH>` | Use a specific cache directory. |
| `--stdin-filename <PATH>` | The filename to assume when reading from stdin. |
| `--extension <MAP>` | Map a file extension to a language, e.g. `--extension ipy:ipynb`. |
| `--exit-zero` | Exit 0 even when violations are found. |
| `--exit-non-zero-on-fix` | Exit non-zero if any file was modified by `--fix`. |
| `--show-files` | Print the files Ruff would check with the current settings, then exit. |
| `--show-settings` | Print the resolved settings Ruff would use for a file, then exit. |

> [!TIP]
> Many boolean flags have a `--no-` counterpart (e.g. `--no-fix`, `--no-preview`,
> `--no-respect-gitignore`). This lets a command-line flag override a value set in your
> configuration file in either direction.

### Reading the output

```bash
ruff check src/
```

```text
src/example.py:1:8: F401 [*] `numpy` imported but unused
src/example.py:8:5: F841 [*] Local variable `x` is assigned to but never used
Found 2 errors.
[*] 2 fixable with the `--fix` option.
```

Use `--statistics` to get a per-rule summary instead of one line per violation:

```bash
ruff check --statistics
```

```text
12  F401  unused-import
 3  E501  line-too-long
```

### Choosing an output format

Ruff can serialize diagnostics in several formats via `--output-format` (or the
`RUFF_OUTPUT_FORMAT` environment variable). The default is `full`, which shows the
offending source line with the diagnostic. Other commonly used values include `concise`
(one line per diagnostic), `json`, and `junit`.

```bash
# Machine-readable JSON, e.g. for a CI dashboard
ruff check --output-format json

# Concise, one diagnostic per line
ruff check --output-format concise
```

> [!NOTE]
> To see every output format your installed version supports, run `ruff check --help`
> and look at the `--output-format` entry.

---

## Fixing violations automatically

Many rules ship an automatic fix. Ruff classifies fixes as **safe** or **unsafe**:

- **Safe fixes** preserve the meaning of your code and are applied by `--fix`.
- **Unsafe fixes** might change behavior or intent and require `--unsafe-fixes` to opt in.

```bash
# Apply safe fixes
ruff check --fix

# Also apply unsafe fixes
ruff check --fix --unsafe-fixes

# Preview what would change without writing files
ruff check --fix --diff

# Apply fixes and don't fail on remaining violations
ruff check --fix-only
```

> [!WARNING]
> `--fix` modifies files in place. Run it on a clean working tree (commit or stash first)
> so you can review the changes with `git diff`. Use `--diff` to preview without writing.

You can constrain which rules are eligible for fixing with `--fixable` and `--unfixable`:

```bash
# Only let Ruff fix import sorting, even though more rules are enabled
ruff check --fix --fixable I001
```

---

## Formatting your code (`ruff format`)

`ruff format` is Ruff's autoformatter. It produces a consistent, Black-compatible style.

```bash
# Format the whole project in place
ruff format

# Format specific paths
ruff format src/ tests/

# Format code from stdin
cat app.py | ruff format -
```

### `ruff format` options

| Flag | Description |
|------|-------------|
| `--check` | Don't write changes; exit non-zero if any file *would* be reformatted. Ideal for CI. |
| `--diff` | Don't write changes; print the diff between current and formatted output. |
| `--line-length <N>` | Set the maximum line length used for wrapping. |
| `--range <RANGE>` | Format only a line/column range, e.g. `--range=1:1-5:1` (single file only). |
| `--target-version <VER>` | Minimum Python version to target. |
| `--preview` | Enable unstable formatting. Use `--no-preview` to disable. |
| `--exclude <PATTERNS>` | Omit files/directories from formatting. |
| `--stdin-filename <PATH>` | Filename to assume when reading from stdin. |
| `--extension <MAP>` | Map a file extension to a language. |
| `--no-cache`, `-n` | Disable cache reads. |
| `--exit-non-zero-on-format` | Exit non-zero if any file was reformatted. |

For CI, the canonical "fail if not formatted" invocation is:

```bash
ruff format --check
```

```text
Would reformat: src/example.py
1 file would be reformatted, 4 files already formatted
```

### Range formatting

`--range` formats only part of a single file. The format is
`<start_line>:<start_column>-<end_line>:<end_column>`, with 1-based line and column numbers:

```bash
# Format lines 10 through 20
ruff format --range=10-20 app.py

# Format from line 2 to the end
ruff format --range=2 app.py

# Format the first three lines
ruff format --range=-3 app.py
```

> [!NOTE]
> Range formatting works on a single file at a time and is not supported for Jupyter
> notebooks.

---

## Selecting and ignoring rules

Ruff rules are identified by a short code: a **linter prefix** plus a number. The prefix
tells you which upstream tool the rule originates from. A few of the most common families:

| Prefix | Origin | Example |
|--------|--------|---------|
| `F` | Pyflakes (logical errors, unused imports/variables) | `F401` unused import |
| `E`, `W` | pycodestyle (style, PEP 8) | `E501` line too long |
| `I` | isort (import sorting) | `I001` unsorted imports |
| `N` | pep8-naming | `N802` function name should be lowercase |
| `UP` | pyupgrade (modern syntax) | `UP006` use `list` not `List` |
| `B` | flake8-bugbear (likely bugs) | `B008` function call in default arg |
| `C4` | flake8-comprehensions | `C401` unnecessary generator |
| `SIM` | flake8-simplify | `SIM108` use ternary |
| `PL` | Pylint | `PLR2004` magic value |
| `RUF` | Ruff-native rules | `RUF100` unused `noqa` |

> [!TIP]
> This is a small sample. Run `ruff linter` to list **every** linter family your version
> supports, and `ruff rule <CODE>` to read the full explanation of any rule.

Rules can be selected by full code (`F401`), by prefix (`F` selects all Pyflakes rules),
or with the special selector `ALL`.

```bash
# Enable only Pyflakes and isort
ruff check --select F,I

# Enable everything, then turn off a noisy category
ruff check --select ALL --ignore D

# Add a rule on top of your configured selection
ruff check --extend-select B
```

The selection flags interact as follows:

- `--select` **replaces** the active rule set.
- `--extend-select` **adds** to the configured set without replacing it.
- `--ignore` removes rules from the active set.
- `--per-file-ignores` ignores specific rules only in matching files.

```bash
# Allow assert statements (S101) only in the tests directory
ruff check --select ALL --per-file-ignores "tests/*:S101"
```

---

## Suppressing violations with `# noqa`

To silence a single line, add a `# noqa` comment. Scope it to specific codes so you don't
hide unrelated problems:

```python
import os  # noqa: F401
value = eval(expr)  # noqa: S307
```

- `# noqa` — suppress all violations on this line.
- `# noqa: F401, F841` — suppress only these codes on this line.

Useful related flags:

```bash
# Insert `# noqa` on every currently-failing line (great for adopting Ruff gradually)
ruff check --add-noqa

# Optionally add a reason after the codes
ruff check --add-noqa="legacy code, to be cleaned up"

# Ignore all existing noqa comments (see what's really there)
ruff check --ignore-noqa
```

> [!TIP]
> Enable the Ruff-native rule `RUF100` to flag `# noqa` comments that are no longer
> needed, so suppressions don't rot in your codebase.

---

## Discovering rules and settings

Ruff is self-documenting. Use these commands instead of guessing:

```bash
# List every linter family Ruff supports
ruff linter

# Explain a specific rule (cause, examples, fix availability)
ruff rule F401

# Explain every rule, as JSON
ruff rule --all --output-format json

# List all configuration options
ruff config

# Show the docs for a single config key
ruff config lint.isort.known-first-party
```

`--show-settings` resolves and prints the exact configuration Ruff would apply to a file —
indispensable when a rule isn't firing the way you expect:

```bash
ruff check --show-settings app.py
```

`--show-files` lists which files Ruff would actually analyze under the current settings:

```bash
ruff check --show-files
```

---

## Configuration file

Ruff reads its configuration from (in order of preference) a `ruff.toml` / `.ruff.toml`
file, or the `[tool.ruff]` table of a `pyproject.toml`. Settings are discovered by walking
up the directory tree from each file being checked, so sub-projects can have their own
configuration.

A representative `pyproject.toml`:

```toml
[tool.ruff]
# Top-level file/format settings
line-length = 88
indent-width = 4
target-version = "py39"
src = ["src", "tests"]
exclude = ["build", "dist", ".venv"]

[tool.ruff.lint]
# Rule selection
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]
# Per-file rule ignores
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]
"__init__.py" = ["F401"]

[tool.ruff.lint.isort]
known-first-party = ["my_package"]
lines-after-imports = 2

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true
```

> [!IMPORTANT]
> Rule selection (`select`, `ignore`, `per-file-ignores`, etc.) lives under
> `[tool.ruff.lint]`, **not** at the top level. Formatting options live under
> `[tool.ruff.format]`. The top-level `[tool.ruff]` table holds settings shared by both,
> such as `line-length`, `target-version`, `src`, and `exclude`.

### Most-used configuration keys

These are grounded in Ruff's configuration schema. Run `ruff config` for the complete,
version-accurate list.

**Top level — `[tool.ruff]`:**

| Key | Purpose |
|-----|---------|
| `line-length` | Maximum line length (also drives `E501` and the formatter). |
| `indent-width` | Number of spaces per indentation level. |
| `target-version` | Minimum supported Python version (`py37`…`py313`). |
| `src` | Directories treated as first-party source roots (affects import sorting). |
| `include` / `exclude` | File patterns to analyze / skip. |
| `extend-include` / `extend-exclude` | Add to the include/exclude sets. |
| `respect-gitignore` | Honor `.gitignore` and similar files (default: true). |
| `force-exclude` | Apply excludes even to paths passed explicitly on the CLI. |
| `fix` / `fix-only` / `unsafe-fixes` | Default fix behavior. |
| `output-format` | Default diagnostic format. |
| `preview` | Enable unstable rules/features. |
| `required-version` | Pin the Ruff version a project expects. |
| `extend` | Inherit settings from another config file. |
| `cache-dir` | Where to store the lint cache. |

**Linting — `[tool.ruff.lint]`:**

| Key | Purpose |
|-----|---------|
| `select` / `ignore` | Rules to enable / disable. |
| `extend-select` / `extend-fixable` / `extend-unfixable` | Additive rule selection. |
| `per-file-ignores` / `extend-per-file-ignores` | Per-pattern rule ignores. |
| `fixable` / `unfixable` | Restrict which rules may be auto-fixed. |
| `dummy-variable-rgx` | Regex matching intentionally-unused variable names. |
| `task-tags` | Tags recognized in comments (e.g. `TODO`, `FIXME`). |
| `typing-modules` | Modules treated as providing typing constructs. |
| `external` | Codes from other tools that Ruff should not flag as unknown in `noqa`. |
| `allowed-confusables` | Unicode confusable characters to permit. |

Each upstream plugin also has its own sub-table — for example `[tool.ruff.lint.isort]`,
`[tool.ruff.lint.pycodestyle]`, `[tool.ruff.lint.pydocstyle]`, `[tool.ruff.lint.mccabe]`,
`[tool.ruff.lint.pylint]`, `[tool.ruff.lint.flake8-bugbear]`, `[tool.ruff.lint.flake8-quotes]`,
and many more. Run `ruff config lint.<plugin>` to see the keys for each.

**Formatting — `[tool.ruff.format]`:**

| Key | Purpose |
|-----|---------|
| `quote-style` | Preferred string quotes (e.g. `double`, `single`). |
| `indent-style` | Indent with `space` or `tab`. |
| `line-ending` | Line ending style (e.g. `lf`, `crlf`, `auto`). |
| `skip-magic-trailing-comma` | Ignore the "magic" trailing comma when collapsing collections. |
| `docstring-code-format` | Format code examples inside docstrings. |
| `docstring-code-line-length` | Line length for code inside docstrings. |
| `preview` | Enable unstable formatting. |

### Overriding config on the command line

The global `--config` flag accepts either a path to a config file **or** an inline
`KEY = VALUE` TOML override. Inline overrides take precedence over every config file.

```bash
# Use a specific config file
ruff check --config path/to/ruff.toml

# Override a single setting inline
ruff check --config "lint.line-length = 100"
ruff check --config "format.quote-style = 'single'"

# Combine: a file plus targeted overrides
ruff check --config ruff.toml --config "extend-select = ['I']"
```

To ignore all configuration files entirely, use `--isolated`:

```bash
ruff check --isolated --select F
```

> [!CAUTION]
> `--isolated` cannot be combined with a `--config` *file* (they conflict), but it can be
> combined with inline `--config "KEY = VALUE"` overrides. Mixing `--isolated` with a
> config file path produces an error.

---

## Other `ruff` commands

| Command | Purpose |
|---------|---------|
| `ruff check [PATHS]` | Lint files or directories. |
| `ruff format [PATHS]` | Format files or directories. |
| `ruff rule <CODE>` / `ruff rule --all` | Explain a rule (or all rules). |
| `ruff linter` | List all supported upstream linters. |
| `ruff config [KEY]` | List or describe configuration options. |
| `ruff clean` | Clear Ruff's caches in the current directory and subdirectories. |
| `ruff analyze graph [PATHS]` | Generate a map of Python file dependencies (see below). |
| `ruff server` | Run the Ruff language server (for editors). |
| `ruff version` | Print Ruff's version. |
| `ruff help <command>` | Show help for any subcommand. |

### Import dependency graphs (`ruff analyze graph`)

`ruff analyze graph` produces a JSON map of which files import which:

```bash
# Map each file to the files it depends on
ruff analyze graph src/

# Map each file to the files that depend on it
ruff analyze graph --direction dependents src/

# Try to resolve imports written as string literals
ruff analyze graph --detect-string-imports src/
```

### Global options (available on every command)

| Flag | Description |
|------|-------------|
| `--config <FILE-or-KEY=VALUE>` | Config file path or inline TOML override. |
| `--isolated` | Ignore all configuration files. |
| `--color <WHEN>` | `auto`, `always`, or `never`. |
| `--verbose`, `-v` | Verbose logging. |
| `--quiet`, `-q` | Print diagnostics only. |
| `--silent`, `-s` | Suppress all logging (still exits non-zero on diagnostics). |

---

## Editor integration (`ruff server`)

Ruff includes a built-in language server, started with `ruff server`. Most editors invoke
it for you through an extension rather than you running it directly. It provides real-time
diagnostics, quick-fixes, import organization, and formatting using the same engine as the
CLI, so editor results match what `ruff check` and `ruff format` produce.

```bash
# Run the language server (normally launched by your editor)
ruff server

# Enable unstable server features
ruff server --preview
```

> [!TIP]
> Because the server shares Ruff's configuration resolution, the rules and formatting you
> see in your editor are exactly what CI will enforce — as long as both use the same
> `pyproject.toml`/`ruff.toml`.

---

# Part 2: ty — the type checker

`ty` is an extremely fast Python type checker that ships from the same repository as Ruff.
It analyzes your code for type errors and can also run as a language server.

> [!NOTE]
> `ty` is a separate binary from `ruff`. Installing one does not install the other unless
> your packaging method bundles both. Where `ruff check` looks for lint violations, `ty
> check` looks for type errors.

## Quick Start (ty)

```bash
# Check the current project for type errors
ty check

# Check specific paths
ty check src/

# Print ty's version
ty version
```

A successful run reports:

```text
All checks passed!
```

When ty finds problems, it summarizes them:

```text
Found 3 diagnostics
```

---

## Checking a project (`ty check`)

`ty check` analyzes a project for type errors. With no path, it checks the project root.
Project metadata (`pyproject.toml`) is discovered by walking up the directory tree.

```bash
# Check the whole project
ty check

# Check specific files or directories
ty check src/ app.py

# Run from a specific project directory
ty check --project ./backend
```

### Frequently used `ty check` options

| Flag | Description |
|------|-------------|
| `--project <PROJECT>` | Run within the given project directory. |
| `--python <PATH>` | Path to your project's Python environment or interpreter (alias: `--venv`). |
| `--python-version <VERSION>` | Python version to assume when resolving types (alias: `--target-version`). |
| `--python-platform <PLATFORM>` | Target platform for `sys.platform` specialization (alias: `--platform`). |
| `--extra-search-path <PATH>` | Additional module-resolution search path (repeatable). |
| `--typeshed <PATH>` | Custom directory for stdlib typeshed stubs (alias: `--custom-typeshed-dir`). |
| `--fix` | Apply fixes to resolve errors. |
| `--add-ignore` | Insert `ty: ignore` comments to suppress all rule diagnostics. |
| `--error <RULE>` / `--warn <RULE>` / `--ignore <RULE>` | Set a rule's severity (repeatable; `all` targets every rule). |
| `--config <KEY=VALUE>`, `-c` | Inline TOML configuration override. |
| `--config-file <PATH>` | Path to a `ty.toml` file. |
| `--output-format <FORMAT>` | Diagnostic format: `full`, `concise`, `gitlab`, `github`, `junit`. |
| `--error-on-warning` | Exit 1 if there are any warning-level diagnostics. |
| `--exit-zero` | Always exit 0, even when there are errors. |
| `--watch`, `-W` | Watch files and recheck on change. |
| `--exclude <GLOB>` | Glob patterns for files to exclude (gitignore-style). |
| `--respect-ignore-files` | Honor `.gitignore` etc. (use `--no-respect-ignore-files` to disable). |
| `--force-exclude` | Apply excludes even to paths passed directly on the CLI. |
| `--color <WHEN>` | `auto`, `always`, or `never`. |
| `--no-progress` | Hide spinners and progress bars. |
| `--verbose`, `-v` | Verbose output (`-vv`, `-vvv` for more). |
| `--quiet`, `-q` | Quiet output (`-qq` for silent). |

### Watch mode

```bash
ty check --watch
```

ty stays running, re-checking files related to whatever you change. Press `Ctrl+C` to stop.

---

## Pointing ty at your environment

ty resolves third-party imports using your project's Python environment. If you use a
project manager like uv, or have an activated virtual environment / Conda environment, ty
usually finds it automatically. When it can't, point it explicitly:

```bash
# Point at a virtual environment directory
ty check --python .venv

# Point at a specific interpreter
ty check --python .venv/bin/python3

# Point at a system Python prefix
ty check --python /usr
```

The Python version ty assumes affects which syntax is allowed and which standard-library
and conditional type definitions apply. ty determines it in this order:

1. `project.requires-python` in `pyproject.toml` (minimum of the range), then
2. the Python version inferred from your configured/activated environment, then
3. the latest stable Python version ty supports.

Override it directly when needed:

```bash
ty check --python-version 3.12
```

---

## Selecting rule severities

Unlike Ruff's enable/disable model, ty rules are tuned by **severity**. Use `--error`,
`--warn`, and `--ignore`, each repeatable, with later flags overriding earlier ones. Use
`all` to target every rule at once.

```bash
# Treat a specific rule as an error
ty check --error possibly-unresolved-reference

# Downgrade a rule to a warning
ty check --warn division-by-zero

# Silence a rule entirely
ty check --ignore unused-ignore-comment

# Make everything an error, then relax one rule
ty check --error all --warn redundant-cast
```

To suppress diagnostics inline in your source, use a `ty: ignore` comment, or let ty add
them for you across the project:

```bash
ty check --add-ignore
```

```text
Added 7 ignore comments
```

Explain any rule with `ty explain`:

```bash
ty explain rule division-by-zero
```

---

## Configuring ty

ty reads configuration from a `ty.toml` file, or from the `[tool.ty]` table of a
`pyproject.toml`. (When using `--config-file`, only a standalone `ty.toml` is accepted.)

```toml
[tool.ty.src]
exclude = ["./vendor", "**/__pycache__/**"]

[tool.ty.environment]
python-version = "3.12"

[tool.ty.rules]
possibly-unresolved-reference = "error"
division-by-zero = "error"
unused-ignore-comment = "error"
```

Override any setting inline with `-c` / `--config`:

```bash
ty check -c "environment.python-version = '3.13'"
ty check -c "rules.division-by-zero = 'warn'"
```

Set a config file path with `--config-file` or the `TY_CONFIG_FILE` environment variable:

```bash
ty check --config-file ./config/ty.toml
```

---

## Other `ty` commands

| Command | Purpose |
|---------|---------|
| `ty check [PATHS]` | Check a project for type errors. |
| `ty server` | Start the ty language server. |
| `ty explain rule <RULE>` | Explain a rule (or all rules if omitted). |
| `ty version` | Print ty's version. |

---

# Common Workflows

### Lint + format + type-check in one local pass

```bash
ruff check --fix      # fix lint violations
ruff format           # apply formatting
ty check              # check for type errors
```

### Continuous integration (no writes, fail on any issue)

```bash
ruff check --output-format github        # annotate violations in GitHub Actions
ruff format --check                       # fail if anything is unformatted
ty check --error-on-warning               # fail on type errors and warnings
```

> [!TIP]
> In CI, `ruff format --check` and `ruff check` (without `--fix`) never modify files —
> they only report and set the exit code. That makes them safe to run on pull requests.

### Adopting Ruff in an existing, messy codebase

```bash
# 1. Start strict, then suppress everything currently failing
ruff check --select ALL --add-noqa

# 2. Commit the noqa baseline, then chip away at categories over time
ruff check --select ALL --ignore D,ANN   # e.g. defer docstrings & annotations

# 3. Later, find suppressions you no longer need
ruff check --select RUF100
```

### Pre-commit-style local gate

```bash
ruff check --fix && ruff format && ty check
```

This fixes and formats, then type-checks — and stops at the first command that exits
non-zero.

---

# Environment Variables

| Variable | Tool | Effect |
|----------|------|--------|
| `RUFF_OUTPUT_FORMAT` | ruff | Default value for `--output-format`. |
| `RUFF_OUTPUT_FILE` | ruff | Default value for `--output-file`. |
| `RUFF_NO_CACHE` | ruff | Disable cache reads (same as `--no-cache`). |
| `RUFF_CACHE_DIR` | ruff | Cache directory location. |
| `TY_CONFIG_FILE` | ty | Path to a `ty.toml` configuration file. |
| `TY_OUTPUT_FORMAT` | ty | Default value for `--output-format`. |
| `TY_LOG` | ty | Tracing filter directives (overrides `-v`/`-q` verbosity). |
| `TY_LOG_PROFILE` | ty | Set to `1`/`true` to write a flamegraph profile (`tracing.folded`). |
| `TY_MEMORY_REPORT` | ty | Print a memory report after a check: `short`, `full`, or `json`. |
| `TY_MAX_PARALLELISM` | ty | Cap the number of worker threads ty uses. |

> [!NOTE]
> A `--`prefixed CLI flag always takes precedence over the matching environment variable,
> which in turn takes precedence over a configuration file.

---

# Exit Codes

**Ruff** (`ruff check` / `ruff format`):

| Code | Meaning |
|------|---------|
| `0` | No violations found (or none remaining after `--fix`). |
| `1` | Violations were found. Use `--exit-zero` to force `0`, or `--exit-non-zero-on-fix` to fail when files were modified. |

**ty** (`ty check`):

| Code | Meaning |
|------|---------|
| `0` | Checking succeeded with no errors. (Warnings also exit `0` unless `--error-on-warning`.) |
| `1` | Type errors were found (or warnings, with `--error-on-warning`). Use `--exit-zero` to force `0`. |
| `2` | Invocation error (e.g. bad arguments, missing working directory). |
| `101` | Internal ty error (a panic or other unexpected failure). |

---

# Troubleshooting

### Error: `No Python files found under the given path(s)`

**Cause:** The path you passed contains no `.py` files after exclusions are applied, or the
files are excluded by your configuration (or `.gitignore`).

**Solution:** Confirm the path is correct, and check whether `--force-exclude` or an
`exclude` pattern is filtering everything out. Use `ruff check --show-files` to see exactly
which files Ruff would analyze.

**Prevention:** Keep `exclude`/`extend-exclude` patterns narrow, and remember that passing
a file explicitly does *not* bypass excludes when `force-exclude` is enabled.

### Error: `You cannot specify more than one configuration file on the command line.`

**Cause:** Two `--config` flags both pointed at config *files* (as opposed to inline
`KEY = VALUE` overrides).

**Solution:** Pass at most one config file path. Use additional `--config "KEY = VALUE"`
flags for any further overrides.

### Error: `The argument --config=... cannot be used with --isolated`

**Cause:** `--isolated` ignores all configuration files, so naming a config file
contradicts it.

**Solution:** Drop either `--isolated` or the `--config` file. You may still use
`--isolated` together with inline `--config "KEY = VALUE"` overrides.

### A `--config "KEY = VALUE"` override is rejected as invalid

**Cause:** The value isn't valid TOML, or it isn't a real Ruff option, or you tried to set
`extend` via `--config` (which is not allowed because `extend` decides which files are read
in the first place).

**Solution:** Quote string values as TOML (`--config "format.quote-style = 'single'"`),
verify the key with `ruff config`, and set `extend` only inside a config file. Ruff's error
output suggests the valid sub-keys when you target a table.

### Rules I configured aren't firing (or unexpected ones are)

**Cause:** Configuration is resolved per-file by walking up the directory tree, so a nested
`pyproject.toml`/`ruff.toml` may be overriding your top-level settings.

**Solution:** Run `ruff check --show-settings <file>` to print the exact, resolved settings
for that file, including which rules are active.

### `ruff format` and the editor disagree, or formatting "flip-flops"

**Cause:** The CLI and the editor are using different configurations or different Ruff
versions.

**Solution:** Ensure both use the same `pyproject.toml`/`ruff.toml`, and pin the version
with `required-version` in `[tool.ruff]`. `ruff server` shares CLI config, so aligning the
config aligns the results.

### ty: `ty failed` followed by a `Cause:` chain

**Cause:** ty hard-errored *before* type checking — typically a configuration resolution or
I/O problem (this is distinct from a type error). The exit code is `2`.

**Solution:** Read the `Cause:` lines; they chain from the high-level failure to the root
(e.g. a malformed `pyproject.toml`). Fix the configuration and re-run.

### ty can't resolve third-party imports

**Cause:** ty cannot find the Python environment where your dependencies are installed.

**Solution:** Pass `--python` pointing at your virtual environment, interpreter, or system
prefix (e.g. `ty check --python .venv`). For unconventional layouts, add module roots with
`--extra-search-path`.

### ty reports the wrong Python version's behavior

**Cause:** ty inferred a different `python-version` than you intended.

**Solution:** Set `python-version` explicitly via `--python-version`, the
`[tool.ty.environment]` table, or `project.requires-python` in `pyproject.toml`.

---

# FAQ

**Q: Is Ruff a replacement for Flake8, isort, and Black?**
Ruff reimplements the rules of many tools (Pyflakes, pycodestyle, isort, pyupgrade,
pydocstyle, flake8 plugins, and more) as a single fast binary, plus a Black-compatible
formatter. Run `ruff linter` to see every upstream linter it covers.

**Q: What's the difference between `ruff check` and `ruff format`?**
`ruff check` finds (and can fix) lint violations. `ruff format` rewrites your code to a
consistent style. They are independent commands; most projects run both.

**Q: What's the difference between Ruff and ty?**
Ruff lints and formats. ty type-checks. They are separate binaries built from the same
repository and share configuration conventions, but they solve different problems.

**Q: How do I see all available rules?**
Run `ruff linter` for linter families and `ruff rule <CODE>` (or `ruff rule --all`) for
individual rule explanations. These reflect exactly what your installed version supports.

**Q: What's the difference between safe and unsafe fixes?**
Safe fixes preserve your code's meaning and are applied by `--fix`. Unsafe fixes might
change behavior and require `--unsafe-fixes`. Preview any fix with `ruff check --fix --diff`.

**Q: How do I suppress a single warning?**
Add `# noqa: <CODE>` to the line for Ruff, or `# ty: ignore` for ty. To bulk-add, use
`ruff check --add-noqa` or `ty check --add-ignore`.

**Q: Where does Ruff store its cache and how do I clear it?**
Ruff caches results in its cache directory (configurable via `cache-dir` or
`RUFF_CACHE_DIR`). Clear it with `ruff clean`, or bypass it with `--no-cache`.

**Q: How do I run Ruff/ty in CI without modifying files?**
Use `ruff check` (without `--fix`), `ruff format --check`, and `ty check`. None of these
write to your files; they only report and set the exit code.

**Q: Can I configure Ruff per-directory?**
Yes. Configuration is resolved by walking up from each file, so nested `ruff.toml` /
`pyproject.toml` files apply to their subtrees. Use `extend` to inherit a base config, and
`--show-settings` to verify what resolves for a given file.

**Q: How do I target a specific Python version?**
For Ruff, set `target-version` (in `[tool.ruff]`) or pass `--target-version`. For ty, set
`python-version` or pass `--python-version`.

---

_Generated June 2026 from astral-sh/ruff at fd16b26. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
