Skip to content

Comments

fix(devtools): scope storage watchers to avoid EMFILE#934

Open
onmax wants to merge 7 commits intonuxt:mainfrom
onmax:fix/devtools-hidden-path-emfile
Open

fix(devtools): scope storage watchers to avoid EMFILE#934
onmax wants to merge 7 commits intonuxt:mainfrom
onmax:fix/devtools-hidden-path-emfile

Conversation

@onmax
Copy link

@onmax onmax commented Feb 23, 2026

Closes #933

  • Replace global nitro.storage.watch() usage with mount-scoped watchers.
  • Keep src live updates for server routes/tasks and custom-mount live updates for the Storage tab.
  • Clean up watcher subscriptions on Nuxt close.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5bb2ab807

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new watchStorageMount utility and UnwatchStorageMount type to observe specific storage mounts. Replaces direct nitro.storage.watch usage with per-mount watchers across server-rpc modules (routes, tasks, storage). Each module stores unwatch functions (unwatchStorage / unwatchStorageMounts), cleans existing watchers before mounting new ones in async ready hooks, and unregisters them in close hooks. Events are normalized/filtered by mount and re-emitted as storage:key:update. No public/exported function signatures were changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(devtools): scope storage watchers to avoid EMFILE' accurately reflects the main change: replacing global storage watchers with mount-scoped watchers to resolve EMFILE issues.
Description check ✅ Passed The description relates to the changeset by explaining the approach (replace global watchers with mount-scoped ones) and what it preserves (live updates for src and custom mounts), matching the code changes.
Linked Issues check ✅ Passed The PR successfully addresses issue #933 by replacing global nitro.storage.watch() with mount-scoped watchers, preventing unnecessary watchers from exceeding OS file-watch limits and causing EMFILE errors.
Out of Scope Changes check ✅ Passed All changes are directly related to scoping storage watchers: new storage-watch.ts helper, modifications to storage.ts, server-routes.ts, and server-tasks.ts with proper cleanup hooks—all within scope of fixing the EMFILE issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/devtools/src/server-rpc/storage-watch.ts (1)

9-16: Optional: preserve driver context when invoking watch.

const watch = mount?.driver.watch detaches the method from its receiver. If any driver implements watch as a regular function referencing this (e.g., this.client, this.options), calling it unbound will throw at runtime.

♻️ Proposed fix
-  const watch = mount?.driver.watch
+  const watch = mount?.driver.watch?.bind(mount.driver)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage-watch.ts` around lines 9 - 16, The
code currently detaches the watch method by assigning const watch =
mount?.driver.watch which can break drivers that use this; instead obtain a
bound function from mount.driver (e.g., bind the method to mount.driver) or call
it as a method on the driver so the driver context is preserved when invoking
watch; update the call site that returns await watch(...) to use the bound
method or call mount.driver.watch with the same arguments so onChange(event,
normalizeKey(`${mountKey}${key}`)) still runs but with the correct receiver.
packages/devtools/src/server-rpc/storage.ts (1)

24-39: Registering close and ready hooks inside nitro:init is inconsistent and fragile.

Both sibling files (server-routes.ts and server-tasks.ts) register ready and close at the function's top level, guarding ready with if (!nitro) return. Nesting these inside nitro:init means each invocation of nitro:init adds a new pair of listeners. While the existing unwatch-before-rewatch logic makes this safe today, it results in redundant hook executions and diverges from the pattern established by the rest of this PR.

♻️ Proposed refactor (aligns with server-routes.ts / server-tasks.ts pattern)
 export function setupStorageRPC({
   nuxt,
   rpc,
   ensureDevAuthToken,
 }: NuxtDevtoolsServerContext) {
   const storageMounts: StorageMounts = {}

   let storage: Storage | undefined
   let unwatchStorageMounts: Array<() => Promise<void> | void> = []

   nuxt.hook('nitro:init', (nitro) => {
     storage = nitro.storage

-    nuxt.hook('close', async () => {
-      await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
-      unwatchStorageMounts = []
-    })
-
-    nuxt.hook('ready', async () => {
-      if (!storage)
-        return
-      await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
-      unwatchStorageMounts = await Promise.all(Object.keys(storageMounts).map(mountName =>
-        watchStorageMount(storage, mountName, (event, key) => {
-          if (shouldIgnoreStorageKey(key))
-            return
-          rpc.broadcast.callHook.asEvent('storage:key:update', key, event)
-        })))
-    })
-
     // Taken from https://github.com/unjs/nitro/blob/...
     const mounts = {
       ...nitro.options.storage,
       ...nitro.options.devStorage,
     }

     for (const name of Object.keys(mounts)) {
       if (shouldIgnoreStorageKey(name))
         continue
       storageMounts[name] = mounts[name]!
     }
   })

+  nuxt.hook('ready', async () => {
+    if (!storage)
+      return
+    const _storage = storage
+    await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
+    unwatchStorageMounts = await Promise.all(Object.keys(storageMounts).map(mountName =>
+      watchStorageMount(_storage, mountName, (event, key) => {
+        if (shouldIgnoreStorageKey(key))
+          return
+        rpc.broadcast.callHook.asEvent('storage:key:update', key, event)
+      })))
+  })
+
+  nuxt.hook('close', async () => {
+    await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
+    unwatchStorageMounts = []
+  })
+
   return { ... }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage.ts` around lines 24 - 39, The hooks
for storage mounts are registered inside a nitro:init listener which causes
duplicate registrations each time nitro:init fires; move the nuxt.hook('ready',
...) and nuxt.hook('close', ...) registrations out of the nitro:init handler to
the module top-level, guarding the ready hook with the same pattern used
elsewhere (e.g. if (!nitro) return) so they are only registered once. Preserve
the existing behavior: on ready, clear prior unwatchs, set unwatchStorageMounts
= await Promise.all(Object.keys(storageMounts).map(mountName =>
watchStorageMount(storage, mountName, (event, key) => { if
(shouldIgnoreStorageKey(key)) return;
rpc.broadcast.callHook.asEvent('storage:key:update', key, event) }))); and on
close await Promise.all(unwatchStorageMounts.map(unwatch => unwatch())) and
reset unwatchStorageMounts = []; reference functions/vars watchStorageMount,
shouldIgnoreStorageKey, rpc.broadcast.callHook.asEvent, storageMounts, storage,
and unwatchStorageMounts when moving the hooks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 1-2: The code imports WatchCallback and WatchEvent from
'unstorage' which are not guaranteed public types; remove these from the import
and add local type aliases instead: define type WatchEvent = "update" | "remove"
and type WatchCallback = (event: WatchEvent, key: string) => void, keep
importing Storage, normalizeBaseKey and normalizeKey as before, and update any
references to WatchCallback/WatchEvent in the file to use the new local types
(e.g., where watch handlers or event parameters are typed).

In `@packages/devtools/src/server-rpc/storage.ts`:
- Around line 33-38: The closure passed to Promise.all may see a non-narrowed
let variable `storage`, causing a type error; fix by capturing the narrowed
value into a const before mapping (e.g. after `if (!storage) return` do `const
_storage = storage`) and then use `_storage` in the call to `watchStorageMount`
(update references in the `unwatchStorageMounts = await
Promise.all(Object.keys(storageMounts).map(...)` callback to
`watchStorageMount(_storage, mountName, ...)`), leaving the rest (including
`shouldIgnoreStorageKey` and `rpc.broadcast.callHook.asEvent`) unchanged.

---

Nitpick comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 9-16: The code currently detaches the watch method by assigning
const watch = mount?.driver.watch which can break drivers that use this; instead
obtain a bound function from mount.driver (e.g., bind the method to
mount.driver) or call it as a method on the driver so the driver context is
preserved when invoking watch; update the call site that returns await
watch(...) to use the bound method or call mount.driver.watch with the same
arguments so onChange(event, normalizeKey(`${mountKey}${key}`)) still runs but
with the correct receiver.

In `@packages/devtools/src/server-rpc/storage.ts`:
- Around line 24-39: The hooks for storage mounts are registered inside a
nitro:init listener which causes duplicate registrations each time nitro:init
fires; move the nuxt.hook('ready', ...) and nuxt.hook('close', ...)
registrations out of the nitro:init handler to the module top-level, guarding
the ready hook with the same pattern used elsewhere (e.g. if (!nitro) return) so
they are only registered once. Preserve the existing behavior: on ready, clear
prior unwatchs, set unwatchStorageMounts = await
Promise.all(Object.keys(storageMounts).map(mountName =>
watchStorageMount(storage, mountName, (event, key) => { if
(shouldIgnoreStorageKey(key)) return;
rpc.broadcast.callHook.asEvent('storage:key:update', key, event) }))); and on
close await Promise.all(unwatchStorageMounts.map(unwatch => unwatch())) and
reset unwatchStorageMounts = []; reference functions/vars watchStorageMount,
shouldIgnoreStorageKey, rpc.broadcast.callHook.asEvent, storageMounts, storage,
and unwatchStorageMounts when moving the hooks.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0216c08 and b5bb2ab.

📒 Files selected for processing (4)
  • packages/devtools/src/server-rpc/server-routes.ts
  • packages/devtools/src/server-rpc/server-tasks.ts
  • packages/devtools/src/server-rpc/storage-watch.ts
  • packages/devtools/src/server-rpc/storage.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/devtools/src/server-rpc/storage-watch.ts (1)

1-2: ⚠️ Potential issue | 🟠 Major

WatchCallback and WatchEvent may not be part of unstorage's stable public type surface.

A previous review raised this exact concern and it remains unaddressed. WatchEvent lives in unstorage's internal types.ts and WatchCallback is the inline type (event: WatchEvent, key: string) => void. Neither is confirmed to be a named export in the public dist/index.d.ts — they may not survive a minor version bump.

Replace with locally-defined aliases:

🛡️ Proposed fix
-import type { Storage, WatchCallback, WatchEvent } from 'unstorage'
+import type { Storage } from 'unstorage'
 import { normalizeBaseKey, normalizeKey } from 'unstorage'

+type WatchEvent = 'update' | 'remove'
+type WatchCallback = (event: WatchEvent, key: string) => void

Run the following script to confirm whether WatchCallback and WatchEvent are named exports in the installed unstorage declaration files:

#!/bin/bash
# Find unstorage dist declaration files and check for WatchCallback / WatchEvent exports
fd -e d.ts . -p unstorage --exec grep -l "export.*Watch" {} \; 2>/dev/null | head -10

echo "--- Named exports ---"
fd "index.d.ts" -p unstorage --exec grep -n "export.*WatchCallback\|export.*WatchEvent" {} \; 2>/dev/null

echo "--- unstorage version ---"
fd "package.json" -p "node_modules/unstorage" --max-depth 1 --exec grep '"version"' {} \; 2>/dev/null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage-watch.ts` around lines 1 - 2, The
code imports WatchCallback and WatchEvent from 'unstorage' which are internal
and may break; remove WatchCallback and WatchEvent from the import and instead
declare local types in this module: add a local type alias "type WatchEvent = {
type: string; [key: string]: any }" (or a stricter shape matching how you use
it) and "type WatchCallback = (event: WatchEvent, key: string) => void", export
them if needed by other modules, then update any references to use these local
types; keep the existing import of Storage, normalizeBaseKey, normalizeKey
unchanged. Also run the provided verification script to confirm exports in your
installed unstorage if you want to double-check.
🧹 Nitpick comments (1)
packages/devtools/src/server-rpc/storage.ts (1)

24-39: The const activeStorage narrowing fix is well-applied, and the close-hook cleanup is clean.

One optional tidying: the shouldIgnoreStorageKey(key) guard at Line 36 is redundant. The storageMounts object is already built with only non-ignored mounts (Lines 49–53), and watchStorageMount always prefixes emitted keys with the mount name, so the key prefix is guaranteed to be non-ignored.

♻️ Optional simplification
     unwatchStorageMounts = await Promise.all(Object.keys(storageMounts).map(mountName =>
       watchStorageMount(activeStorage, mountName, (event, key) => {
-        if (shouldIgnoreStorageKey(key))
-          return
         rpc.broadcast.callHook.asEvent('storage:key:update', key, event)
       })))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage.ts` around lines 24 - 39, Remove the
