Skip to content

Releases: Rayrsn/farhand

v1.9.0 — sync, doctor, metrics, completions

Choose a tag to compare

@github-actions github-actions released this 26 Sep 10:27

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-run connects, 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. --list additionally names every path.
  • fh sync performs 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 9100

13 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/man1

Generated 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.nix exposing fh and fhd plus a dev shell with rust-analyzer, cargo-audit, and cargo-deny, and a default.nix for non-flake consumers.
  • crates.io: the crates are ready to publish under the farhand-* namespace — the name farhand is taken on crates.io by an unrelated project. The executables remain fh and fhd.
  • Homebrew: the formula is current again (see below).

Fixes

  • brew install farhand served 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 --help described sync as "Clean remote project workspaces or caches." The subcommand had been inserted between Clean and its doc comment, so clap attached the wrong description.
  • The Prometheus endpoint emitted duplicate # HELP/# TYPE pairs 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 doctor warned 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:

  • musl target 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 fh and fhd — release.yml only 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.md against what the endpoint actually emits

v1.8.1 — dependency maintenance

Choose a tag to compare

@Rayrsn Rayrsn released this 26 Sep 08:15

Dependency maintenance release — no behavior changes, no new flags.

Dependency updates

  • rcgen 0.13 → 0.14 — the only one that required code. rcgen 0.14 made the
    signing identity explicit: CertificateParams::signed_by now 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 the Issuer directly 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-roots 0.26 → 1.0 — this also collapsed a duplicate that the
    lockfile had been carrying, so the matching deny.toml skip entry is gone:
    the dependency policy is green with one fewer exemption.
  • zstd 0.13 → 0.14, criterion 0.5 → 0.8, clap 4.6.6 → 4.6.7.
  • actions/checkout v4 → v7 and actions/upload-artifact v4 → 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 reading Need where it expected Queued. It now polls the running
    agent's actual lock — the manager is Clone over an Arc, 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 the stable commit
    while the version comments keep claiming 1.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 no CODECOV_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

Choose a tag to compare

@Rayrsn Rayrsn released this 26 Sep 06:43

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

  • fhd now refuses to start without an authentication token unless
    --allow-unauthenticated is 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-env lists 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

  • fh now 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 with STATUS_STACK_OVERFLOW, even
    fh --version. The work now runs on a thread with an explicit stack, and CI
    runs both binaries under ulimit -s 1024 so 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_of needs 1.87. We briefly pinned time to 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 unsafe blocks 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 into active, exec,
    stream, and session modules.
  • fh/src/main.rs: 1,883 → 1,609 lines, with the clap surface in cli.rs,
    RunParams replacing 18 positional arguments, one shared perform_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

  1. fhd now needs a token. Start it with --token <token> or
    FARHAND_TOKEN=<token>, or pass --allow-unauthenticated if you are
    deliberately running tokenless on a trusted loopback interface.
  2. 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

Choose a tag to compare

@github-actions github-actions released this 22 Sep 09:29
Release v1.7.0: Remote Agent Dashboard and Language Server Protocol (…

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 09:30
Release v1.6.0: Native Zero-Config TLS and Declarative Toolchain Mana…

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:13
Farhand v1.5.0 — Automatic Project Init & Template Generation Engine

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:13
Farhand v1.4.0 — Zstandard (zstd) Wire Compression & Global Content-A…

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:13
Farhand v1.3.0 — Preflight Disk Guard, Remote PTY Shell, Ad-Hoc Exec …

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 08:09
Farhand v1.2.0 — Watch Mode, Interactive PTY & Reverse Port Forwarding

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 12:59
Farhand v1.1.0 — Environment Variable Forwarding & Secret Managers