Skip to content

feat: add rn watch entity for file change monitoring#48

Merged
vitali87 merged 3 commits into
mainfrom
feat/rn-watch
Mar 27, 2026
Merged

feat: add rn watch entity for file change monitoring#48
vitali87 merged 3 commits into
mainfrom
feat/rn-watch

Conversation

@vitali87

Copy link
Copy Markdown
Owner

Summary

  • Add u7 rn watch <file> run <command> to watch a file for changes and execute a command when modified
  • Uses inotifywait when available, falls back to a polling loop with stat (1-second interval)
  • Includes file existence check, usage validation, and dry-run support
  • Adds watch to tab completion in completions.sh
  • Adds 5 tests: usage errors, missing file, dry-run output, and help text

Test plan

  • bash test.sh passes all 142 tests (0 failures)
  • Manual test: u7 rn watch somefile run echo changed triggers on file modification
  • Manual test: u7 -n rn watch somefile run echo changed prints dry-run message

@vitali87

Copy link
Copy Markdown
Owner Author

@greptile

@vitali87

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a watch command to the run utility, enabling users to monitor a file for modifications and execute a specified command upon change. The update includes shell completion support and comprehensive unit tests. Review feedback highlights the need for more robust error handling in the watch loops; specifically, the inotifywait and polling implementations should gracefully handle file deletions or access errors to avoid infinite loops or high CPU usage. There is also a recommendation to reconsider the use of eval for command execution to mitigate potential security risks.

Comment thread lib/run.sh Outdated
Comment on lines +105 to +108
while true; do
inotifywait -qq -e modify "$file"
eval "$cmd"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current while true loop can lead to a high-CPU busy-loop if the watched file is deleted or becomes inaccessible. inotifywait will start failing with a non-zero exit code, but the loop will continue, causing it to be called repeatedly without blocking.

A better pattern is to use inotifywait as the condition for the while loop. This way, the loop will automatically terminate when inotifywait returns a non-zero exit code (e.g., on error or file deletion).

Suggested change
while true; do
inotifywait -qq -e modify "$file"
eval "$cmd"
done
while inotifywait -qq -e modify "$file"; do
eval "$cmd"
done
echo "File '$file' is no longer being watched." >&2

Comment thread lib/run.sh
Comment on lines +112 to +120
while true; do
sleep 1
local cur_mod
cur_mod=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null)
if [[ "$cur_mod" != "$last_mod" ]]; then
last_mod="$cur_mod"
eval "$cmd"
fi
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The polling loop does not handle file deletion gracefully. If the file is deleted, stat will fail, cur_mod will be empty, and the command will be executed one last time before the loop goes idle. The loop will then continue polling a non-existent file indefinitely.

The loop should detect when stat fails and exit cleanly, informing the user that the file is no longer accessible.

Suggested change
while true; do
sleep 1
local cur_mod
cur_mod=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null)
if [[ "$cur_mod" != "$last_mod" ]]; then
last_mod="$cur_mod"
eval "$cmd"
fi
done
while true; do
sleep 1
local cur_mod
cur_mod=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null)
if [[ -z "$cur_mod" ]]; then
echo "File '$file' no longer accessible. Stopping watch." >&2
break
fi
if [[ "$cur_mod" != "$last_mod" ]]; then
last_mod="$cur_mod"
eval "$cmd"
fi
done

Comment thread lib/run.sh
if command -v inotifywait &>/dev/null; then
while true; do
inotifywait -qq -e modify "$file"
eval "$cmd"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using eval on command strings can be risky and lead to unexpected behavior due to double evaluation of shell metacharacters. It's generally safer to execute commands in a more controlled environment.

While eval allows executing shell functions from the parent environment, consider if this is a required feature. If not, a safer alternative like bash -c "$cmd" could be used, which runs the command in an isolated subshell. This advice also applies to line 118.

@greptile-apps

greptile-apps Bot commented Mar 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a u7 rn watch <file> run <command> entity that monitors a file for changes and re-executes a command on each modification. It uses inotifywait when available and falls back to a 1-second stat-based polling loop on systems without it. Tab completion, help text, and 5 unit tests are included. Both previously flagged P1 issues (infinite busy-loop on file deletion in the inotifywait path, and spurious command trigger on deletion in the polling path) have been correctly resolved.

  • inotifywait path correctly uses while inotifywait ... so the loop exits naturally when the watched file disappears
  • Polling path guards against an empty stat result and breaks with a message when the file no longer exists
  • One remaining gap: inotifywait -e modify does not fire for editor atomic writes (write-to-temp + rename), whereas the polling path catches those correctly via mtime; adding close_write,create,moved_to events would make both paths behaviorally consistent
  • Tests cover the key non-blocking scenarios (errors, dry-run, help); blocking watch behaviour is noted as manually verified in the test plan

Confidence Score: 5/5

Safe to merge; the only remaining finding is a P2 improvement to the inotifywait event mask

Both previously flagged P1 bugs are fixed. The single remaining comment is a P2 suggestion (missed events on atomic editor saves) — it does not block correctness and there are no P1 or P0 issues outstanding

lib/run.sh lines 105-107 — consider expanding the inotifywait event list

Important Files Changed

Filename Overview
lib/run.sh Adds watch entity with inotifywait/poll fallback; prior deletion-loop and spurious-trigger bugs are fixed, but inotifywait -e modify misses editor atomic-write events that the polling path correctly handles
lib/completions.sh Adds watch to rn entity completions and enables file-path completion for it; straightforward and correct
test.sh Adds 5 tests for usage errors, missing file, dry-run output, and help text; test files are created inside $TEST_DIR and cleaned up correctly

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([u7 rn watch file run cmd]) --> B{Args valid?}
    B -- No --> C[Print usage / return 1]
    B -- Yes --> D{File exists?}
    D -- No --> E[Print error / return 1]
    D -- Yes --> F{Dry-run?}
    F -- Yes --> G[Print dry-run message / return 0]
    F -- No --> H{inotifywait available?}
    H -- Yes --> I[while inotifywait -e modify file]
    I -- modify event --> J[eval cmd]
    J --> I
    I -- exit non-zero --> K([Loop exits])
    H -- No --> L[stat mtime loop, sleep 1s]
    L --> M{File gone?}
    M -- Yes --> N([Break with message])
    M -- No --> O{mtime changed?}
    O -- Yes --> P[eval cmd]
    P --> L
    O -- No --> L
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: lib/run.sh
Line: 105-107

Comment:
**`inotifywait -e modify` misses editor atomic writes**

Many editors (vim, nano, gedit, VS Code by default) use atomic saves: they write to a temp file and then `rename()` it over the original. The kernel fires `IN_MOVED_TO` (and `IN_CREATE`) on the directory, *not* `IN_MODIFY` on the original inode, so `inotifywait -e modify` never wakes up for those saves.

The polling fallback is immune to this (it checks `mtime` via `stat`), so users on Linux with `inotifywait` will see silently missed updates that users on macOS (poll path) would catch — a hard-to-debug behavioural split.

Adding the common safe-write events covers the gap:

```suggestion
        while inotifywait -qq -e modify,close_write,create,moved_to "$file"; do
          eval "$cmd"
        done
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "fix: resolve merge conflict, keep both r..." | Re-trigger Greptile

Comment thread lib/run.sh Outdated
Comment thread lib/run.sh Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a watch command to the u7 run utility, enabling users to monitor a specific file and execute a command whenever changes are detected. The implementation includes shell completion support, a primary execution path using inotifywait, and a polling-based fallback mechanism. Feedback suggests improving security and robustness by replacing eval with bash -c for command execution and removing a redundant touch operation in the test suite.

Comment thread lib/run.sh
if command -v inotifywait &>/dev/null; then
while true; do
inotifywait -qq -e modify "$file"
eval "$cmd"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using eval can introduce security vulnerabilities and unexpected behavior due to its complex parsing rules. A safer and more robust approach is to execute the command in a subshell using bash -c. This contains the execution and avoids altering the current shell's state. This pattern is already used for the priority command in this script.

Suggested change
eval "$cmd"
bash -c "$cmd"

Comment thread lib/run.sh
cur_mod=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null)
if [[ "$cur_mod" != "$last_mod" ]]; then
last_mod="$cur_mod"
eval "$cmd"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

For the same security and robustness reasons mentioned for the inotifywait branch, please replace eval with bash -c here as well.

Suggested change
eval "$cmd"
bash -c "$cmd"

Comment thread test.sh
assert_contains "rn watch shows usage on missing args" "Usage" "$result"

# Test: rn watch usage (missing run keyword)
touch watch_target.txt

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This touch command is unnecessary for this test case. The u7 rn watch command checks for the run keyword before it checks for the file's existence. The test will pass correctly without this line, making it more focused on its purpose: validating the presence of the run keyword. This line can be removed.

@vitali87

Copy link
Copy Markdown
Owner Author

@greptile

@vitali87 vitali87 merged commit 480e3d7 into main Mar 27, 2026
2 checks passed
@vitali87 vitali87 deleted the feat/rn-watch branch March 27, 2026 21:29
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