redundant key-ignore guard inside the nuxt.hook('ready') callback: since
storageMounts is constructed to exclude ignored mounts and watchStorageMount
prefixes emitted keys with the mount name, the shouldIgnoreStorageKey(key) check
before calling rpc.broadcast.callHook.asEvent('storage:key:update', key, event)
can be deleted; update the ready hook (where activeStorage, unwatchStorageMounts
and watchStorageMount are used) to directly broadcast the event without that
guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 10-15: The current call to mount.driver.watch may resolve to
undefined (Driver.watch returns MaybePromise<Unwatch | void>), which causes a
TypeError when cleanup later calls the stored unwatch; wrap the result of await
mount.driver.watch(...) and only push a real function into unwatchStorageMounts:
call mount.driver.watch with the callback (as in the existing return) and check
the returned value (e.g., const unwatch = await mount.driver.watch(...)); if
unwatch is a function, store it in unwatchStorageMounts (or return it),
otherwise store a no-op function or skip storing; update references to
mount.driver.watch and unwatchStorageMounts in this module and storage.ts to
ensure cleanup always calls a valid function.

---

Duplicate comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 1-2: The code imports WatchCallback and WatchEvent from
'unstorage' which are internal and may break; remove WatchCallback and
WatchEvent from the import and instead declare local types in this module: add a
local type alias "type WatchEvent = { type: string; [key: string]: any }" (or a
stricter shape matching how you use it) and "type WatchCallback = (event:
WatchEvent, key: string) => void", export them if needed by other modules, then
update any references to use these local types; keep the existing import of
Storage, normalizeBaseKey, normalizeKey unchanged. Also run the provided
verification script to confirm exports in your installed unstorage if you want
to double-check.

---

Nitpick comments:
In `@packages/devtools/src/server-rpc/storage.ts`:
- Around line 24-39: Remove the redundant key-ignore guard inside the
nuxt.hook('ready') callback: since storageMounts is constructed to exclude
ignored mounts and watchStorageMount prefixes emitted keys with the mount name,
the shouldIgnoreStorageKey(key) check before calling
rpc.broadcast.callHook.asEvent('storage:key:update', key, event) can be deleted;
update the ready hook (where activeStorage, unwatchStorageMounts and
watchStorageMount are used) to directly broadcast the event without that guard.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5bb2ab and 9ad2ff9.

📒 Files selected for processing (2)
  • packages/devtools/src/server-rpc/storage-watch.ts
  • packages/devtools/src/server-rpc/storage.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/devtools/src/server-rpc/storage.ts (1)

41-50: ⚠️ Potential issue | 🟡 Minor

Cleanup is skipped when activeStorage is falsy

The early-return on line 43 short-circuits before unwatchStorageMounts is drained. If ready ever fires a second time while storage happens to be undefined (e.g., after a failed HMR reinit), any previously registered watchers will be orphaned until close.

The fix is trivial — drain first, then guard:

