Skip to content

Persistent incorrect semantic diagnostic after large rebase or checkout: Module has no exported member #47466

Description

Bug Report

When using tsserver in VSCode, I sometimes see persistent false error reports from diagnostics after a large rebase or checkout of the form, "Module '{}' has no exported member '{}'." The file mentioned in the error is a file that was updated in the checkout or rebase. Opening the file in question in VSCode loads the file and resolves the incorrect semantic diagnostic. Alternatively, restarting TSServer resolves the incorrect diagnostic

🔎 Search Terms

module has no exported member watch

🕗 Version & Regression Information

This has been happening as long as we can remember

🙁 Actual behavior

persistent incorrect ssemantic diagnostics

🙂 Expected behavior

No errors after a reasonable period of time

Activity

  1. MartinJohns commented on Jan 16, 2022

    @MartinJohns
    Contributor

    Isn't this fixed by #47274?

  2. MarcCelani-at commented on Jan 16, 2022

    @MarcCelani-at
    Author

    #47274 is not fixed. Besides, these may be separate issues, because the way that caches get invalidated for program structure is different than how source file info caches get updated.

    This is interesting...

    My repro steps:

    I have two branches, foo and bar, which include many file changes between them, including a file, "dependenecy.tsx"

    1. Open VSCode
    2. Open a file, openfile.tsx, which imports from dependency.tsx
    3. checkout branch "bar". I see a "FileWatcher:: Triggered" log line for dependency.tsx
    4. I switch back to "foo". I see "DirectoryWatcher:: Triggered" log lines for dependency.tsx, but I do not see FileWatcher:: Triggered log line should fire for dependency.tsx. With additional logging patched in, I notice that dependency.tsx is never read.
    5. From henceforth, any branch switches back and forth between foo and bar do not trigger the watch for dependency.tsx

    I did not see any FileWatcher:: Closed log lines.

  3. MarcCelani-at commented on Jan 16, 2022

    @MarcCelani-at
    Author

    I have a theory that the issue is this code, which does not properly handle if a file's inode changes:

    TypeScript/src/compiler/sys.ts

    Lines 1653 to 1659 in 1298f49

    return event === "rename" &&
    (!relativeName ||
    relativeName === lastDirectoryPart ||
    (relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) !== -1 && relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length)) &&
    !fileSystemEntryExists(fileOrDirectory, entryKind) ?
    invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) :
    callback(event, relativeName);

    fs.watch() does not watch a "path", it watches an inode, and the inode for a path can change. The code above does not handle this because if it discovers the file still exists after a rename, it assumes the watch it still good, when there's a very good chance that it's not.

    It turns out this is really easy to reproduce with just vim. Repro steps:

    1. Open a file in VSCode
    2. tail log and wait for it to calm down
    3. Open an imported file in vim
    4. Delete an export that is used in the file opened in VSCode. Issue :w command

    When you issue a :w command in vim, it atomically updates the file with an operation that will trigger a rename event.

    This will trigger the bug and it will manifest one of two ways.
    Case 1: FileWatcher will convince itself that it has to close, and you will never see an update for this file again if you do any subsequent writes.

    Case 2: FileWatcher will trigger but will not close, but after that there will be no more watch events if you do it again.

    I suspect git is sometimes (always?) performing some kind of operation like vim is with :w. Perhaps a large checkout/rebase makes a race condition more likely to occur (race between git updating the file and nodejs processing the watch and checking if the file exists).

    I confirmed that my git checkout repro is changing the inode with ls -i. I suspect that the way to fix this is to look at the inode information from statSync.

  4. MarcCelani-at commented on Jan 16, 2022

    @MarcCelani-at
    Author

    (If I am correct, then I am quite certain this issue is different than #47274)

  5. OliverJAsh commented on May 25, 2022

    @OliverJAsh
    Contributor

    Coming here from #44066. If I add these VS Code settings the issue disappears for me, i.e. there are no stale errors when I switch branch (using my test case in #44066):

        "typescript.tsserver.watchOptions": {
            "watchFile": "useFsEvents",
            "watchDirectory": "useFsEvents"
        },

    Just mentioning it in case it's helpful information for debugging.

  6. MarcCelani-at commented on May 25, 2022

    @MarcCelani-at
    Author

    This issue is for users of FsEvents, so your suggestion doesn't really apply here. But thanks for commenting!

  7. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions