Skip to content

fix: handle permission denied in daily summary search and detect fdfind on Ubuntu#398

Merged
shabaraba merged 1 commit intomainfrom
fix/daily-summary-permission-denied-and-fd-detection
Feb 19, 2026
Merged

fix: handle permission denied in daily summary search and detect fdfind on Ubuntu#398
shabaraba merged 1 commit intomainfrom
fix/daily-summary-permission-denied-and-fd-detection

Conversation

@shabaraba
Copy link
Owner

@shabaraba shabaraba commented Feb 19, 2026

概要

VibingDailySummaryAll でアクセス権限のないディレクトリが含まれる場合にエラーで止まる問題と、Ubuntu で fd コマンドが検出されない問題を修正します。

修正内容

1. Permission denied エラーでも処理を継続 (ripgrep_command.lua, collector.lua)

問題:
rg がアクセス権限のないディレクトリに遭遇すると exit code 2 を返します。以前の実装では code != 0 && code != 1 の場合にエラーとして扱い、stdout に出力された部分的な結果も全て破棄していました。

Warn vibing.nvim: Failed to search directory /home/user/workspace: rg command failed: rg: /home/user/workspace/some/dir: Permission denied (os error 13)

修正:

  • ripgrep_command: exit code 2 の場合は stdout の内容(取得できたファイル)を返し、エラーは partial_error として呼び出し元に伝える
  • collector: 部分的なエラーがある場合は "Partial error searching directory" の警告を出しつつ取得済みのファイルで処理を継続。両方の検索が完全に失敗した場合のみ処理を中断する

2. Ubuntu/Debian での fdfind 検出 (fd_command.lua)

問題:
Ubuntu では apt install fd-find でインストールされる fd コマンドの実行ファイル名が fdfind になります。which fd のみで判定していたため、auto 選択で fd が検出されず find にフォールバックしていました。

修正:
detect_fd_executable() 関数で fdfdfind を順番に確認し、見つかった方を使用するように変更。

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • File search operations now continue with partial results when one search fails instead of stopping completely.
    • Permission denied errors during file operations are now handled gracefully with partial results returned.
    • File finding now detects and supports multiple executable variants for better system compatibility.
  • Improvements

    • Added optional modification time-based filtering for file search results.

- ripgrep_command: treat exit code 2 (permission denied) as partial
  error; return found files from stdout instead of discarding them
- collector: continue with partial results when only one of vibing/md
  searches fails; show 'Partial error' warning instead of failing silently
- fd_command: detect fdfind executable (Ubuntu/Debian apt package name)
  in addition to fd, so auto strategy picks up fd on Ubuntu
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

This change enhances fault tolerance across the file discovery pipeline by enabling partial result handling. The collector now retains results when one search succeeds despite the other failing. The fd_command detects the fd executable at runtime and adds pattern-based matching with mtime filtering. The ripgrep_command treats permission errors as non-fatal, returning partial results with warnings.

Changes

Cohort / File(s) Summary
Error Handling & Partial Results
lua/vibing/application/daily_summary/collector.lua
Enhanced find_vibing_files to retain partial results when one search succeeds while the other fails. Now emits partial error warnings instead of failing completely when both searches don't yield results.
fd Command Enhancements
lua/vibing/infrastructure/file_finder/fd_command.lua
Added runtime executable detection for fd/fdfind via detect_fd_executable(). Introduced extension extraction from patterns, fallback to regex matching, mtime-based filtering, and dynamic command assembly using discovered executable.
Ripgrep Error Tolerance
lua/vibing/infrastructure/file_finder/ripgrep_command.lua
Exit code 2 (permission denied errors) now treated as non-fatal, returning partial results with warning messages from stderr. Added optional mtime-based filtering of collected files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #358: Modifies the same file-finder and collector code paths, touching collector.find_vibing_files, fd_command, and ripgrep_command functions.
  • PR #355: Modifies lua/vibing/application/daily_summary/collector.lua and find_vibing_files with complementary symlink-safe recursion and search directory handling.
  • PR #388: Modifies collector.lua and affects vibing file discovery/filtering logic alongside mtime-based search refinements.

Poem

🐰 When searches stumble, we don't despair,
Partial results float through the air,
fd and ripgrep learn to bend,
Errors become friends, not the end. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: handling permission denied errors in daily summary search and detecting fdfind on Ubuntu, matching the core objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/daily-summary-permission-denied-and-fd-detection

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@claude
Copy link
Contributor

claude bot commented Feb 19, 2026

コードレビュー

このPRは2つの実際のユーザー体験上の問題を修正しており、全体的な方向性は正しいと思います。以下に詳細なフィードバックをまとめます。


✅ 良い点

  1. ripgrepのexit code 2の扱い: rgのexit codeのセマンティクス(0=一致あり、1=一致なし、2=エラー)を正確に理解した上で、部分的な結果を活かす設計は適切です。
  2. コメントの充実: exit codeの意味をコード内で明示しており、後から読む人にとってわかりやすいです。
  3. fdfind検出の実装: Ubuntuでの fdfdfind フォールバックはシンプルかつ効果的です。
  4. 後方互換性: 既存の動作を壊すことなく修正が加えられています。

🔍 気になる点・改善提案

1. detect_fd_executable() の実行タイミング(パフォーマンス)

-- fd_command.lua
function FdCommand:new(opts)
  local instance = setmetatable({}, self)
  instance.name = 'fd_command'
  instance.mtime_days = opts and opts.mtime_days
  instance.executable = detect_fd_executable()  -- インスタンス生成ごとに which コマンドを実行
  return instance
end

FdCommand:new() が呼ばれるたびに which fd / which fdfind を実行します。VibingDailySummaryAll で複数のディレクトリを処理する場合、毎回 vim.system() が呼ばれることになります。モジュールレベルでのキャッシュを検討する価値があります:

-- モジュールレベルでキャッシュ(一度だけwhichを実行)
local _fd_executable = nil
local function detect_fd_executable()
  if _fd_executable ~= nil then
    return _fd_executable
  end
  for _, name in ipairs({ 'fd', 'fdfind' }) do
    local result = vim.system({ 'which', name }, { text = true }):wait()
    if result.code == 0 then
      _fd_executable = name
      return _fd_executable
    end
  end
  _fd_executable = false  -- nilとfalseを区別してキャッシュ済みであることを示す
  return nil
end

2. collector.lua のエラーメッセージ改善の余地

-- 両方エラーの場合、err1のメッセージのみ表示
vim.notify(
  string.format('vibing.nvim: Failed to search directory %s: %s', directory, err1),
  vim.log.levels.WARN
)

err1err2 が両方存在する場合、err2 の内容が無視されます。両方のエラーが別々の問題(例: *.vibing 検索は権限エラー、*.md 検索はまったく別の問題)の場合、err2 が見えなくなります。軽微な問題ですが、デバッグ時に困る可能性があります。

3. fdfindwhich による検出に関する注意点

which コマンドはPATHにあるコマンドのみを検出します。これはほとんどの場合で問題ありませんが、which 自体が存在しないシステム(非常に稀ですが)では動作しません。現実的な問題ではありませんが、vim.fn.executable() を使う方がNeovimのAPIとして一貫性があります:

local function detect_fd_executable()
  for _, name in ipairs({ 'fd', 'fdfind' }) do
    if vim.fn.executable(name) == 1 then
      return name
    end
  end
  return nil
end

vim.fn.executable() は内部でPATHを検索するため which と同等ですが、vim.system() の呼び出しが不要になり、よりNeovimらしい実装になります。また、既存の supports_platform() の実装パターンとも合わせやすいです。

4. ripgrep_command.luapartial_error 変数のスコープ

軽微ですが、partial_errornil 初期化されて後に条件付きで設定されるパターンは、Lua的には自然です。問題はありません。

5. テストカバレッジ

新機能に対するユニットテストが含まれていません。特に以下のケースのテストがあると品質保証として望ましいです:

  • rgがexit code 2を返す場合に部分的なファイルリストが返ること
  • fdfind がフォールバックとして検出されること
  • 両方の検索が完全に失敗した場合に空配列が返ること

まとめ

実際のユーザーが遭遇した問題(Ubuntuでの fd 未検出、権限エラーでの処理中断)への対応として、修正内容は適切です。特に vim.fn.executable() への変更(提案3)はNeovimのAPIとの一貫性向上と vim.system() 呼び出しの削減を同時に実現できるため、検討をお勧めします。

パフォーマンスのキャッシュ(提案1)も VibingDailySummaryAll で多数のディレクトリを処理するシナリオを考慮すると有益かもしれません。

全体として、このPRはマージに値する内容だと思います。

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

🧹 Nitpick comments (3)
lua/vibing/infrastructure/file_finder/ripgrep_command.lua (2)

30-32: Stale @return annotation — partial_error is returned on partial success, not only on failure.

The comment "only on failure" no longer holds: when exit code 2 occurs and files were found, the function returns both a non-empty files table and a partial_error. Callers relying on this doc annotation (e.g., generated LuaLS type-checks) will have an incorrect expectation.

📝 Proposed annotation update
 ---@return string[] files Array of absolute paths to matched files
