codeant-ai-for-open-source[bot] commented on code in PR #40084:
URL: https://github.com/apache/superset/pull/40084#discussion_r3231498817


##########
superset/extensions/local_extensions_watcher.py:
##########
@@ -29,37 +29,113 @@
 
 logger = logging.getLogger(__name__)
 
+# Sentinel file Flask watches via --extra-files.  Touching it on a real change
+# triggers a server reload without depending on cwd or the location of any
+# Python source file.
+RELOAD_TRIGGER = Path(__file__).resolve().parent / ".reload_trigger"
+
 # Guard to prevent multiple initializations
 _watcher_initialized = False
 _watcher_lock = threading.Lock()
 
 
-def _get_file_handler_class() -> Any:
+def _get_file_handler_class() -> Any:  # noqa: C901
     """Get the file handler class, importing watchdog only when needed."""
     try:
-        from watchdog.events import FileSystemEventHandler
+        import hashlib
+
+        from watchdog.events import (
+            FileCreatedEvent,
+            FileModifiedEvent,
+            FileMovedEvent,
+            FileSystemEventHandler,
+        )
 
         class LocalExtensionFileHandler(FileSystemEventHandler):
-            """Custom file system event handler for LOCAL_EXTENSIONS 
directories."""
+            """Custom file system event handler for LOCAL_EXTENSIONS 
directories.
+
+            Only reacts to genuine content changes (create / modify / move) in 
the
+            dist directory, verified by comparing a SHA-256 of the file's 
content.
+            This avoids the Docker VirtioFS / osxfs problem where reading a 
file
+            generates inotify events that watchdog surfaces as modifications.
+            """
+
+            def __init__(self) -> None:
+                super().__init__()
+                # sha256 of last-seen content, keyed by absolute path
+                self._file_hashes: dict[str, str] = {}
+                # Deduplicate: only trigger once per second across all files
+                self._last_trigger: float = 0.0
+                self._lock = threading.Lock()
+
+            # ── helpers ──────────────────────────────────────────────────────
+
+            @staticmethod
+            def _sha256(path: str) -> str | None:
+                try:
+                    with open(path, "rb") as fh:
+                        return hashlib.sha256(fh.read()).hexdigest()
+                except OSError:
+                    return None
+
+            def _content_changed(self, path: str) -> bool:
+                """Return True only when the file's content differs from last 
seen.
+
+                The first time a path is observed its hash is stored as the 
baseline
+                and False is returned — that event is a 'first-seen', not a 
change.
+                Only a subsequent event where the digest differs from the 
baseline
+                is treated as a genuine content change.
+                """
+                digest = self._sha256(path)
+                if digest is None:
+                    return False
+                old_digest = self._file_hashes.get(path)
+                self._file_hashes[path] = digest
+                if old_digest is None:
+                    # First observation — record baseline, do not trigger 
restart.
+                    return False
+                return old_digest != digest
+
+            # ── event handler 
─────────────────────────────────────────────────
 
             def on_any_event(self, event: Any) -> None:
-                """Handle any file system event in the watched directories."""
+                """Handle file system events in the watched directories."""
                 if event.is_directory:
                     return
 
-                # Only trigger on changes to files in `dist` directory
+                # Only react to true write events; skip access / close / open 
etc.
+                if not isinstance(
+                    event, (FileCreatedEvent, FileModifiedEvent, 
FileMovedEvent)
+                ):
+                    return
+
+                # Only care about files inside a `dist` directory
                 src = getattr(event, "src_path", None)
                 if not isinstance(src, str) or "dist" not in Path(src).parts:
                     return
 
+                # Verify the file content actually changed to ignore spurious
+                # inotify events generated by Docker bind-mount reads.
+                if not self._content_changed(src):
+                    return
+
+                # Debounce: one restart per second max, regardless of how many
+                # files webpack writes simultaneously.
+                now = time.monotonic()
+                with self._lock:
+                    if now - self._last_trigger < 1.0:
+                        return
+                    self._last_trigger = now