🛡️ Proposed fix
  nuxt.hook('ready', async () => {
+   await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
+   unwatchStorageMounts = []
    const activeStorage = storage
    if (!activeStorage)
      return
-   await Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
    unwatchStorageMounts = await Promise.all(Object.keys(storageMounts).map(mountName =>
      watchStorageMount(activeStorage, mountName, (event, key) => {
        rpc.broadcast.callHook.asEvent('storage:key:update', key, event)
      })))
  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage.ts` around lines 41 - 50, The ready
hook currently returns early when activeStorage (storage) is falsy, skipping
cleanup of existing watchers; change the order in the nuxt.hook('ready', ...)
handler to always first drain/unwatch existing watchers by awaiting
Promise.all(unwatchStorageMounts.map(unwatch => unwatch())) (or await
Promise.all(unwatchStorageMounts) if they are already promises), then reset
unwatchStorageMounts and only after that check if storage/activeStorage is
truthy before re-registering new watchers with watchStorageMount and
rpc.broadcast.callHook.asEvent('storage:key:update', ...); ensure references to
storage, activeStorage, unwatchStorageMounts and watchStorageMount are used
exactly as in the diff.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/devtools/src/server-rpc/storage.ts`:
- Around line 41-50: The ready hook currently returns early when activeStorage
(storage) is falsy, skipping cleanup of existing watchers; change the order in
the nuxt.hook('ready', ...) handler to always first drain/unwatch existing
watchers by awaiting Promise.all(unwatchStorageMounts.map(unwatch => unwatch()))
(or await Promise.all(unwatchStorageMounts) if they are already promises), then
reset unwatchStorageMounts and only after that check if storage/activeStorage is
truthy before re-registering new watchers with watchStorageMount and
rpc.broadcast.callHook.asEvent('storage:key:update', ...); ensure references to
storage, activeStorage, unwatchStorageMounts and watchStorageMount are used
exactly as in the diff.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad2ff9 and 3bb41da.

📒 Files selected for processing (2)
  • packages/devtools/src/server-rpc/storage-watch.ts
  • packages/devtools/src/server-rpc/storage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/devtools/src/server-rpc/storage-watch.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 10-14: The current call to storage.watch(...) opens watchers for
every mount (even though you filter by mountKey), causing EMFILE growth; change
watchStorageMount to call storage.getMount(mountKey) (or equivalent) to retrieve
the specific driver and invoke that driver's watch(...) method instead of
storage.watch, so only the target mount creates watchers; handle the case where
getMount returns null/undefined by returning a no-op/unwatch function and
preserve the original callback signature (event: WatchEvent, key: string) while
normalizing the key before calling onChange.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb41da and d5e8ce2.

📒 Files selected for processing (1)
  • packages/devtools/src/server-rpc/storage-watch.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/devtools/src/server-rpc/storage-watch.ts (1)

1-19: All previous issues addressed — LGTM.

The implementation correctly:

  • Uses driver-scoped mount.driver.watch to avoid the EMFILE regression.
  • Guards the void return from Driver.watch with ?? (() => {}).
  • Defines WatchEvent/WatchCallback locally instead of relying on unstorage internals.
  • Uses normalizeBaseKey(mount.base) !== mountKey as the critical guard preventing fallback to the root mount when no exact match exists.

One optional cleanup on line 11: storage.getMount() is typed as returning { base: string; driver: Driver } (non-nullable), so the !mount check can never be true and can be removed.

Optional cleanup
-  if (!mount || normalizeBaseKey(mount.base) !== mountKey || !mount.driver?.watch)
+  if (normalizeBaseKey(mount.base) !== mountKey || !mount.driver?.watch)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/server-rpc/storage-watch.ts` around lines 1 - 19, The
mount null-check is redundant because storage.getMount() is typed as
non-nullable; in watchStorageMount remove the unnecessary `if (!mount || ...)`
branch and rely only on the existing guard `normalizeBaseKey(mount.base) !==
mountKey || !mount.driver?.watch` (or equivalent) to decide early return,
keeping the rest of the function (calling mount.driver.watch, normalizing keys,
and returning unwatch) unchanged; this targets the mount variable and the
watchStorageMount function to simplify the condition without altering behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/devtools/src/server-rpc/storage-watch.ts`:
- Around line 1-19: The mount null-check is redundant because storage.getMount()
is typed as non-nullable; in watchStorageMount remove the unnecessary `if
(!mount || ...)` branch and rely only on the existing guard
`normalizeBaseKey(mount.base) !== mountKey || !mount.driver?.watch` (or
equivalent) to decide early return, keeping the rest of the function (calling
mount.driver.watch, normalizing keys, and returning unwatch) unchanged; this
targets the mount variable and the watchStorageMount function to simplify the
condition without altering behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5e8ce2 and 8bacedb.

📒 Files selected for processing (1)
  • packages/devtools/src/server-rpc/storage-watch.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EMFILE in hidden project paths (e.g. ~/.codex/worktrees) with devtools enabled

1 participant