·
Last changedVersion:
This version passed the hardening gate. Hardening applied 14 targeted changes and preserved all other text. Classified findings before hardening: 1. Classified findings closed: 0. Classified findings remaining: 1. Unclassified scanner findings remaining: 0. Review findings closed: 13. Review findings remaining: 1.
1
1
Expand | File | Group | Subtype | Severity | Status |
|---|---|---|---|---|---|
LICENSE | Excessive Agency & Rogue Autonomy | — | Low | Open |
1 finding
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).Delete any of these from the top of the file (pyrefly honors # mypy: ignore-errors
for mypy compat, so that one must go too):
pyrefly.tomlIMPORTANT: 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.
When migrating one file, set matches to that exact file path. Use a directory glob only when the user confirms a migration of the whole directory. When mirroring, copy only the value from the parent config, keep every other key as strict as shown above, and report the pyrefly.toml change at handoff.
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:
# pyrefly: ignore[<category>] # TODO.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. Each TODO suppression names the upstream symbol
or file responsible, and list every added suppression in the final summary for
user review.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 minimum
Python 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). Prefer
Callable[..., 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.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 the
guard, and use from __future__ import annotations (or string forward refs) so
runtime imports stay lazy:
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.X | Y), Sequence[X]-style abstract type, or a bound TypeVar
for 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 to Any but stricter — pyrefly rejects value.foo() without an
isinstance.Any — last rung. Always preferred over a # pyrefly: ignore on 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").object/Any in 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 beats object).Any —
don't pattern-match "looks dynamic" on the first try.# 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. In the example below the
category placeholder stands for missing-attribute, the pyrefly name:
# 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: 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:
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, using the same underscore names, when the
wrapper prepends or appends args.
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. In files other than the target, ask the user before removing them.
Required before handing off — annotations frequently shift import order and line length:
Resolve anything lintrunner can't auto-fix manually.
Pass only the target file, pyrefly.toml, and any files the user approved. Review the resulting diff before handing off.
Running tests executes repository code with your privileges. Run them only in a checkout the user has confirmed is trusted, and ask the user first if the repository is unfamiliar.
Precedence when something fails: tests passing > pyrefly clean > annotation
strictness. If a freshly-added annotation breaks a test, move 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.
Backward-compat check. Run iff
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/.
Unit tests for the modified module. Search both ways before concluding no coverage exists (the built-in file and content search tools work too; in the shell, the module name must be a plain name taken from the target path):
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 annotations
still need string quoting:
Get started with chainctlchainctl skill install public/pytorch/pyrefly-type-coverage:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.cgr.dev/public/github.com/pytorch/.claude/skills/pyrefly-type-coverage:latest
BSD-3-Clause
HARDENING.md
LICENSE
SKILL.md