----@return string? error Error message (only on failure)
+---@return string? error Error message on failure, or partial warning on exit code 2 (permission denied)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lua/vibing/infrastructure/file_finder/ripgrep_command.lua` around lines 30 -
32, Update the `@return` annotation for RipgrepCommand:find to reflect that it may
return a non-empty files array along with a partial_error string on partial
success (e.g., when exit code 2 occurs), not just on failure; locate the
RipgrepCommand:find function and change the doc comment for the second return to
indicate the error can be returned on partial success or failure (e.g., "string?
partial_error Error message (returned on failure or partial success)").

63-69: "rg command failed:" prefix is misleading for a partial/warning result.

Line 60 and line 68 use the identical "rg command failed: " prefix, but they represent different severity levels — a complete failure vs. a non-fatal partial permission error. A caller that logs the raw message (e.g., for debugging) cannot distinguish the two. Consider a distinct prefix such as "rg partial error: ".

📝 Suggested prefix change
-    partial_error = "rg command failed: " .. first_error
+    partial_error = "rg partial error (permission denied): " .. first_error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lua/vibing/infrastructure/file_finder/ripgrep_command.lua` around lines 63 -
69, The current message for partial/non-fatal rg failures uses the same "rg
command failed:" prefix as fatal failures; update the partial-error construction
in the ripgrep_command.lua block where partial_error is set (when result.code ==
2 and result.stderr) to use a distinct prefix such as "rg partial error:" (or
similar) so callers can distinguish non-fatal permission/partial results from
full failures; keep extracting the first stderr line via
result.stderr:match("([^\n]+)") and only change the prefix text in the string
concatenation that assigns partial_error.
lua/vibing/infrastructure/file_finder/fd_command.lua (1)

10-20: Module-level cache would be beneficial for detect_fd_executable() when custom options are used.

The factory already caches finder instances for default calls, but when get_finder() is called with custom options like mtime_days or strategy (as in collector.lua:219), the factory cache is bypassed. This means FdCommand:new() runs again per call, triggering 1-2 blocking vim.system():wait() calls even though the executable name is invariant for a Neovim session. Similarly, get_available_strategies() creates multiple instances to check support.

A module-level cache would reduce this to at most one detection per session, following the same pattern already used in the factory.

♻️ Proposed module-level cache
+---@type string?
+local _cached_executable = nil
+local _executable_detected = false
+
 ---Detect fd executable name (fd or fdfind on Ubuntu/Debian)
 ---@return string? executable name or nil if not found
 local function detect_fd_executable()
+  if _executable_detected then
+    return _cached_executable
+  end
   for _, name in ipairs({ "fd", "fdfind" }) do
     local result = vim.system({ "which", name }, { text = true }):wait()
     if result.code == 0 then
+      _cached_executable = name
+      _executable_detected = true
       return name
     end
   end
+  _executable_detected = true
   return nil
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lua/vibing/infrastructure/file_finder/fd_command.lua` around lines 10 - 20,
detect_fd_executable() runs blocking vim.system():wait() on every instantiation
(e.g., FdCommand:new(), get_finder() with custom options, and
get_available_strategies()), causing repeated work; add a module-level cache
(e.g., local fd_executable_cache) and have detect_fd_executable return the
cached value if set, otherwise perform the which check once and store the
result; update any callers (FdCommand:new, get_finder, get_available_strategies)
to rely on detect_fd_executable (no other changes) so that only a single
detection happens per Neovim session.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lua/vibing/application/daily_summary/collector.lua`:
- Around line 22-39: The current logic misses emitting a warning when exactly
one search errors but neither returns results; change the partial-warning branch
to notify whenever any error exists (partial_err) instead of requiring
has_results, since the earlier block already handles the case where both
searches failed with no results (err1 and err2 and not has_results) and returns.
Update the condition around the vim.notify call that currently checks
partial_err and has_results to simply check partial_err (ref: err1, err2,
has_results, partial_err in collector.lua) so any single-search error produces a
diagnostic.

---

Nitpick comments:
In `@lua/vibing/infrastructure/file_finder/fd_command.lua`:
- Around line 10-20: detect_fd_executable() runs blocking vim.system():wait() on
every instantiation (e.g., FdCommand:new(), get_finder() with custom options,
and get_available_strategies()), causing repeated work; add a module-level cache
(e.g., local fd_executable_cache) and have detect_fd_executable return the
cached value if set, otherwise perform the which check once and store the
result; update any callers (FdCommand:new, get_finder, get_available_strategies)
to rely on detect_fd_executable (no other changes) so that only a single
detection happens per Neovim session.