Review Comment:
   **Suggestion:** The debounce is implemented as a leading-edge rate limit, so 
reload is triggered immediately on the first write and later writes in the next 
second are dropped. During multi-file webpack builds this can restart Flask 
before the build settles and then suppress the final update, leaving the app 
reloaded against partial artifacts. Switch to trailing debounce (trigger after 
a quiet window) rather than immediate trigger-and-drop. [incorrect condition 
logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Reload may occur before webpack build fully completes.
   - ⚠️ Final asset updates within debounce window can be skipped.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Ensure the watcher is running as in the previous cases: `create_app()` at
   `superset/app.py:47-84` calls `start_local_extensions_watcher_thread(app)` 
when
   `app.debug` is true, which starts `setup_local_extensions_watcher(app)` at
   `superset/extensions/local_extensions_watcher.py:146` and installs a
   `LocalExtensionFileHandler` with `self._last_trigger` initialized to `0.0` 
at line 68.
   
   2. In a controlled test, arrange for two successive genuine content changes 
on files under
   a watched `dist` directory: for example, use a Python script to write new 
contents to
   `/app/local_extensions/my_ext/dist/index.js` twice in quick succession so 
that both events
   pass `_content_changed()` at 
`superset/extensions/local_extensions_watcher.py:81-97` and
   reach the debounce block at lines 122-128.
   
   3. Observe via timestamps and logs that on the first qualifying event, `now =
   time.monotonic()` at line 122 and setting `self._last_trigger = now` at line 
128 allow the
   handler to log "File change detected in LOCAL_EXTENSIONS" and call
   `os.utime(RELOAD_TRIGGER, ...)` at lines 130-138, immediately triggering a 
Flask reload
   while subsequent writes in the same rebuild are still in progress.
   
   4. Trigger a second qualifying change within one second of the first (for 
example, by only
   sleeping 0.2 seconds between writes); on this second `on_any_event()` call, 
the condition
   `if now - self._last_trigger < 1.0:` at line 126 is true, so the function 
returns early
   and does not touch `RELOAD_TRIGGER`, meaning any later-or-final asset 
updates within that
   1-second window cannot cause another reload and the app may restart against 
an earlier,
   potentially incomplete build state instead of the final artifacts.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%20122%3A128%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20The%20debounce%20is%20implemented%20as%20a%20leading-edge%20rate%20limit%2C%20so%20reload%20is%20triggered%20immediately%20on%20the%20first%20write%20and%20later%20writes%20in%20the%20next%20second%20are%20dropped.%20During%20multi-file%20webpack%20builds%20this%20can%20restart%20Flask%20before%20the%20build%20settles%20and%20then%20suppress%20the%20final%20update%2C%20leaving%20the%20app%20reloaded%20against%20partial%20artifacts.%20Switch%20to%20trailing%20debounce%20%28trigger%20after%20a%20quiet%20window%29%20rather%20than%20immediate%20trigger-and-drop.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%2
 
0If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%20122%3A128%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20The%20debounce%20is%20implemented%20as%20a%20leading-edge%20rate%20limit%2C%20so%20reload%20is%20triggered%20immediately%20on%20the%20first%20write%20and%20later%20writes%20in%20the%20next%20second%20are%20dropped.%20During%20multi-file%20webpack%20bu
 
ilds%20this%20can%20restart%20Flask%20before%20the%20build%20settles%20and%20then%20suppress%20the%20final%20update%2C%20leaving%20the%20app%20reloaded%20against%20partial%20artifacts.%20Switch%20to%20trailing%20debounce%20%28trigger%20after%20a%20quiet%20window%29%20rather%20than%20immediate%20trigger-and-drop.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/extensions/local_extensions_watcher.py
   **Line:** 122:128
   **Comment:**
        *Incorrect Condition Logic: The debounce is implemented as a 
leading-edge rate limit, so reload is triggered immediately on the first write 
and later writes in the next second are dropped. During multi-file webpack 
builds this can restart Flask before the build settles and then suppress the 
final update, leaving the app reloaded against partial artifacts. Switch to 
trailing debounce (trigger after a quiet window) rather than immediate 
trigger-and-drop.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=88affabd66f96b9c9e4e23c9cb8ebdddf9c7b3bc3a8fe03f3d9ec2abcb568811&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=88affabd66f96b9c9e4e23c9cb8ebdddf9c7b3bc3a8fe03f3d9ec2abcb568811&reaction=dislike'>👎</a>



##########
superset/extensions/local_extensions_watcher.py:
##########
@@ -29,37 +29,113 @@
 
 logger = logging.getLogger(__name__)
 
+# Sentinel file Flask watches via --extra-files.  Touching it on a real change
+# triggers a server reload without depending on cwd or the location of any
+# Python source file.
+RELOAD_TRIGGER = Path(__file__).resolve().parent / ".reload_trigger"
+
 # Guard to prevent multiple initializations
 _watcher_initialized = False
 _watcher_lock = threading.Lock()
 
 
-def _get_file_handler_class() -> Any:
+def _get_file_handler_class() -> Any:  # noqa: C901
     """Get the file handler class, importing watchdog only when needed."""
     try:
-        from watchdog.events import FileSystemEventHandler
+        import hashlib
+
+        from watchdog.events import (
+            FileCreatedEvent,
+            FileModifiedEvent,
+            FileMovedEvent,
+            FileSystemEventHandler,
+        )
 
         class LocalExtensionFileHandler(FileSystemEventHandler):
-            """Custom file system event handler for LOCAL_EXTENSIONS 
directories."""
+            """Custom file system event handler for LOCAL_EXTENSIONS 
directories.
+
+            Only reacts to genuine content changes (create / modify / move) in 
the
+            dist directory, verified by comparing a SHA-256 of the file's 
content.
+            This avoids the Docker VirtioFS / osxfs problem where reading a 
file
+            generates inotify events that watchdog surfaces as modifications.
+            """
+
+            def __init__(self) -> None:
+                super().__init__()
+                # sha256 of last-seen content, keyed by absolute path
+                self._file_hashes: dict[str, str] = {}
+                # Deduplicate: only trigger once per second across all files
+                self._last_trigger: float = 0.0
+                self._lock = threading.Lock()
+
+            # ── helpers ──────────────────────────────────────────────────────
+
+            @staticmethod
+            def _sha256(path: str) -> str | None:
+                try:
+                    with open(path, "rb") as fh:
+                        return hashlib.sha256(fh.read()).hexdigest()
+                except OSError:
+                    return None
+
+            def _content_changed(self, path: str) -> bool:
+                """Return True only when the file's content differs from last 
seen.
+
+                The first time a path is observed its hash is stored as the 
baseline
+                and False is returned — that event is a 'first-seen', not a 
change.
+                Only a subsequent event where the digest differs from the 
baseline
+                is treated as a genuine content change.
+                """
+                digest = self._sha256(path)
+                if digest is None:
+                    return False
+                old_digest = self._file_hashes.get(path)
+                self._file_hashes[path] = digest
+                if old_digest is None:
+                    # First observation — record baseline, do not trigger 
restart.
+                    return False
+                return old_digest != digest
+
+            # ── event handler 
─────────────────────────────────────────────────
 
             def on_any_event(self, event: Any) -> None:
-                """Handle any file system event in the watched directories."""
+                """Handle file system events in the watched directories."""
                 if event.is_directory:
                     return
 
-                # Only trigger on changes to files in `dist` directory
+                # Only react to true write events; skip access / close / open 
etc.
+                if not isinstance(
+                    event, (FileCreatedEvent, FileModifiedEvent, 
FileMovedEvent)
+                ):
+                    return
+
+                # Only care about files inside a `dist` directory
                 src = getattr(event, "src_path", None)
                 if not isinstance(src, str) or "dist" not in Path(src).parts:
                     return
 
+                # Verify the file content actually changed to ignore spurious
+                # inotify events generated by Docker bind-mount reads.
+                if not self._content_changed(src):
+                    return

Review Comment:
   **Suggestion:** Move events are evaluated only through `src_path`, but 
atomic build workflows often move a temp file into `dist` (destination changes, 
source may be outside `dist` or already gone). This causes legitimate updates 
to be ignored or hashed from a non-existent source path. For `FileMovedEvent`, 
use `dest_path` for both the `dist` check and content hash. [incorrect variable 
usage]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Moved dist assets never trigger reload when src outside.
   - ⚠️ Extension UI may show stale bundles after builds.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run Superset in debug mode with LOCAL_EXTENSIONS configured so that 
`create_app()` in
   `superset/app.py:47-84` calls `start_local_extensions_watcher_thread(app)` at
   `superset/app.py:80-82`, which starts the watcher thread and ultimately 
instantiates
   `LocalExtensionFileHandler` via `_get_file_handler_class()` at
   `superset/extensions/local_extensions_watcher.py:42-140`.
   
   2. In a test or REPL, import the handler class from 
`_get_file_handler_class()` and
   construct a `FileMovedEvent` from `watchdog.events` where `src_path` is 
outside any `dist`
   directory (for example `/tmp/tmpfile.js`) and `dest_path` points into an 
extension dist
   directory (for example `/app/local_extensions/my_ext/dist/index.js`); this 
matches atomic
   rename behavior where a temp file is moved into `dist`.
   
   3. Call `handler.on_any_event(event)` on this `FileMovedEvent`; inside 
`on_any_event()` at
   `superset/extensions/local_extensions_watcher.py:101-121`, the code reads 
`src =
   getattr(event, "src_path", None)` at line 112 and checks `if not 
isinstance(src, str) or
   "dist" not in Path(src).parts:` at line 114, which passes because `"dist"` 
is not in the
   source path segments, causing an early `return` at line 115.
   
   4. Because filtering and hashing are both based on `src_path`, the handler 
never calls
   `_content_changed()` with the destination path and never reaches the reload 
trigger at
   lines 130-138, so an asset that was just moved into the watched `dist` 
directory does not
   cause `RELOAD_TRIGGER` to be touched and no Flask reload occurs, even though 
the final
   asset visible to the app changed.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%20112%3A120%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Variable%20Usage%3A%20Move%20events%20are%20evaluated%20only%20through%20%60src_path%60%2C%20but%20atomic%20build%20workflows%20often%20move%20a%20temp%20file%20into%20%60dist%60%20%28destination%20changes%2C%20source%20may%20be%20outside%20%60dist%60%20or%20already%20gone%29.%20This%20causes%20legitimate%20updates%20to%20be%20ignored%20or%20hashed%20from%20a%20non-existent%20source%20path.%20For%20%60FileMovedEvent%60%2C%20use%20%60dest_path%60%20for%20both%20the%20%60dist%60%20check%20and%20content%20hash.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20
 
it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%20112%3A120%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Variable%20Usage%3A%20Move%20events%20are%20evaluated%20only%20through%20%60src_path%60%2C%20but%20atomic%20build%20workflows%20often%20move%20a%20temp%20file%20into%20%60dist%60%20%28destination%20changes%2C%20source%20may%20be%20outside%20%60dist%60%20or%20already%20gone%29.%20This%20causes%20legitimate%20updates%20to%20be%20ignored%20or%20hash
 
ed%20from%20a%20non-existent%20source%20path.%20For%20%60FileMovedEvent%60%2C%20use%20%60dest_path%60%20for%20both%20the%20%60dist%60%20check%20and%20content%20hash.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/extensions/local_extensions_watcher.py
   **Line:** 112:120
   **Comment:**
        *Incorrect Variable Usage: Move events are evaluated only through 
`src_path`, but atomic build workflows often move a temp file into `dist` 
(destination changes, source may be outside `dist` or already gone). This 
causes legitimate updates to be ignored or hashed from a non-existent source 
path. For `FileMovedEvent`, use `dest_path` for both the `dist` check and 
content hash.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=23eb89b32a46012944e8e434d8b5490ac8e6a94fb354eada2a4d4c18f5fb5eb6&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=23eb89b32a46012944e8e434d8b5490ac8e6a94fb354eada2a4d4c18f5fb5eb6&reaction=dislike'>👎</a>



##########
superset/extensions/local_extensions_watcher.py:
##########
@@ -29,37 +29,113 @@
 
 logger = logging.getLogger(__name__)
 
+# Sentinel file Flask watches via --extra-files.  Touching it on a real change
+# triggers a server reload without depending on cwd or the location of any
+# Python source file.
+RELOAD_TRIGGER = Path(__file__).resolve().parent / ".reload_trigger"
+
 # Guard to prevent multiple initializations
 _watcher_initialized = False
 _watcher_lock = threading.Lock()
 
 
-def _get_file_handler_class() -> Any:
+def _get_file_handler_class() -> Any:  # noqa: C901
     """Get the file handler class, importing watchdog only when needed."""
     try:
-        from watchdog.events import FileSystemEventHandler
+        import hashlib
+
+        from watchdog.events import (
+            FileCreatedEvent,
+            FileModifiedEvent,
+            FileMovedEvent,
+            FileSystemEventHandler,
+        )
 
         class LocalExtensionFileHandler(FileSystemEventHandler):
-            """Custom file system event handler for LOCAL_EXTENSIONS 
directories."""
+            """Custom file system event handler for LOCAL_EXTENSIONS 
directories.
+
+            Only reacts to genuine content changes (create / modify / move) in 
the
+            dist directory, verified by comparing a SHA-256 of the file's 
content.
+            This avoids the Docker VirtioFS / osxfs problem where reading a 
file
+            generates inotify events that watchdog surfaces as modifications.
+            """
+
+            def __init__(self) -> None:
+                super().__init__()
+                # sha256 of last-seen content, keyed by absolute path
+                self._file_hashes: dict[str, str] = {}
+                # Deduplicate: only trigger once per second across all files
+                self._last_trigger: float = 0.0
+                self._lock = threading.Lock()
+
+            # ── helpers ──────────────────────────────────────────────────────
+
+            @staticmethod
+            def _sha256(path: str) -> str | None:
+                try:
+                    with open(path, "rb") as fh:
+                        return hashlib.sha256(fh.read()).hexdigest()
+                except OSError:
+                    return None
+
+            def _content_changed(self, path: str) -> bool:
+                """Return True only when the file's content differs from last 
seen.
+
+                The first time a path is observed its hash is stored as the 
baseline
+                and False is returned — that event is a 'first-seen', not a 
change.
+                Only a subsequent event where the digest differs from the 
baseline
+                is treated as a genuine content change.
+                """
+                digest = self._sha256(path)
+                if digest is None:
+                    return False
+                old_digest = self._file_hashes.get(path)
+                self._file_hashes[path] = digest
+                if old_digest is None:
+                    # First observation — record baseline, do not trigger 
restart.
+                    return False

Review Comment:
   **Suggestion:** The first real change for each file is dropped because new 
paths are treated as baseline (`old_digest is None -> return False`). Since 
`_file_hashes` is not pre-populated at startup, the first modify/create event 
after watcher startup will not trigger a reload, so developers can edit a file 
once and see no restart. Initialize hashes up front (or treat first event for 
existing watched files as a change) to avoid missing the first edit. [logic 
error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ First LOCAL_EXTENSIONS dist edit triggers no Flask reload.
   - ⚠️ Developers must save extension assets twice to reload.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset in debug mode so the watcher is enabled: `create_app()` at
   `superset/app.py:47-84` checks `app.debug` and calls
   `start_local_extensions_watcher_thread(app)` at `superset/app.py:80-82`.
   
   2. The daemon thread created in `start_local_extensions_watcher_thread()` at
   `superset/extensions/local_extensions_watcher.py:104-110` calls
   `setup_local_extensions_watcher(app)` at
   `superset/extensions/local_extensions_watcher.py:146`, which in turn calls
   `_get_file_handler_class()` at 
`superset/extensions/local_extensions_watcher.py:42-140` to
   instantiate `LocalExtensionFileHandler` with `self._file_hashes` initialized 
as an empty
   dict at line 66.
   
   3. Edit a built asset file under a configured LOCAL_EXTENSIONS `dist` 
directory (one of
   the paths in `app.config["LOCAL_EXTENSIONS"]`, read at
   `superset/extensions/local_extensions_watcher.py:164-179`) so that watchdog 
emits a
   `FileModifiedEvent` and `LocalExtensionFileHandler.on_any_event()` at
   `superset/extensions/local_extensions_watcher.py:101-121` is invoked.
   
   4. Inside `_content_changed()` at 
`superset/extensions/local_extensions_watcher.py:81-97`,
   for this first event on the given file path, `old_digest = 
self._file_hashes.get(path)` at
   line 92 returns `None`, `self._file_hashes[path] = digest` at line 93 stores 
the baseline,
   and the `if old_digest is None:` block at lines 94-96 returns `False`, 
causing
   `on_any_event()` to exit at lines 117-120 without touching `RELOAD_TRIGGER` 
and without
   logging the "File change detected in LOCAL_EXTENSIONS" message—resulting in 
no Flask
   reload on the first real content update to that file after watcher startup.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%2092%3A96%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20The%20first%20real%20change%20for%20each%20file%20is%20dropped%20because%20new%20paths%20are%20treated%20as%20baseline%20%28%60old_digest%20is%20None%20-%3E%20return%20False%60%29.%20Since%20%60_file_hashes%60%20is%20not%20pre-populated%20at%20startup%2C%20the%20first%20modify%2Fcreate%20event%20after%20watcher%20startup%20will%20not%20trigger%20a%20reload%2C%20so%20developers%20can%20edit%20a%20file%20once%20and%20see%20no%20restart.%20Initialize%20hashes%20up%20front%20%28or%20treat%20first%20event%20for%20existing%20watched%20files%20as%20a%20change%29%20to%20avoid%20missing%20the%20first%20edit.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%2
 
0I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fextensions%2Flocal_extensions_watcher.py%0A%2A%2ALine%3A%2A%2A%2092%3A96%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20The%20first%20real%20change%20for%20each%20file%20is%20dropped%20because%20new%20paths%20are%20treated%20as%20baseline%20%28%60old_digest%20is%20None%20-%3E%20return%20False%60%29.%20Since%20%60_file_hashes%60%20is%20not%20pre-populated%20at%20s
 
tartup%2C%20the%20first%20modify%2Fcreate%20event%20after%20watcher%20startup%20will%20not%20trigger%20a%20reload%2C%20so%20developers%20can%20edit%20a%20file%20once%20and%20see%20no%20restart.%20Initialize%20hashes%20up%20front%20%28or%20treat%20first%20event%20for%20existing%20watched%20files%20as%20a%20change%29%20to%20avoid%20missing%20the%20first%20edit.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/extensions/local_extensions_watcher.py
   **Line:** 92:96
   **Comment:**
        *Logic Error: The first real change for each file is dropped because 
new paths are treated as baseline (`old_digest is None -> return False`). Since 
`_file_hashes` is not pre-populated at startup, the first modify/create event 
after watcher startup will not trigger a reload, so developers can edit a file 
once and see no restart. Initialize hashes up front (or treat first event for 
existing watched files as a change) to avoid missing the first edit.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=341824d469406f51b7f1840772467e651c29413f03471cf46a82a2fbd2871c03&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=341824d469406f51b7f1840772467e651c29413f03471cf46a82a2fbd2871c03&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to