What's new
See exactly what crosses the wire
A build reports its sync as a side effect. These make the transfer the subject:
fh sync --dry-runconnects, exchanges the manifest, and reports what would move — file count, bytes, what the agent already has, and the share of the project that would cross the network — without sending a single file.--listadditionally names every path.fh syncperforms the sync without running a build, warming the workspace for the next run.fh why <path>explains one path: it will be uploaded, it is already on the agent as a content-addressed hit, or it is excluded — naming the rule that excluded it.
fh doctor — one command to tell you what's wrong
Validates configuration, token handling, transport, declared toolchains, and the agent's reachability, disk, queue depth, and load. The probe is deliberately two-phase, because a single failed request cannot distinguish "the daemon is not running" from "the daemon is there and rejected your token" — and those need completely different fixes. Nothing is synced and no run slot is used. Exits 125 when something is actually broken, so it is usable from a script.
Prometheus metrics (opt-in)
fhd --listen 0.0.0.0:9876 --token "$FARHAND_TOKEN" --metrics-port 910013 metric names covering runs, slots, queue depth, active builds per project, disk, load, and memory, plus /healthz for container probes. Binds separately from the agent port so it can be firewalled on its own, and a metrics port that cannot bind logs an error instead of taking the agent down. Hand-rolled over a TcpListener — this is one static text endpoint, and a web framework in the daemon's dependency tree would be a poor trade. See docs/observability.md for the metric reference, alert rules, and an importable Grafana dashboard.
Shell completions and man pages
fh completions bash > /etc/bash_completion.d/fh
fh man --dir /usr/share/man/man1Generated from the same clap definitions the binary uses, so they cannot describe a stale interface. Both need no host, no config, and no network.
Watch mode is configurable
Watch mode now honours each template's ignoreExtra, so a project can declare paths that must not trigger a rebuild. The debounce is tunable with --watch-debounce (default 150 ms) for editors that save in bursts and for network filesystems. The built-in feedback-loop guards stay unconditional on purpose: a negated template pattern cannot un-ignore target/, which would otherwise rebuild on our own output forever.
Progress while the delta is packed
[============ ] 47% 12/25 packing delta 0.3s
Driven by real per-entry callbacks from the packer, not a timer guessing at progress, and rendered only when stdout is a terminal so redirected output and CI logs stay clean.
Packaging
- Nix: a
flake.nixexposingfhandfhdplus a dev shell withrust-analyzer,cargo-audit, andcargo-deny, and adefault.nixfor non-flake consumers. - crates.io: the crates are ready to publish under the
farhand-*namespace — the namefarhandis taken on crates.io by an unrelated project. The executables remainfhandfhd. - Homebrew: the formula is current again (see below).
Fixes
brew install farhandserved v1.7.0 while v1.8.0 and v1.8.1 had shipped. The formula's URLs interpolate its own version, so it pointed at 1.7.0's artifacts with a version string that matched its own checksum — no error, just old binaries, including the Windows stack overflow that made the client crash before printing its version.fh --helpdescribedsyncas "Clean remote project workspaces or caches." The subcommand had been inserted betweenCleanand its doc comment, so clap attached the wrong description.- The Prometheus endpoint emitted duplicate
# HELP/# TYPEpairs for the load-average family — a scrape-time parse error, not a cosmetic one. Label values now escape quotes, backslashes, and newlines, since a project name comes from a directory name and Unix allows newlines in those. fh doctorwarned about unencrypted transport on a loopback target, contradicting its own advice, while a real sync reported "would cross the network" after the bytes had crossed.
Gates added
Several of the bugs above were invisible from a Linux box or from a green build, so each class now has a check that fails when it should:
musltarget and a 1 MiB main-thread stack (Windows parity) in CI- an MSRV gate at the real minimum (1.88)
- shell script syntax, since the install and packaging scripts are how most people install farhand and nothing executed them
- a nightly job that runs the release build command and asserts the executables are still named
fhandfhd—release.ymlonly runs on a tag, so selector drift there would otherwise be invisible until a release was already cut - a nightly job comparing the Homebrew formula against the latest published release
- a test comparing the metric reference in
docs/observability.mdagainst what the endpoint actually emits