Skip to content

Inconsistent type inference in LSP and CLI for external search paths #4987

Closed as not planned

Description

@janlarres

Describe the Bug

I have a file system structure like this:

./
├── extproject/
│   └── src/
│       └── extpkg/
│           ├── __init__.py
│           └── date.py
└── project/
    ├── .zed/
    │   └── settings.json
    ├── pyproject.toml
    └── test.py

project is the project I'm working on with Zed, and extproject is a third-party project that my project depends on.

When running pyrefly as a language server in Zed, the return type of now() is inferred as "Unknown":
Image

However, on the command line it is recognized correctly:

$ pyrefly check
 INFO Checking project configured at `/home/jan/Downloads/pyrefly-test/project/pyproject.toml`
 INFO revealed type: datetime [reveal-type]
 --> test.py:5:12
  |
5 | reveal_type(test)
  |            ------
 INFO 0 errors

$ pyrefly --version
pyrefly 1.3.1

project/test.py:

from typing_extensions import reveal_type
from extpkg.date import now

test = now()
reveal_type(test)

project/pyproject.toml:

[tool.pyrefly]
search-path = ["../extproject/src"]
preset = "default"

projects/.zed/settings.json:

{
  "languages": {
    "Python": {
      "language_servers": [
        "pyrefly",
        "!ruff",
        "!ty",
        "!basedpyright",
        "!pyright",
        "!pylsp",
        "...",
      ],
    },
  },
}

exproject/src/extpkg/date.py:

from datetime import datetime
from zoneinfo import ZoneInfo

def now():
    return datetime.now(ZoneInfo("UTC"))

I've also attached the files here.
pyrefly-test.zip

Sandbox Link

No response

(Only applicable for extension issues) IDE Information

Zed 1.20.2
Commit: 7c451e694f3c52ee0aeb01d7e28b5fa18cd0ad2f
Version: 1.20.2+stable.360.7c451e694f3c52ee0aeb01d7e28b5fa18cd0ad2f

Pyrefly extension version 0.0.1

LSP log
 INFO Language server processed event `DidChangeConfiguration` in 0.00s (0.01s waiting)
 INFO File /home/jan/Downloads/pyrefly-test/project/test.py opened, prepare to validate open files.
 INFO Ran task on sourcedb_queue heavy task queue. Queue time: 0.00, task time: 0.00
 INFO Published 1 diagnostics for file:///home/jan/Downloads/pyrefly-test/project/test.py
 INFO Validated open files and committed transaction.
 INFO Language server processed event `DidOpenTextDocument` in 0.09s (0.01s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.09s waiting)
 INFO Could not decode `LspConfig` from Null, skipping client configuration request: invalid type: null, expected struct LspConfig.
 INFO Client configuration applied to workspace: Some(Url { scheme: "file", cannot_be_a_base: false, username: "", password: None, host: None, port: None, path: "/home/jan/Downloads/pyrefly-test/project", query: None, fragment: None })
 INFO Could not decode `LspConfig` from Null, skipping client configuration request: invalid type: null, expected struct LspConfig.
 INFO Client configuration applied to workspace: None
 INFO Language server processed event `LspResponse` in 0.00s (0.09s waiting)
 INFO Could not decode `LspConfig` from Null, skipping client configuration request: invalid type: null, expected struct LspConfig.
 INFO Client configuration applied to workspace: Some(Url { scheme: "file", cannot_be_a_base: false, username: "", password: None, host: None, port: None, path: "/home/jan/Downloads/pyrefly-test/project", query: None, fragment: None })
 INFO Could not decode `LspConfig` from Null, skipping client configuration request: invalid type: null, expected struct LspConfig.
 INFO Client configuration applied to workspace: None
 INFO Language server processed event `LspResponse` in 0.00s (0.09s waiting)
 INFO Handling non-canceled request textDocument/inlayHint (2)
 INFO Language server processed event `LspRequest(textDocument/inlayHint)` in 0.00s (0.05s waiting)
 INFO Handling non-canceled request textDocument/semanticTokens/full (3)
 INFO Language server processed event `LspRequest(textDocument/semanticTokens/full)` in 0.00s (0.01s waiting)
 INFO Publishing workspace diagnostics for 1 non-open files.
 INFO Ran task on recheck_queue heavy task queue. Queue time: 0.00, task time: 0.00
 INFO Populating all files in the config (File("/home/jan/Downloads/pyrefly-test/project/pyproject.toml")).
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Prepare to check 1 files.
 INFO Populated all files in the project path, prepare to recheck open files.
 INFO Ran task on recheck_queue heavy task queue. Queue time: 0.00, task time: 0.00
 INFO Populating up to 2000 files in the workspace ("/home/jan/Downloads/pyrefly-test/project").
 INFO Published 1 diagnostics for file:///home/jan/Downloads/pyrefly-test/project/test.py
 INFO Validated open files and committed transaction.
 INFO Publishing workspace diagnostics for 1 non-open files.
 INFO Language server processed event `RecheckFinished` in 0.00s (0.00s waiting)
 INFO Prepare to check 1 files.
 INFO Populated all files in the workspace, prepare to recheck open files.
 INFO Ran task on recheck_queue heavy task queue. Queue time: 0.00, task time: 0.00
 INFO Publishing workspace diagnostics for 1 non-open files.
 INFO Ran task on recheck_queue heavy task queue. Queue time: 0.00, task time: 0.00
 INFO Published 1 diagnostics for file:///home/jan/Downloads/pyrefly-test/project/test.py
 INFO Validated open files and committed transaction.
 INFO Publishing workspace diagnostics for 1 non-open files.
 INFO Language server processed event `RecheckFinished` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Language server processed event `LspResponse` in 0.00s (0.00s waiting)
 INFO Handling non-canceled request textDocument/documentHighlight (4)
 INFO Language server processed event `LspRequest(textDocument/documentHighlight)` in 0.00s (0.00s waiting)
 INFO Handling non-canceled request textDocument/foldingRange (5)
 INFO Language server processed event `LspRequest(textDocument/foldingRange)` in 0.00s (0.00s waiting)
 INFO Handling non-canceled request textDocument/codeLens (6)
 INFO Language server processed event `LspRequest(textDocument/codeLens)` in 0.00s (0.00s waiting)
 INFO Handling non-canceled request textDocument/semanticTokens/full (7)
 INFO Language server processed event `LspRequest(textDocument/semanticTokens/full)` in 0.00s (0.00s waiting)
 INFO Handling non-canceled request textDocument/codeAction (8)
 INFO Language server processed event `LspRequest(textDocument/codeAction)` in 0.00s (0.00s waiting)

