logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

Once: Cache CLI commands

Original article
github.com|72 points|30 comments|by baquero|Oct 9, 2026

Once: Cache CLI commands

alex0ptr

once is a utility designed to execute a command in your current working directory, output the results to stdout, and store that output in a lightweight, per-user background daemon.

If you call the same command again—provided the directory and tenant key remain the same—the tool retrieves the result from memory instead of re-running the process, until the cache entry expires.

🎯 Primary Use Case

The most common application is retrieving secrets from managers like 1Password. Instead of providing a fingerprint for every single read request, you can cache the token for a few hours.

Example: .zshrc Configuration

# Assign a unique tenant key for the current terminal session
export ONCE_TENANT="${ONCE_TENANT :- $(uuidgen)}"

# Cache a GitHub token for 8 hours, ignoring the directory
export GITHUB_TOKEN="$(once --ttl 8h --no-dir -- op read op://Private/GitHub/token)"

In this scenario, only the first request triggers the fingerprint prompt; subsequent requests within the 8-hour window are instant.

Example: mise Integration

Within a mise.local.toml for a specific project:

[env]
ONCE_TENANT = "5D0F399A-5091-43F1-9C43-7C5A2377D90D" # Context-specific key
TOKEN = "{{ exec(command='once --ttl 12h --no-dir -- op read op://Private/something/token') }}"

🛠 Installation & Requirements

  • Go Version: 1.27+
  • Dependencies: Standard library only.
  • OS Support: macOS and Linux.

Install via Go:

go install github.com/alex0ptr/once@latest

📖 Usage Guide

To see all available options, run once --help, once help, or once -h.

Command Syntax

once --ttl DURATION [--until TIME] [--tenant KEY] [--refresh] [--no-dir] -- COMMAND [ARGS...]

Management Commands

  • once status: Displays daemon status, the number of active entries, and the scheduled shutdown time.
  • once clear: Wipes the current cache.

Option Reference

OptionDescription
--ttlSets the cache duration (e.g., 1h, 30m).
--refreshUses the cache →\rightarrow→ Forces a re-run of the command and updates the cached value.
--no-dirExcludes the current working directory from the cache key calculation.

Technical Nuances

  • Environment: The tool inherits the terminal, working directory, and environment variables from your shell.
  • Shell Features: Because once executes commands directly, you must wrap pipes or globs in a shell call: once --ttl 1h -- sh -c 'op item get "AWS" --format json | jq -r .fields[0].value'
  • I/O Handling: Only stdout is cached. stdin and stderr are passed through in real-time, ensuring interactive prompts still function.
  • Error Handling: If a command returns a non-zero exit code, the result is not cached, and the error code is passed back to the shell.

📂 Directory-Independent Caching

By default, the directory is part of the key because commands like git rev-parse HEAD vary by folder. However, for global commands (like op read), use the --no-dir flag to share one cached value across all directories.

⏰ Absolute Expiration with --until

The --until flag specifies an absolute timestamp. The entry expires at whichever comes first: the --ttl or the --until time.

Supported Formats:

  • 2026-10-09T06:00:00+02:00
  • 2026-10-09T06:00:00+0200
  • 2026-10-09T06:00
  • 2026-10-09 (interpreted as local time)

Examples:

  • Expire at 10 PM today: once --ttl 24h --until "$(date +%F)T22:00" --no-dir -- op read ...
  • Expire tomorrow morning (Linux/GNU): once --ttl 24h --until "$(date -d tomorrow +%F)T06:00" -- op read ...
  • Expire tomorrow morning (macOS/BSD): once --ttl 24h --until "$(date -v+1d +%F)T06:00" -- op read ...

Note: If the --until time has already passed, the command runs normally but is not cached.


🔑 Tenant Keys & Logic

The cache key is generated using the following logic: Key=HMAC-SHA256(tenant,working directory∥command∥args)\text{Key} = \text{HMAC-SHA256}(\text{tenant}, \text{working directory} \parallel \text{command} \parallel \text{args})Key=HMAC-SHA256(tenant,working directory∥command∥args) (The directory is omitted if --no-dir is used).

What is a Tenant Key? It is a namespace, not a security boundary. It is not intended to be secret and can be stored in config files. It serves to:

  1. Separate contexts (e.g., different projects or shell sessions).
  2. Ensure a context with a different key must fetch its own secrets.

Using ${ONCE_TENANT:-$(uuidgen)} ensures every new terminal has a unique tenant, while child processes share the same cache. Warning: Keys passed via --tenant are visible in the process list.


⚙️ How It Works

  1. The client computes the key and queries the daemon via a Unix socket.
  2. On a miss, the client runs the command in the current environment.
  3. Upon success, the client starts the daemon (if not running) and provides the output and expiry.
  4. The daemon shuts down automatically once the final entry expires.

Socket Location: The socket is stored in $XDG_RUNTIME_DIR/once-uid/ (or a temporary directory) with permissions set to 0700 to prevent other users from connecting. The client computes the key locally, so the tenant key is never transmitted to the daemon.

🛡 Security Model

Security is enforced via Unix socket and file system permissions:

  • Runtime Directory: Mode 0700
  • Socket: Mode 0600

While other processes under your own user account can read the tenant key (from environment variables or configs) and thus access the cache, other system users are blocked. Memory is overwritten before being dropped on a best-effort basis.