pyrefly-type-coverage
Pyrefly-type-coverage adds Python type annotations to code and configures local files to standardize error reporting.
Install
mkdir -p .claude/skills/pyrefly-type-coverage && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/2224" && unzip -o skill.zip -d .claude/skills/pyrefly-type-coverage && rm skill.zipInstalls to .claude/skills/pyrefly-type-coverage
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Migrate a file to use stricter Pyrefly type checking with annotations required for all functions, classes, and attributes.Key capabilities
- →Add type annotations to functions
- →Automate linting config updates
- →Enable strict type checking
- →Resolve unannotated-return errors
How it works
It systematically updates file-level configuration and adds function signatures to satisfy strict static analysis requirements.
Inputs & outputs
When to use pyrefly-type-coverage
- →Enable strict type checking for a module
- →Add missing type annotations to functions
- →Migrate legacy code to Pyrefly standards
- →Resolve type-related linting errors
About this skill
Pyrefly Type Coverage Skill
Prerequisites
- The file must live in a project with a
pyrefly.toml. pyrefly,lintrunner, and the project's test runner must be on PATH. If any are missing, stop and ask whether a conda environment needs activating — don't install or substitute (per repo CLAUDE.md).
Step 1: Remove file-level type-check suppressions
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
Step 2: Add a sub-config entry to 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.
Step 3: Run pyrefly
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:
- Reported in another file (path != target): leave it. Don't widen scope. If
the error is now blocking the target, suppress at the report site with
# pyrefly: ignore[<category>] # TODO. - Reported in the target file but the message names a symbol defined elsewhere
(e.g.,
bad-returnbecause an imported function's annotation is wrong): suppress locally with the same TODO comment. Don't invent acast()that papers over the upstream gap. - Reported in the target file, originates locally: fix it.
Use # pyrefly: ignore[...] only as a last resort, and only on non-target categories.
Step 4: Add annotations
Examine call sites when the right type isn't obvious from the function body.
Annotation conventions
- Use PEP 604 / PEP 585 syntax (
int | None,list[str]) — assume Python >= 3.10. - Prefer
collections.abcovertypingfor ABCs (Callable,Sequence,Generator, ...). - For generic helpers, import from
typingwhen available on the project's minimum Python version, and fromtyping_extensionsonly when you need a newer feature (e.g.,Selfandoverrideif supporting < 3.11/3.12, or PEP 696default=forTypeVar/ParamSpec). Don't blanket-import fromtyping_extensions. - Always parameterize
Callable(never bareCallable). PreferCallable[..., object]; reach forCallable[..., Any]only when a caller genuinely consumes the dynamic return — if the result is just passed through (or the callable isn't even invoked),objectis stricter and equally correct. (See ParamSpec below for the signature-preserving wrapper case.) - Give any module-local global you introduce a leading underscore —
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 (_PoutnumbersP~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. - A boolean predicate —
is_*/has_*name, takes a broad type (oftenobject), returnsbool— usually wantsTypeGuard[X](orTypeIs[X], which also narrows the negative branch).TypeGuardis intyping(>= 3.10, so import from there);TypeIsonly enteredtypingin 3.13, so import it fromtyping_extensions(>= 4.10) to stay 3.10-compatible. Anissubclass-style helper takingklass: type[_T]should returnTypeGuard[type[_T]]. Prefer an explicitisinstance(x, type)guard overtry/except TypeErroraroundissubclass()— clearer, and it lets the checker narrow. - When a return type is derived from a parameter — passthroughs/identity
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]withParamSpec/TypeVar) rather than widening toobject/Any. "Output type == some input type" is exactly what aTypeVarencodes;objectin /objectout 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 singleTypeVaris wrong — name the actual domain type instead. - Class attributes assigned in
__init__should get a class-level annotation so pyrefly can see them. - Break import cycles with
if TYPE_CHECKING:— annotation-only imports go inside the guard, and usefrom __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: ... - Never suppress the three target categories.
unannotated-return,unannotated-parameter, andimplicit-anyare 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. - Widen, don't bail. When the right type is hard to infer, walk down this
ladder rather than reaching for an ignore:
- Most specific concrete type observable from call sites and return paths.
- A union (
X | Y),Sequence[X]-style abstract type, or a boundTypeVarfor genuinely generic functions (identity-passthrough, container helpers). object— strictest fallback that still type-checks. Forces callers to narrow before use, e.g.,def serialize(value: object) -> str:. Visually similar toAnybut stricter — pyrefly rejectsvalue.foo()without anisinstance.Any— last rung. Always preferred over a# pyrefly: ignoreon a target category, 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").
- Be especially wary of
object/Anyin return position — a function usually 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 beatsobject). - Read at least three call sites before deciding a parameter must be
Any— don't pattern-match "looks dynamic" on the first try. - Narrow-scope
# pyrefly: ignore[...](on a non-target category) is reserved for 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)() - When an inline
# pyrefly: ignore[...]would push a line past the length limit, put it on the line immediately above the flagged line rather than reaching for# fmt: skipto keep it inline — pyrefly honors a previous-line ignore. (Exception: the backward-compat carve-out below, where it must sit on thedefline.)
Backward compatibility (the one exception to never-suppress)
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
Step 5: Iterate
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.
Step 6: L
Content truncated.
When not to use it
- →Rapid prototyping where types are unknown
- →Legacy codebase migrations without testing
Prerequisites
Limitations
- →Can trigger secondary errors elsewhere in code
- →Cannot auto-infer complex custom types
How it compares
It prioritizes strict type compliance over loose type hinting, specifically for Pyrefly environments.
Compared to similar skills
pyrefly-type-coverage side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| pyrefly-type-coverage (this skill) | 6 | 3mo | Review | Advanced |
| python-design-patterns | 19 | 2mo | No flags | Intermediate |
| python-patterns | 6 | 2mo | Review | Beginner |
| modular-code | 4 | 7mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by pytorch
View all by pytorch →You might also like
python-design-patterns
wshobson
Python design patterns including KISS, Separation of Concerns, Single Responsibility, and composition over inheritance. Use when making architecture decisions, refactoring code structure, or evaluating when abstractions are appropriate.
python-patterns
affaan-m
Pythonic 惯用法、PEP 8 标准、类型提示以及构建健壮、高效、可维护的 Python 应用程序的最佳实践。
modular-code
parcadei
Modular Code Organization
ast-grep-find
parcadei
AST-based code search and refactoring via ast-grep MCP
naming-analyzer
davila7
Suggest better variable, function, and class names based on context and conventions.
dead-code
parcadei
Find unused functions and dead code in the codebase