# How to Use pyrefly suppress to Migrate Existing Errors When Upgrading

> Learn how to use pyrefly suppress to migrate existing errors during Pyrefly upgrades. Freeze diagnostics, upgrade seamlessly, and clean up code with this powerful tool.

- Repository: [Meta/pyrefly](https://github.com/facebook/pyrefly)
- Tags: how-to-guide
- Published: 2026-05-21

---

**`pyrefly suppress` automatically inserts or removes `# pyrefly: ignore[…]` comments in Python source files, allowing teams to freeze existing diagnostics before upgrading Pyrefly and clean up stale annotations afterward.**

When upgrading the `facebook/pyrefly` type checker, new error kinds or stricter diagnostics often break existing code. Rather than blocking the upgrade, you can use `pyrefly suppress` to annotate current violations, upgrade safely, and iteratively fix issues while keeping your codebase clean.

## The Two Modes of pyrefly suppress

The command operates in two mutually exclusive modes controlled by the `--remove-unused` flag. Both entry points live in [`pyrefly/lib/commands/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/commands/suppress.rs) (lines 48–84).

**Add Suppressions** (default) scans for user-visible errors—excluding directives like `reveal_type` and already-unused ignores—and inserts ignore comments. **Remove Unused Ignores** deletes annotations that no longer match any diagnostic or strips only the stale error codes while preserving still-relevant ones.

## Architecture and Implementation

### CLI Parsing with SuppressArgs

The `SuppressArgs` struct (derived from `clap::Parser`) collects configuration options in [`pyrefly/lib/commands/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/commands/suppress.rs):

- `FilesArgs` for target file paths
- `--json` for pre-collected error lists
- `--remove-unused` to toggle cleanup mode
- `--comment-location` accepting `line-before` (default) or `same-line`

```rust
#[derive(Clone, Debug, Parser)]
pub struct SuppressArgs { … }

```

### Core Suppression Pipeline

When `SuppressArgs::run` executes, it delegates to algorithms in [`pyrefly/lib/error/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/error/suppress.rs):

1. **Error Collection**: If `--json` is provided, errors deserialize into `SerializedError` structs; otherwise, the command invokes the `check` sub-command directly.
2. **Filtering**: Directives and unused ignores are excluded from the add-suppression flow.
3. **Grouping**: `suppress_errors` groups `SerializedError` instances by file path.
4. **Insertion**: `add_suppressions` parses each file, merges new error codes with existing ignores via `merge_error_codes`, and rewrites the file.

The `SerializedError` struct provides a cheap, JSON-friendly representation containing the file path, line number, error name, and message.

### Edge Case Handling

The implementation in [`pyrefly/lib/error/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/error/suppress.rs) guarantees correctness across diverse Python constructs:

- **Multiline Strings**: The `find_containing_range` helper remaps errors inside multi-line strings or f-strings to the start line, ensuring comments are placed *outside* the literal.
- **Line Endings**: `detect_line_ending` preserves CRLF vs. LF to avoid dirtying diffs.
- **Generated Files**: A `GENERATED_TOKEN` guard skips files marked as auto-generated.
- **Comment Merging**: Existing `# pyrefly: ignore` lines are updated rather than duplicated when new error codes apply to the same line.

### Removing Stale Ignores

In removal mode (`--remove-unused`), `remove_unused_ignores_from_serialized` parses each file and matches error messages starting with `"Unused …"`. It either deletes the entire comment or surgically removes only the unused codes via `update_ignore_comment_with_used_codes`, respecting string literals and backslash continuations.

## Step-by-Step Migration Workflow

Follow this sequence to upgrade Pyrefly without breaking your build:

1. **Collect Current Errors**

   ```bash
   pyrefly check . --output json > errors.json
   ```

2. **Suppress Existing Violations**

   Choose comment placement based on your style guide:

   ```bash
   # Place comments on the line before (default)

   pyrefly suppress . --json errors.json --comment-location line-before
   
   # Or append to the same line

   pyrefly suppress . --json errors.json --comment-location same-line
   ```

3. **Upgrade Pyrefly**

   Update your dependency via `pip install -U pyrefly` or bump the Cargo revision.

4. **Iteratively Fix Errors**

   Address suppressed issues at your own pace while maintaining a green CI.

5. **Clean Up Stale Ignores**

   Once fixes are complete, remove redundant annotations:

   ```bash
   pyrefly suppress . --remove-unused
   ```

   Alternatively, supply a JSON dump of unused-ignore errors:

   ```bash
   pyrefly suppress . --json unused_ignores.json --remove-unused
   ```

## CLI Usage Examples

```bash

# Add suppressions for all errors in the project (line-before placement)

pyrefly suppress .

# Use same-line comments for compact diffs

pyrefly suppress . --comment-location same-line

# Process errors from a CI artifact instead of running check again

pyrefly suppress . --json /tmp/errors.json

# Remove ignores that no longer apply

pyrefly suppress . --remove-unused

# Clean up using a pre-generated list of unused ignores

pyrefly suppress . --json /tmp/unused.json --remove-unused

```

## Programmatic Usage in Rust

You can invoke the suppression engine directly from Rust code:

**Adding Suppressions:**

```rust
use pyrefly::error::suppress::{self, CommentLocation, SerializedError};

fn silence_errors(errors: Vec<SerializedError>) {
    // Place ignore comments on the line before each error
    suppress::suppress_errors(errors, CommentLocation::LineBefore);
}

```

**Removing Unused Ignores:**

```rust
use pyrefly::error::suppress;
use pyrefly::state;

fn clean_ignores(path: &std::path::Path) {
    // Collect unused ignore diagnostics from the type-checking state
    let unused = state::errors::collect_unused_ignore_errors(&state);
    // Remove them from source files
    suppress::remove_unused_ignores(unused);
}

```

**Sample JSON Input:**

```json
[
  {
    "path": "src/example.py",
    "line": 12,
    "name": "bad-assignment",
    "message": "Assignment of type `str` to a variable declared as `int`"
  }
]

```

Running `pyrefly suppress` with the above produces:

```python

# pyrefly: ignore [bad-assignment]

x: int = "oops"

```

## Summary

- **`pyrefly suppress`** lives in [`pyrefly/lib/commands/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/commands/suppress.rs) and orchestrates the add/remove flow.
- **Add mode** inserts `# pyrefly: ignore[code]` comments via `suppress_errors` in [`pyrefly/lib/error/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/error/suppress.rs), merging with existing annotations and handling multiline strings.

- **Remove mode** deletes or trims stale ignores via `remove_unused_ignores_from_serialized`, respecting generated file tokens and line endings.
- **Migration workflow**: Collect errors → Suppress → Upgrade → Fix → Remove unused.
- **Placement options**: `line-before` (default) or `same-line` via `CommentLocation`.

## Frequently Asked Questions

### What is the difference between adding and removing suppressions?

Adding suppressions (the default) inserts new ignore comments for every current error except directives and already-unused ignores. Removing suppressions (`--remove-unused`) scans for diagnostics starting with "Unused …" and either deletes the comment entirely or strips only the specific error codes that no longer apply, leaving valid ignores intact.

### How does pyrefly suppress handle multiline strings?

The algorithm uses `find_containing_range` to detect when an error occurs inside a multiline string or f-string literal. It remaps the error location to the start line of the literal, ensuring that `# pyrefly: ignore` comments are placed outside the string content where they are syntactically valid.

### Can I use pyrefly suppress in CI pipelines?

Yes. Run `pyrefly check --output json` to generate an error artifact, then pass it to `pyrefly suppress --json` in a subsequent job. This avoids re-running the type checker and ensures consistent suppression generation across different environments.

### Where are the core suppression algorithms implemented?

The CLI entry point and argument parsing reside in [`pyrefly/lib/commands/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/commands/suppress.rs). The actual logic for adding and removing comments—including `SerializedError`, `suppress_errors`, `add_suppressions`, and `remove_unused_ignores_from_serialized`—is implemented in [`pyrefly/lib/error/suppress.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/error/suppress.rs). Helpers for locating error ranges and collecting unused ignores are found in [`pyrefly/lib/state/errors.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/errors.rs).