Migrate a file to use stricter Pyrefly type checking with annotations required for all functions, classes, and attributes.
npx skills add https://github.com/pytorch/pytorch --skill pyrefly-type-coverage
pyrefly.toml.pyrefly, lintrunner, and the project's test runner must be on PATH. **If anyare missing, stop and ask whether a conda environment needs activating** — don't
install or substitute (per repo CLAUDE.md).
Delete any of these from the top of the file (pyrefly honors # mypy: ignore-errors
for mypy compat, so that one must go too):
# pyre-ignore-all-errors
# pyre-ignore-all-errors[16,21,53,56]
# @lint-ignore-every PYRELINT
# mypy: ignore-errors
pyrefly.toml[[sub-config]]
matches = "path/to/directory/**"
[sub-config.errors]
implicit-import = false
implicit-any = true
bad-param-name-override = false
unannotated-return = true
unannotated-parameter = true
IMPORTANT: Setting any error key in [sub-config.errors] overrides only that key
relative to the parent — but enabling unannotated-return / unannotated-parameter /
implicit-any will resurface errors that were previously hidden file-wide. If you see
unrelated errors (e.g., bad-param-name-override) flooding the output, mirror the
parent config's setting for that key in the sub-config to silence them.
pyrefly check <FILENAME>
Goal: resolve all unannotated-return, unannotated-parameter, and implicit-any
errors by adding annotations — see Step 4's ladder. These three target categories are
always resolvable; never suppress them with # pyrefly: ignore. The single
exception is @compatibility(is_backward_compatible=True) (Step 4).
Other categories (bad-argument-type, missing-attribute, …) are real type bugs.
Handle them by where pyrefly reports them:
the error is now blocking the target, suppress at the report site with
# pyrefly: ignore[<category>] # TODO.
(e.g., bad-return because an imported function's annotation is wrong):
suppress locally with the same TODO comment. Don't invent a cast() that
papers over the upstream gap.
Use # pyrefly: ignore[...] only as a last resort, and only on non-target categories.
Examine call sites when the right type isn't obvious from the function body.
int | None, list[str]) — assume Python >= 3.10.collections.abc over typing for ABCs (Callable, Sequence, Generator, ...).typing when available on the project's minimumPython version, and from typing_extensions only when you need a newer feature
(e.g., Self and override if supporting < 3.11/3.12, or PEP 696 default= for
TypeVar / ParamSpec). Don't blanket-import from typing_extensions.
Callable (never bare Callable). PreferCallable[..., object]; reach for Callable[..., Any] only when a caller
genuinely consumes the dynamic return — if the result is just passed through
(or the callable isn't even invoked), object is stricter and equally
correct. (See ParamSpec below for the signature-preserving wrapper case.)
TypeVar/ParamSpec (matching the string arg: _T = TypeVar("_T"),
_P = ParamSpec("_P"), _R = TypeVar("_R")), TypeAliases, helper constants,
and sentinels alike. This is the prevailing torch convention for non-public
names (_P outnumbers P ~6:1 in the tree). Exceptions (leave un-underscored):
a name imported by other modules, listed in __all__, or used as a runtime
token (e.g. an annotation-string dispatch marker). Applies only to names you
add — do not rename pre-existing globals; that's an unrelated refactor
outside this skill's scope.
is_*/has_* name, takes a broad type (often object),returns bool — usually wants TypeGuard[X] (or TypeIs[X], which also
narrows the negative branch). TypeGuard is in typing (>= 3.10, so import
from there); TypeIs only entered typing in 3.13, so import it from
typing_extensions (>= 4.10) to stay 3.10-compatible. An issubclass-style
helper taking klass: type[_T] should return TypeGuard[type[_T]]. Prefer an
explicit isinstance(x, type) guard over try/except TypeError around
issubclass() — clearer, and it lets the checker narrow.
functions, "return one of these args" helpers, decorators, registries keyed by
type — reach for a TypeVar (or, for a callable arg whose signature flows
through, Callable[_P, _R] with ParamSpec/TypeVar) rather than widening to
object/Any. "Output type == some input type" is exactly what a TypeVar
encodes; object in / object out discards it. Caveat: if the function
*transforms* the value so the output type differs from the input (e.g. converts
an array to an int), a single TypeVar is wrong — name the actual domain type
instead.
__init__ should get a class-level annotation so pyrefly can see them.if TYPE_CHECKING: — annotation-only imports go inside theguard, and use from __future__ import annotations (or string forward refs) so
runtime imports stay lazy:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from torch.fx import GraphModule
def transform(gm: GraphModule) -> GraphModule: ...
unannotated-return,unannotated-parameter, and implicit-any are always resolvable by adding
an annotation; # pyrefly: ignore[<one of those>] is not an acceptable
outcome. The single exception is the Backward compatibility carve-out below.
ladder rather than reaching for an ignore:
X | Y), Sequence[X]-style abstract type, or a bound TypeVarfor genuinely generic functions (identity-passthrough, container helpers).
object — strictest fallback that still type-checks. Forces callers tonarrow before use, e.g., def serialize(value: object) -> str:. Visually
similar to Any but stricter — pyrefly rejects value.foo() without an
isinstance.
Any — last rung. Always preferred over a # pyrefly: ignore on a targetcategory, but only after rungs 1–3 fail. Be able to articulate why each
earlier rung doesn't fit (e.g., "union exceeds 8 types", "no observable
common bound", "callers genuinely never narrow").
object/Any in return position — a functionusually knows more about what it produces than its callers do. A wide return is
right only at a genuine boundary (it returns its input unchanged, or the value
is handler/caller-defined); if the body builds a known shape, name it (a domain
alias or union beats object).
Any —don't pattern-match "looks dynamic" on the first try.
# pyrefly: ignore[...] (on a non-target category) is reservedfor cases where pyrefly is *actually wrong* about a specific local error —
dynamic metaprogramming, third-party stub gaps:
# pyrefly: ignore[attr-defined]
result = getattr(obj, dynamic_name)()
# pyrefly: ignore[...] would push a line past the lengthlimit, put it on the line immediately above the flagged line rather than
reaching for # fmt: skip to keep it inline — pyrefly honors a previous-line
ignore. (Exception: the backward-compat carve-out below, where it must sit on
the def line.)
CRITICAL: Functions decorated with @compatibility(is_backward_compatible=True)
must NOT have their signatures changed. The backward-compat test
(test_function_back_compat) compares stringified inspect.signature against a golden
file — adding annotations (even -> None) changes that string and the test fails.
Use pyrefly ignore comments instead:
@compatibility(is_backward_compatible=True)
def my_function( # pyrefly: ignore[unannotated-return]
self,
arg1, # can't add type here either
):
...
The # pyrefly: ignore comment must be on the def line (where pyrefly reports the error),
not on the closing ).
ParamSpec for signature-preserving wrappers (decorators, functools.wraps-style
helpers). Use Callable[P, R] so the wrapped function's signature flows through
to the caller — Callable[..., Any] loses it. Skip ParamSpec if the wrapper
genuinely accepts arbitrary callables. Pair with Concatenate[X, P] when the
wrapper prepends or appends args.
from collections.abc import Callable
from typing import ParamSpec, TypeVar
_P = ParamSpec("_P")
_R = TypeVar("_R")
def log_calls(fn: Callable[_P, _R]) -> Callable[_P, _R]:
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
return fn(*args, **kwargs)
return wrapper
Re-run pyrefly check. New annotations often surface bad-return errors where the
function actually returns an incompatible type — fix those. Repeat until clean.
Tightening a shared helper (e.g. adding a TypeGuard or a precise return) can
make pre-existing # pyrefly: ignore comments in its callers unused. Re-check and
delete now-dead suppressions and any stale explanatory comments — don't leave them.
Required before handing off — annotations frequently shift import order and line
length:
lintrunner -a <files...>
Resolve anything lintrunner can't auto-fix manually.
Precedence when something fails: tests passing > pyrefly clean > annotation
strictness. If a freshly-added annotation breaks a test, narrow it one rung in
the discipline ladder (e.g., concrete → object, or remove an Any widening
that broke a downstream isinstance check) before reverting the file.
grep -l '@compatibility(is_backward_compatible=True)' <target> returns the
file — the decorator is the actual precondition for the golden file. The
broader "imports torch.fx" heuristic catches half of torch/.
python -m pytest test/test_fx.py::TestFXAPIBackwardCompatibility -x -v
no coverage exists:
# torch/foo/bar.py is usually covered by test/test_foo.py or test/test_bar.py
ls test/ | grep -i <module-name>
# or by import
grep -rl "from torch.foo.bar import\|import torch.foo.bar" test/
If both come up empty, tell the user — don't silently skip. Type changes can
introduce real runtime regressions (Optional[X] vs X, Sequence vs
list when .append is called, etc.).
from __future__ import annotationsstill need string quoting:
class MyClass:
def __new__(cls) -> "MyClass": ...
CLAUDE.md). Stop and surface the diff for review when the file is clean.
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take pytorch/pyrefly-type-coverage from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.