Direct answer
Grunt's watch task does not have a documented cache-invalidation contract, and stale notifications are not a confirmed bug in Grunt core. The behavior you see is almost always caused by the underlying filesystem watcher (Node's fs.watch or fs.watchFile), editor atomic-save patterns, debounce settings, or plugin-level caching—not by a Grunt-version-specific cache bug.
What is actually happening
- Watcher implementation: Grunt delegates to
grunt-contrib-watch (or similar plugins), which in turn uses Node's filesystem APIs. On Linux/macOS fs.watch relies on inotify/kqueue; on Windows it may fall back to polling. Event delivery is not guaranteed to be instant.
- Atomic saves: Many editors write to a temporary file then rename over the target. The watcher may still hold a handle to the old inode, so the rename appears as a
delete + create pair, and the new content is only picked up on the next poll cycle or after a debounce window.
- Debounce and coalescing: The watch task defaults to a 500 ms debounce. Rapid external edits can be merged, making the first notification look delayed.
- Plugin caches: Some plugins snapshot the file list and mtimes at startup. If a task reads from that snapshot instead of re-statting the filesystem, external changes stay invisible until the watch target restarts.
Version differences
Grunt 0.9.x vs 1.x does not introduce a distinct cache-invalidaton contract. The watcher behavior is determined by the watch plugin version and the Node.js version (which ships the fs bindings). Upgrading Grunt alone rarely changes the symptom.
Minimal verification steps
- Identify the watch plugin and its version:
npm ls grunt-contrib-watch
- Run watch with verbose logging and a single target, no livereload, no spawn:
grunt watch:target --verbose --debounce=0
- From another terminal, modify a watched file in two ways and note the timestamp of the first log line that mentions the file:
# Direct append
echo "test" >> watched/file.js
# Atomic replace (editor-like)
echo "test" > watched/file.js.tmp && mv watched/file.js.tmp watched/file.js
- If the direct append is reported promptly but the atomic replace is delayed or missed, the cause is the watcher's handling of rename events, not a Grunt cache bug.
When to suspect a plugin cache
If the file's mtime has changed (verify with stat watched/file.js) but Grunt still processes stale content over multiple watch cycles, inspect the plugin's internal file list. For grunt-contrib-watch the list is rebuilt each cycle; custom plugins may cache it. Restarting the watch target clears such a cache.
One diagnostic detail that changes the recommendation
Which watch plugin and version are you using? If it is a fork or a task-specific wrapper (e.g., grunt-newer, grunt-watch), its caching strategy may differ from grunt-contrib-watch and would require a plugin-specific fix.