· via Hacker News – Front Page (native)
Once: open-source Go CLI caches shell command output in a per-user daemon
Once runs a command, stores its stdout in a per-user daemon and answers repeat calls from memory until expiry; its flagship use is fingerprint-free 1Password secret reads.
A per-user daemon that remembers command output
An open-source tool called Once, published on GitHub by alex0ptr, has surfaced on the Hacker News front page. According to the project's README, Once executes a command in your current directory, prints its stdout and parks the result in memory inside a small per-user background daemon. Repeat calls carrying the same command, working directory and tenant key are answered straight from memory until the entry expires, and when the final entry lapses the daemon exits on its own.
The project is written in Go with no dependencies beyond the standard library. It installs with a single go install command, needs Go 1.27 or newer, runs on macOS and Linux only, and is MIT-licensed.
How the cache is keyed
The basic invocation is once --ttl DURATION -- COMMAND, with several optional switches. The time-to-live flag is required; --until imposes an absolute expiry ceiling, and whichever limit arrives first wins. --refresh ignores the cache, reruns the command and overwrites the stored value. --no-dir leaves the working directory out of the cache key, which matters because a command such as git rev-parse HEAD can produce different output per directory, while an 1Password read does not depend on where you happen to be.
The cache key is an HMAC-SHA256 over the tenant key plus the working directory, command and arguments, with the directory omitted under --no-dir. The tenant key acts as a namespace for separating shells, projects or scripts; the README is explicit that it is not a security boundary and does not need to be kept secret. The client computes the key itself, so the tenant value never travels to the daemon, though a key passed via --tenant does appear in the process list.
Only stdout is cached. Stdin and stderr pass through, so interactive prompts keep working; commands that exit non-zero are never cached and their exit code is forwarded. Everything after the -- separator runs directly without a shell, so pipes and globs have to be wrapped manually. Two subcommands round things out: once status reports the daemon, its entry count and its stop time, while once clear drops every cached value and shuts the daemon down.
The motivating use case
The README's headline scenario is fetching secrets from 1Password without re-approving each read with a fingerprint. A first call such as once --ttl 8h --no-dir -- op read op://Private/GitHub/token triggers the biometric prompt; later calls inside the eight-hour window that share the tenant key skip it. The project's examples show the pattern wired into direnv and mise configuration, exporting tokens into the environment on demand, with per-terminal or per-project tenant keys generated from a UUID.
Security model and limits
Communication happens over a Unix socket in a mode-0700 runtime directory, with the socket itself at mode 0600, which keeps other users from connecting. Processes belonging to the same user are deliberately not excluded: they can reach the socket and read the tenant key, and the README says this is a design decision rather than an oversight. Values sit unencrypted in the daemon's memory, are overwritten before being dropped on a best-effort basis, and are not locked against swapping. Core dumps of the daemon are disabled through RLIMIT_CORE 0 plus PR_SET_DUMPABLE 0 on Linux. Outputs larger than 64 MiB pass through uncached, and expiry is checked against the wall clock on every read, so entries still expire correctly after a laptop wakes from sleep.
Why it matters
Slow or friction-heavy commands that recur throughout a developer's day, such as biometric-gated secret reads or network round-trips, are a real productivity tax, and Once attacks the problem with a deliberately small surface: one dependency-free binary and an in-memory cache that cleans up after itself. Just as notable is the candour of the documentation, which spells out what the tool protects against, what it explicitly does not, and where the hard edges are. For anyone assembling direnv- or mise-driven environments, it offers a pragmatic way to make repeated setup commands effectively free without reshaping the surrounding workflow.
- #cli
- #open-source
- #go
- #caching
- #developer-tools