In `@lua/vibing/infrastructure/file_finder/ripgrep_command.lua`:
- Around line 30-32: Update the `@return` annotation for RipgrepCommand:find to
reflect that it may return a non-empty files array along with a partial_error
string on partial success (e.g., when exit code 2 occurs), not just on failure;
locate the RipgrepCommand:find function and change the doc comment for the
second return to indicate the error can be returned on partial success or
failure (e.g., "string? partial_error Error message (returned on failure or
partial success)").
- Around line 63-69: The current message for partial/non-fatal rg failures uses
the same "rg command failed:" prefix as fatal failures; update the partial-error
construction in the ripgrep_command.lua block where partial_error is set (when
result.code == 2 and result.stderr) to use a distinct prefix such as "rg partial
error:" (or similar) so callers can distinguish non-fatal permission/partial
results from full failures; keep extracting the first stderr line via
result.stderr:match("([^\n]+)") and only change the prefix text in the string
concatenation that assigns partial_error.

Comment on lines +22 to +39
-- Warn about errors but continue with partial results
-- If both searches completely failed with no results, return empty
local has_results = (vibing_files and #vibing_files > 0) or (md_files and #md_files > 0)
if err1 and err2 and not has_results then
vim.notify(
string.format("vibing.nvim: Failed to search directory %s: %s", directory, err1),
vim.log.levels.WARN
)
return {}
end
-- Warn about partial errors (permission denied on some subdirectories)
local partial_err = err1 or err2
if partial_err and has_results then
vim.notify(
string.format("vibing.nvim: Partial error searching directory %s: %s", directory, partial_err),
vim.log.levels.WARN
)
end
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent failure when exactly one search errors and neither returns results.

The current condition if partial_err and has_results means a warning is only emitted when there are results plus a partial error. When one search completely fails (e.g., err1 = "rg command failed: …") and the other succeeds with zero results (err2 = nil, md_files = {}), all three conditions evaluate as:

variable value
has_results false
err1 and err2 and not has_results false (err2 is nil) → no early abort
partial_err and has_results falseno warning

The function returns {} silently. The user sees "no files found" with no diagnostic, masking a real error in one of the two finders.

🐛 Proposed fix — always warn when any error is present
-  -- Warn about partial errors (permission denied on some subdirectories)
-  local partial_err = err1 or err2
-  if partial_err and has_results then
-    vim.notify(
-      string.format("vibing.nvim: Partial error searching directory %s: %s", directory, partial_err),
-      vim.log.levels.WARN
-    )
-  end
+  -- Warn about partial errors (permission denied on some subdirectories)
+  -- or when one search failed outright while the other returned nothing
+  local partial_err = err1 or err2
+  if partial_err then
+    vim.notify(
+      string.format("vibing.nvim: Partial error searching directory %s: %s", directory, partial_err),
+      vim.log.levels.WARN
+    )
+  end

Note: This makes the early-abort block above (which already notifies and returns {}) the only path that suppresses the partial warning — specifically when both searches fail with no results. All other error states will emit a diagnostic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Warn about errors but continue with partial results
-- If both searches completely failed with no results, return empty
local has_results = (vibing_files and #vibing_files > 0) or (md_files and #md_files > 0)
if err1 and err2 and not has_results then
vim.notify(
string.format("vibing.nvim: Failed to search directory %s: %s", directory, err1),
vim.log.levels.WARN
)
return {}
end
-- Warn about partial errors (permission denied on some subdirectories)
local partial_err = err1 or err2
if partial_err and has_results then
vim.notify(
string.format("vibing.nvim: Partial error searching directory %s: %s", directory, partial_err),
vim.log.levels.WARN
)
end
-- Warn about errors but continue with partial results
-- If both searches completely failed with no results, return empty
local has_results = (vibing_files and `#vibing_files` > 0) or (md_files and `#md_files` > 0)
if err1 and err2 and not has_results then
vim.notify(
string.format("vibing.nvim: Failed to search directory %s: %s", directory, err1),
vim.log.levels.WARN
)
return {}
end
-- Warn about partial errors (permission denied on some subdirectories)
-- or when one search failed outright while the other returned nothing
local partial_err = err1 or err2
if partial_err then
vim.notify(
string.format("vibing.nvim: Partial error searching directory %s: %s", directory, partial_err),
vim.log.levels.WARN
)
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lua/vibing/application/daily_summary/collector.lua` around lines 22 - 39, The
current logic misses emitting a warning when exactly one search errors but
neither returns results; change the partial-warning branch to notify whenever
any error exists (partial_err) instead of requiring has_results, since the
earlier block already handles the case where both searches failed with no
results (err1 and err2 and not has_results) and returns. Update the condition
around the vim.notify call that currently checks partial_err and has_results to
simply check partial_err (ref: err1, err2, has_results, partial_err in
collector.lua) so any single-search error produces a diagnostic.

@shabaraba shabaraba merged commit 5e55778 into main Feb 19, 2026
7 checks passed
@shabaraba shabaraba deleted the fix/daily-summary-permission-denied-and-fd-detection branch February 19, 2026 00:45
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.

1 participant