Skip to Content
User GuideFeaturesFileChanged hooks

FileChanged hooks

Use FileChanged to observe files changed by Qwen Code, shell commands, editors, or other processes. It runs in local, non-managed sessions. See the Hooks guide for hook configuration, executors, and trust rules.

Event input

The input includes the common hook fields and:

{ "file_path": "/home/me/work/demo/.envrc", "event": "change" }

file_path is absolute. event is add, change, or unlink for an observed file addition, modification, or deletion. The common session_id and cwd belong to the listening session; they do not identify the writer. Multiple sessions watching a shared directory can each receive its changes. File content is not included.

Only file events are delivered. Directory-self addDir and unlinkDir events are not delivered. Integrations tracking a watched root’s own lifecycle must check filesystem state (for example with stat) and reconcile independently; a child’s unlink does not prove that the root was removed.

Watch filenames in the working directory

Add a FileChanged hook with literal filenames separated by |:

{ "hooks": { "FileChanged": [ { "matcher": ".envrc|.env", "hooks": [ { "type": "command", "name": "configuration-changed", "command": "node \"$QWEN_PROJECT_DIR/.qwen/hooks/on-file-changed.mjs\"" } ] } ] } }

This registers .envrc and .env in the session’s cwd. Static registration does not expand globs or scan directories recursively. Omitted or empty matchers, *, and .* register no static paths; those hooks receive events from the session’s watched paths, including dynamic paths. Events are filtered against the file’s basename using the existing matcher rules.

Regex patterns filter already watched paths; register their files or directories explicitly through watchPaths.

Static roots accept only bare literal filenames, including dotfiles. Regex/glob metacharacters and path separators do not create static roots. Existing directories and paths that cannot be inspected are excluded; missing files may be watched for creation. Static file discovery observes their parent directory while excluding unrelated entries before traversal. Use explicit watchPaths for recursive directories. Changing the session working directory relocates static roots before relocation finishes; explicit absolute dynamic roots stay unchanged.

Watch directories and paths outside cwd

A successful synchronous SessionStart hook can register absolute files or directories through hookSpecificOutput.watchPaths. Directories are watched recursively, including children created later. Recursive discovery skips .git and node_modules subtrees. To observe a path inside them, register that file or directory explicitly. Special file types are ignored, and ordinary directories have no depth limit. Initial registration waits for watcher readiness before SessionStart returns.

Recursive discovery ignores atomic-write temporary files named <target>.<12 lowercase hex characters>.tmp, including delayed renames and orphans. Explicitly registering such a file still observes it. Directories with this suffix remain traversable, and other .tmp files are observed normally.

For example, register memory folders:

{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "name": "watch-memory-folders", "command": "node \"$QWEN_PROJECT_DIR/.qwen/hooks/watch-memory.mjs\"" } ] } ], "FileChanged": [ { "hooks": [ { "type": "command", "name": "memory-file-changed", "command": "node \"$QWEN_PROJECT_DIR/.qwen/hooks/on-file-changed.mjs\"" } ] } ] } }

Have watch-memory.mjs print this JSON to stdout. Replace the example paths with the actual absolute folders shown by /memory:

{ "hookSpecificOutput": { "hookEventName": "SessionStart", "watchPaths": [ "/home/me/.qwen/projects/demo/memory", "/home/me/.qwen/memories" ] } }

The FileChanged hook has no matcher, so it receives changes throughout those registered directories. Its handler reads the event JSON from stdin. Choose a log or synchronization destination outside the watched paths to avoid triggering the handler with its own writes.

Update dynamic paths

For several workspaces in the same filesystem, register all their actual memory directories in one list. Route notifications using file_path and your own directory-to-workspace mapping, rather than the listening session’s cwd. Independent sessions can observe the same shared root, so deduplicate the work in the business handler. Roots in another container or remote instance need a watcher in that filesystem owner; registering their paths on the host does not observe them.

Successful synchronous SessionStart and FileChanged output can set watchPaths. Include the matching hookEventName in hookSpecificOutput.

OutputEffect on dynamic paths
A list of absolute files or directoriesReplaces the previous dynamic list
[]Clears the dynamic list
Omitted or invalid watchPathsKeeps the previous dynamic list
Output from an async hookDoes not register or change paths

When several hooks return lists, the last valid, explicitly supplied list in hook execution order wins. All entries must be absolute paths; one invalid entry makes the list invalid. Static filename registrations remain active after a dynamic replacement or clear.

To clear dynamic paths from a FileChanged handler, return:

{ "hookSpecificOutput": { "hookEventName": "FileChanged", "watchPaths": [] } }

Observation limits

The initial directory scan is not replayed. Native filesystem events from a recent write may arrive after registration. Explicitly registered symbolic-link files and directory roots observe their targets, including links in ancestor paths. Notifications use the configured alias; registering multiple aliases to one target produces a notification for each alias. Refresh registration after retargeting a link. Symbolic links discovered inside a recursive root are not traversed. Events are reported after writes settle. Watching is best effort: filesystem coalescing, inaccessible paths, process downtime, and path reconfiguration can leave gaps. Integrations that require a complete synchronized copy must handle initial scans, reconciliation, and retry. Watcher or hook failures are logged and leave the originating file operation unchanged.

FileChanged cannot veto or undo a write or add model context. Only explicitly registered paths are watched: memory folders are not registered automatically, and there is no dedicated auto-memory enable/disable event. A hook that writes a watched file can trigger itself; avoid those writes or deduplicate the work.

SSH execution environments, shell sandbox workers, and managed dispatchers do not start a local filesystem watcher. Normal hook enablement and trust rules still apply. Watching stops when its owning session shuts down.

Last updated on