aux4/watch

Watch a file — or the output of any command — and run something every time it changes.

aux4 watch is for reacting to a difference you cannot name in advance. If you already know the value you are waiting for, aux4/poller expresses that directly:

  • aux4 poll — wait until a deploy reports READY, then continue. You can name the target.
  • aux4 watch — back up the log whenever it grows. You cannot; you only know it will differ.

Installation

aux4 aux4 pkger install aux4/watch

Quick Start

Back up a log file every time it is written to:

aux4 watch file sys.log --onChange "cp sys.log backup/" --times unlimited

Block until a build artifact shows up, then carry on:

aux4 watch file target/report.csv --maxWait 600

React when someone pushes to a remote branch:

aux4 watch command "git ls-remote origin main" --onChange "git pull" --interval 60 --times unlimited

Commands

watch file

Watches a single file. The file does not need to exist yet — whatever state is present at startup becomes the baseline, and startup never triggers anything, so pointing a watch at a missing file makes it fire when the file appears.

aux4 watch file <path> [--onChange <command>] [--onDelete <command>] [--onCreate <command>] [--mode <mtime|content>] [--interval <seconds>] [--debounce <milliseconds>] [--times <count|unlimited>] [--maxWait <seconds>]

| Option | Description | Default | | --- | --- | --- | | path | File to watch, as a positional argument | required | | --onChange | Runs when the file changes or is created | | | --onDelete | Runs when the file is deleted. Never runs onChange | | | --onCreate | Runs when the file is created. Falls back to onChange | | | --mode | mtime (modification time and size) or content (sha256) | mtime | | --interval | Seconds between fallback polls | 2 | | --debounce | Milliseconds of quiet required before reacting | 200 | | --times | How many changes to react to, or unlimited | 1 | | --maxWait | Seconds to wait for the next change before giving up | waits forever |

watch command

Runs a command on an interval and treats its output as a fingerprint. This is the general form — anything you can reduce to a line of output can be watched: HTTP endpoints, git refs, query results, DNS records, package versions.

aux4 watch command <command> [--onChange <command>] [--interval <seconds>] [--times <count|unlimited>] [--maxWait <seconds>]

| Option | Description | Default | | --- | --- | --- | | command | Command whose output is the fingerprint, as a positional argument | required | | --onChange | Runs when the output changes | | | --interval | Seconds between polls | 2 | | --times | How many changes to react to, or unlimited | 1 | | --maxWait | Seconds to wait for the next change before giving up | waits forever |

How Changes Are Detected

watch file uses filesystem events for an immediate reaction and keeps polling underneath as a safety net, because events are unreliable or absent on NFS, SMB shares, and some container bind mounts. What it actually watches is the parent directory filtered to the filename, which is what lets a not-yet-created file be watched at all and what keeps a watch alive across a log rotation that replaces the file.

watch command has no events available to it, so --interval is its only clock. Match it to how fast the thing you are watching really moves — a remote git ref does not need checking every two seconds.

Choosing a mode

mtime compares modification time and size. Size is part of it because modification time has whole-second granularity on some filesystems, so two appends inside the same second would otherwise be invisible. This is the right default for logs.

content compares a sha256 of the file, so a touch that does not change the bytes is not a change. It costs a full read on every check, which is worth it for a config file and wasteful for a large log.

Debouncing

A process writing a file emits a burst of events. --debounce is the quiet period required before reacting, so a burst fires the hook once instead of dozens of times — and a hook that copies the file is far less likely to catch it half-written.

Events

Three transitions are distinguished, and they are not interchangeable:

| Transition | Runs | | --- | --- | | File exists and its fingerprint differs | --onChange | | File appeared where there was none | --onCreate, or --onChange when unset | | File disappeared | --onDelete only |

A deletion never falls through to --onChange, because there is nothing left for the hook to act on — running cp sys.log ... against a missing file would fail by construction.

Only --onChange and --onCreate count towards --times. Deletions do not, so a log rotation does not spend part of the budget on its way to the next real change.

Running It in the Background

An unlimited watch never returns, so run it under aux4/jobs when it should outlive your shell:

aux4 jobs run --command "aux4 watch file sys.log --onChange 'cp sys.log backup/' --times unlimited"

That also gives you --onFailure for alerting if the watch ever exits.

Exit Codes

| Code | Meaning | | --- | --- | | 0 | The --times budget was satisfied | | 124 | No change arrived within --maxWait | | other | The exit code of the hook, passed through unchanged |

124 follows the convention used by timeout, which keeps 1–123 free for your hook's own codes so nothing is ambiguous.

--maxWait bounds the wait for the next change, not the total run. The clock restarts every time a change fires, so --times 10 --maxWait 60 means "give up if a minute passes with nothing happening."

License

This package is licensed under the Apache License, Version 2.0.

See LICENSE for details.