| name | ty-python-lsp |
| description | Use when reading, navigating, or editing Python (.py/.pyi) in a project where the ty language server is running. Covers when to prefer LSP requests (definition, references, hover, document/workspace symbols, call hierarchy) over grep, how to position requests so they don't silently miss, how to read ty's diagnostics, and which type-system gaps to expect while ty is in beta. |
Using ty as a Python language server
ty is Astral's Python type checker and language server. It is fast and its
navigation is semantic, so it resolves through imports, re-exports, aliases, and
into .venv and the stdlib — things a text search cannot do.
ty is in beta. Navigation is solid; the type system still has holes. See
"Beta expectations" below before trusting a type conclusion.
Prefer LSP over grep when the question is semantic
These are operation values on the LSP tool.
| Question | Use |
|---|
| Where is this symbol actually defined? | goToDefinition |
| Who calls / uses this? | findReferences |
| What is this expression's type? What are the args? | hover |
| What's in this file, structurally? | documentSymbol |
| Where is a symbol whose file I don't know? | workspaceSymbol |
| What's the call chain into or out of this function? | prepareCallHierarchy, then incomingCalls / outgoingCalls |
| Where is this string literal / TODO / config key? | grep |
| Which files exist, what's the layout? | glob / read |
Run documentSymbol before reading a large file — it's a cheap sitemap and
saves pulling the whole file into context.
Grep is still right for non-semantic text and for symbols that ty cannot resolve
(see beta gaps). When LSP returns nothing for something you can see with your own
eyes, that is usually a positioning bug on your side, not absence — check the
next section before falling back.
Positioning is exact, and failure is silent
Positions are zero-indexed lines, and characters are UTF-16 code units
(so astral-plane characters — emoji, some CJK — count as 2).
Point at a character inside the identifier you're asking about, not at the dot,
the paren, the whitespace before it, or the line. Off by one and you get an empty
result with no error, which reads exactly like "not found."
result = client.fetch_user(uid)
If a request comes back empty, re-check the column against the actual line text
before concluding the symbol doesn't exist.
Warm-up: the server indexes the workspace on start. The very first
workspaceSymbol or cross-file findReferences request against a cold server
can return fewer results than it should. If a result looks suspiciously empty
right after startup, retry once.
Diagnostics
Diagnostics are pushed as you open and edit files — you don't poll for them.
After a Write or Edit to a .py file, they surface on their own. Treat a new
diagnostic on a line you just touched as feedback on that edit.
ty is a gradual checker: when it cannot infer a type it yields Unknown
and stays quiet rather than erroring. Unknown in a hover is a signal that
inference bottomed out — often an unannotated function return, a dynamically
assigned attribute, or an untyped third-party call. It is not proof the code is
correct, and it is not itself an error to fix.
For type errors on the whole project (rather than open files), run the checker
directly: ty check.
Not provided by the server
Claude Code offers a goToImplementation operation, but ty does not implement
it. The request hard-errors (-32601 Unknown request) rather than returning an
empty list — so a tool error there means "ty can't do this," not "no
implementations exist." Reach for findReferences on the base symbol, or grep
for (BaseName), instead.
Also unsupported: code lens, document link, document color, rename-files.
Formatting is out of scope for ty. Use ruff format; ty exposes no
formatting, range-formatting, or on-type-formatting.
Beta expectations
Navigation and hover are reliable. The type system has known gaps, tracked in
astral-sh/ty#1889 — consult it
before reporting a bug or trusting a surprising type. As of this writing the
notable ones:
- Return type inference for unannotated functions is not implemented
(#128) — the single most common
source of
Unknown. Adding a return annotation usually restores inference
downstream.
TypeVarTuple / Unpack unsupported (#156),
including Unpack for **kwargs.
- Framework-specific plugins — Pydantic, Django, and
attrs — are not
modeled, so synthesized __init__s and fields may type as Unknown.
functools.partial is only partially modeled (#1536);
simple cases do resolve (partial(add, 1) → partial[(b: int) -> int]).
- Compiled extensions (
.so) and PEP 723 inline script metadata are unresolved.
- Protocol
@classmethod/@staticmethod members and type[SomeProtocol] are incomplete.
Missing LSP features are tracked separately in
astral-sh/ty#3588.
When ty's answer contradicts what the code plainly does, suspect one of the gaps
above rather than rewriting working code to satisfy the checker.