Activity

  1. self-assigned this
    on Sep 22, 2026
  2. added theissue type on Sep 22, 2026
  3. assigned and unassigned on Sep 22, 2026
  4. kinto0 commented on Sep 22, 2026

    @kinto0
    Contributor

    Thanks for the detailed report! I can reproduce this.

    Dropping a pyrefly.toml in extproject/ fixes this for me.

    Without a pyrefly.toml, with a extproject/ file open, you might notice this message in the corner:
    Image

    Pyrefly is running with the basic preset because no pyrefly.toml was found.
    
    Run pyrefly init to continue setting up Pyrefly.
    
    Docs: https://pyrefly.org/en/docs/IDE/
    
    Pyrefly version: 1.3.1
    

    Adding a configuration gets us out of basic mode (the "fix" for the symptoms).

    This "basic mode" disables most errors and keeps a vanilla experience similar to Pylance, and it looks like it's disabling our infer-return-types setting. I don't know if this is intentional since I think return type inference is especially useful in basic mode (the usual problem with return-type inference is type errors on unannotated functions which is not an issue in basic mode where these errors are disabled).

    Let me look into why return-type inference is off by default in basic mode...

  5. janlarres commented on Sep 23, 2026

    @janlarres
    Author

    Interesting, any idea why this would be different in the language server vs the CLI? As far as I can tell from the documentation this "auto" behaviour should work the same in both cases.

    So when using search-path the target is treated as its own project with its own configuration? That seems a bit unintuitive to me, I would have expected it to use the configuration of the project I'm referencing it from.

  6. grievejia commented on Sep 23, 2026

    @grievejia
    Contributor

    This difference is intentional. In LSP mode, Pyrefly can analyze files from multiple projects in one session. Different projects can set different Pyrefly configs (including Python versions, search paths, and type-checking settings). The same external library (such as extproject/) can also be imported by multiple downstream projects. Pyrefly cannot always select one importing project's configuration for that library.

    Pyrefly therefore selects a configuration separately for each source file. It searches upward from the file and uses the nearest applicable configuration. Such kind of heuristic obviously doesn't always find the config file that you want, but it does prevents settings from one project from affecting another project, and it prevents Pyrefly LSP behaviors from depending on file open orders in the IDE.

    In your example, project/pyproject.toml lets Pyrefly find extproject/src/extpkg/date.py via search path. However, that configuration does not "cover" date.py (i.e. it doesn't live in an ancestor dir of extproject/). Pyrefly therefore cannot use that configuration to analyze that file, and has to fallback to the default conservative settings (i.e. basic mode).

    CLI pyrefly check without file arguments works differently. Its behavior is defined as "type check one project" and therefore it doesn't need to consider multiple projects, so it can just select one project configuration and uses it for all imported files. CLI pyrefly check with file arguments (e.g. pyrefly check project/test.py), on the other hand, behave more like IDE, for similar reasons.

    For your case, one solution is to place one Pyrefly configuration in the common parent directory. For example, you can lift the Pyrefly settings to pyrefly-test/pyrefly.toml (need to update project-includes and search-path accordingly).

    If you can't modify extproject/, or if the external dependency files do not share a useful parent directory with project/, you can set an explicit configuration path in project/.zed/settings.json instead:

    {
      "lsp": {
        "pyrefly": {
          "initialization_options": {
            "pyrefly": {
              "configPath": "/home/jan/Downloads/pyrefly-test/project/pyproject.toml"
            }
          }
        }
      },
      "languages": { "Python": { "language_servers": ["pyrefly", "..."] } }
    }

    This setting makes the language server use the specified configuration for imported external files.

  7. janlarres commented on Sep 24, 2026

    @janlarres
    Author

    Thanks for the thorough explanation! I didn't really get this difference in behaviour from the documentation, but I may have missed something. I think it would be useful if this could be made clearer in the IDE section. I'm able to make this work for me now, although I'll leave the issue open in case you still consider the return type inference behaviour a bug.

  8. github-actions commented on Oct 9, 2026

    @github-actions

    This issue has someone assigned, but has not had recent activity for more than 2 weeks.

    If you are still working on this issue, please add a comment so everyone knows. Otherwise, please unassign yourself and allow someone else to take over.

    Thank you for your contributions!

  9. kinto0 commented on Oct 12, 2026

    @kinto0
    Contributor

    closing for now since the documentation has been updated

  10. reopened this on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

language-serverIssues specific to our IDE integration rather than type checking

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions