Releases: Rayrsn/farhand
Release list
v1.9.0 — sync, doctor, metrics, completions
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
v1.8.1 — dependency maintenance
Dependency maintenance release — no behavior changes, no new flags.
Dependency updates
rcgen0.13 → 0.14 — the only one that required code. rcgen 0.14 made the
signing identity explicit:CertificateParams::signed_bynow takes
(public_key, &Issuer)instead of(public_key, &ca_cert, &ca_key), with the
issuer's distinguished name, key identifier, usages, and signing key
travelling together. The mutual-TLS client PKI was migrated to the new model,
building theIssuerdirectly instead of round-tripping the self-signed CA
through PEM. The wire format and the generated certificates are unchanged, and
the mTLS e2e test (client certificate required and verified) passes
unmodified.webpki-roots0.26 → 1.0 — this also collapsed a duplicate that the
lockfile had been carrying, so the matchingdeny.tomlskip entry is gone:
the dependency policy is green with one fewer exemption.zstd0.13 → 0.14,criterion0.5 → 0.8,clap4.6.6 → 4.6.7.actions/checkoutv4 → v7 andactions/upload-artifactv4 → v7, each pinned
SHA verified against its tag before merging.
Fixed
- A concurrency test no longer relies on a 100 ms sleep. The
project-locking test waited a fixed interval for the first client to take the
workspace lock; on Windows that was occasionally not enough, and the test
failed readingNeedwhere it expectedQueued. It now polls the running
agent's actual lock — the manager isCloneover anArc, so the test holds
a handle to it — leaving no timing assumption behind.
Dependabot is now scoped to what CI actually exercises
Three action bumps are ignored, each with its reason recorded:
dtolnay/rust-toolchain— pinned three times for three different
toolchains (stable, the 1.88 MSRV job, nightly). Dependabot models it as one
requirement, so an update collapses all three pins to thestablecommit
while the version comments keep claiming1.88.0/nightly. Merging that
would have compiled the MSRV job with current stable, leaving the gate green
and meaningless.codecov/codecov-action— there is noCODECOV_TOKEN, so uploads are
tokenless, and v5+ changed that path. Because the job sets
fail_ci_if_error: false, an action that quietly stopped uploading would
leave CI green and the coverage badge frozen. Revisit once a token exists.softprops/action-gh-release— runs only in the Release workflow, which
fires on a tag, so no PR check ever exercises it. It will be validated
during a real release, where a failure is loud.
Upgrading is a drop-in replacement: no flags, wire format, or configuration
changed in this release.
v1.8.0 — hardening release
This is the hardening release. Everything in it is about making farhand
trustworthy under a skeptical eye: the security claims are enforced by machines
rather than prose, the unsafe surface is documented and fenced, and the two
monolithic source files are gone.
Security defaults that used to be opt-in
fhdnow refuses to start without an authentication token unless
--allow-unauthenticatedis passed explicitly. Empty tokens are rejected as
a misconfiguration, and non-loopback binds warn loudly.- Token comparison is constant time (SHA-256 digests) across HELLO/RUN,
STATUS, HISTORY, and CLEAN — no length or prefix leakage. - Frame memory now grows only as bytes actually arrive (64 KiB incremental
reads) instead of trusting the length header, with a 1 MiB pre-authentication
cap. A malicious header can no longer induce a large allocation. - New
--max-connections(default 32) and--max-queued-runs(default 16). - Environment forwarding blocks infrastructure credentials (
AWS_,GITHUB_,
NPM_,DATABASE_, …) and credential suffixes (*_TOKEN,*_SECRET,
*_API_KEY, …).fh --print-envlists exactly which names would be sent —
values are never shown.
A real data-corruption fix
Workspace CoW cloning shelled out to cp -c -R and fell back to hardlinks.
A hardlinked file modified in place — a formatter, git checkout, an editor
save — silently corrupted the shared CAS object or seed workspace behind it.
Cloning is now pure Rust (clonefile / FICLONE / copy, preserving modes,
mtimes, and symlinks) and the hardlink fallback is gone entirely.
Correctness and operations
-
fhnow runs on Windows at all. Windows gives the main thread a 1 MiB
stack where Linux gives 8 MiB, and the client needed more than that before
doing any work — it aborted withSTATUS_STACK_OVERFLOW, even
fh --version. The work now runs on a thread with an explicit stack, and CI
runs both binaries underulimit -s 1024so it cannot regress silently. -
CAS garbage collection (
--cas-ttl-days, default 30 /--cas-max-gb):
storage growth was previously unbounded. Eviction is LRU-by-use. -
Collision-free run IDs (were
millis ^ pid, which collided for runs
started in the same millisecond and clobbered STATUS entries). -
Blocking filesystem work moved off the async runtime via
spawn_blocking. -
A queued run whose client disconnects now frees its slot immediately.
CI, MSRV, and receipts
- MSRV corrected from a false 1.75 to 1.88, and now enforced in CI. The old
claim was untrue twice: the dependency graph needs 1.88, and our own
is_multiple_ofneeds 1.87. We briefly pinnedtimeto reach 1.85, then
rejected it because that version carries RUSTSEC-2026-0009 — the
advisory-fixed dependency and the honest floor won. - CI is now a 3-OS matrix plus MSRV, coverage (Codecov), cargo-deny, RustSec
audit, and a fuzz smoke pass; nightly adds ThreadSanitizer, a deep fuzz run,
and benchmark trends. cargo-deny is green with zero exceptions. All
third-party actions are pinned by commit SHA. - Real cargo-fuzz targets (
fuzz_read_frame,fuzz_unpack_tar,
fuzz_wire_paths) asserting protocol invariants, with committed seed corpora
for known attack vectors (traversal tar, absolute-path tar, lying header). - All 11
unsafeblocks carry SAFETY contracts; five crates now
#![forbid(unsafe_code)]outright and CI fails on undocumented unsafe. - Cancellation is now proven, not assumed. The previous disconnect test
dropped the connection, slept 500 ms, and asserted nothing. It now runs a
two-level process tree remotely and requires both the direct child and the
grandchild to die, plus proof the cancelled run released its concurrency
permit. It was validated by mutating the daemon to kill only the direct
child — the test fails with the grandchild surviving, which is precisely the
orphan-process bug the design forbids.
Architecture
fhd/src/lib.rs: 2,209 → 1,232 lines, split intoactive,exec,
stream, andsessionmodules.fh/src/main.rs: 1,883 → 1,609 lines, with the clap surface incli.rs,
RunParamsreplacing 18 positional arguments, one sharedperform_handshake,
and watch mode in its own function.
All refactoring moved code verbatim — no behavior changes — and the per-suite
test counts were verified after every step.
Upgrade notes
fhdnow needs a token. Start it with--token <token>or
FARHAND_TOKEN=<token>, or pass--allow-unauthenticatedif you are
deliberately running tokenless on a trusted loopback interface.- Build with Rust 1.88 or newer (edition 2021).
Full details in CHANGELOG.md.
Assets include archives for Linux (x86_64/aarch64 musl), macOS (x86_64/aarch64),
and Windows, each with a .sha256 checksum.
v1.7.0
Release v1.7.0: Remote Agent Dashboard and Language Server Protocol (…
v1.6.0
Release v1.6.0: Native Zero-Config TLS and Declarative Toolchain Mana…
v1.5.0
Farhand v1.5.0 — Automatic Project Init & Template Generation Engine
v1.4.0
Farhand v1.4.0 — Zstandard (zstd) Wire Compression & Global Content-A…
v1.3.0
Farhand v1.3.0 — Preflight Disk Guard, Remote PTY Shell, Ad-Hoc Exec …
v1.2.0
Farhand v1.2.0 — Watch Mode, Interactive PTY & Reverse Port Forwarding
v1.1.0
Farhand v1.1.0 — Environment Variable Forwarding & Secret Managers