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]