Commands
Overview
hush <command> [options]Hush keeps secrets encrypted at rest. The primary way to use secrets is hush run -- <command>, which decrypts to memory and injects environment variables—secrets never touch the disk.
Command Categories
| Category | Commands | Description |
|---|---|---|
| Primary (AI-Safe) | run, set, edit, inspect, has | Safe for AI assistants—never expose secret values |
| Setup | init, encrypt, status, skill | Configuration and encryption |
| Deployment | push, check | CI/CD and cloud deployment |
| Debugging | resolve, trace | Debug secret filtering and routing |
Global Options
| Option | Description |
|---|---|
-e, --env <env> | Environment: development or production (default: development) |
-r, --root <dir> | Root directory (default: current directory) |
-h, --help | Show help message |
-v, --version | Show version number |
run
Run a command with secrets injected as environment variables. This is the primary way to use secrets. Secrets are decrypted to memory only—they never touch the disk.
# Run with development secrets (default)hush run -- npm start
# Run with production secretshush run -e production -- npm buildhush run -e prod -- npm build
# Run with secrets filtered for a specific targethush run -t api -- wrangler devhush run --target web -- npm startOptions
| Option | Description |
|---|---|
-e, --env <env> | Environment: development or production |
-t, --target <name> | Filter secrets for a specific target from hush.yaml |
-- <command> | The command to run (everything after --) |
How It Works
- Decrypts secrets to memory (never written to disk)
- Merges shared → environment → local secrets
- Resolves subdirectory templates (if present)
- Filters root secrets based on target config (if matched)
- Merges template vars + filtered root secrets (additive)
- Spawns the child process with secrets as environment variables
- Inherits stdin/stdout/stderr for full interactivity
Examples
# Local developmenthush run -- npm run dev
# Production buildhush run -e prod -- npm run build
# Wrangler with API secrets onlyhush run -t api -- wrangler dev
# Docker with secretshush run -- docker compose up
# Any command that needs secretshush run -- node scripts/migrate.jsset
Set a secret. Supports inline values, interactive prompts, or piped input.
# Inline value (recommended for AI agents)hush set DATABASE_URL "postgres://user:pass@host/db"hush set STRIPE_KEY "sk_live_xxx" -e production
# Interactive prompt (for users)hush set DATABASE_URLhush set API_KEY -e production
# Set in local overrides (.hush.local.encrypted)hush set MY_OVERRIDE --local
# Piped input (for scripts/automation)echo "my-secret" | hush set MY_KEYcat cert.pem | hush set CERTIFICATEOptions
| Option | Description |
|---|---|
-e, --env <env> | Target environment file (default: shared .hush.encrypted) |
--local | Set in .hush.local.encrypted (personal overrides, not committed) |
--gui | Use GUI dialog for input (shows value for verification) |
Input Methods (Priority Order)
- Inline value:
hush set KEY "value"- value provided directly - Piped input:
echo "value" | hush set KEY- reads from stdin - GUI dialog: Opens when
--guiflag used or no TTY available - Interactive prompt: Terminal prompt with visible input
How It Works
- Gets value from inline arg, pipe, GUI, or prompt
- Decrypts the target file
- Sets or updates the key
- Re-encrypts the file
- Confirms success with character count (no value shown)
Example Output
$ hush set DATABASE_URL "postgres://localhost/mydb"✓ DATABASE_URL set in .hush.encrypted (25 chars)
$ hush set API_KEYEnter value for API_KEY: sk_test_xxx✓ API_KEY set in .hush.encrypted (14 chars)edit
Edit secrets in your $EDITOR. Opens the decrypted file, and re-encrypts on save.
# Edit shared secretshush edit
# Edit environment-specific secretshush edit developmenthush edit productionhush edit devhush edit prod
# Edit local overrideshush edit localHow It Works
- Decrypts the target file to a temporary location
- Opens in
$EDITOR(vim, nano, code —wait, etc.) - Re-encrypts when you save and close
- Deletes the temporary file
init
Generate a hush.yaml configuration file with auto-detected targets.
hush initThis command scans your monorepo for packages with package.json files and creates an initial configuration.
Example Output
# hush.yaml (generated)sources: shared: .hush development: .hush.development production: .hush.production
targets: - name: root path: . format: dotenv - name: app path: ./packages/app format: dotenv - name: api path: ./packages/api format: wranglerencrypt
Encrypt source .hush files to .hush.encrypted files.
hush encryptWhat Gets Encrypted
Based on your hush.yaml sources configuration:
.hush→.hush.encrypted.hush.development→.hush.development.encrypted.hush.production→.hush.production.encrypted
After encryption, the plaintext .hush files are automatically deleted.
inspect
List all variables with masked values. Safe for AI agents.
hush inspecthush inspect -e productionExample Output
Secrets for development:
DATABASE_URL = post****************... (45 chars) STRIPE_SECRET_KEY = sk_t****************... (32 chars) API_KEY = (not set)
Total: 3 variables
Target distribution:
root (.) - 3 vars app (./app/) - 1 vars include: EXPO_PUBLIC_* api (./api/) - 2 vars exclude: EXPO_PUBLIC_*This lets AI agents reason about your configuration without seeing actual secrets.
has
Check if a specific secret exists. Returns exit code 0 if set, 1 if not.
# Check if a variable is sethush has DATABASE_URL
# Quiet mode (no output, just exit code)hush has API_KEY -q
# Use in scriptshush has DATABASE_URL -q && echo "DB configured"Options
| Option | Description |
|---|---|
-q, --quiet | Suppress output, only return exit code |
Example Output
DATABASE_URL is set (45 chars)Or if not set:
DATABASE_URL not foundpush
Push production secrets to Cloudflare (Workers and Pages).
# Push all configured targetshush push
# Push a specific targethush push -t apihush push -t app
# Preview without pushinghush push --dry-run
# Detailed preview showing each variablehush push --dry-run --verbosehush push -t app --dry-run --verboseOptions
| Option | Description |
|---|---|
-t, --target <name> | Push only the specified target |
--dry-run | Preview what would be pushed without making changes |
--verbose | Show detailed output (with --dry-run) |
Supported Destinations
| Target Type | Configuration | Wrangler Command |
|---|---|---|
| Cloudflare Workers | format: wrangler | wrangler secret put |
| Cloudflare Pages | push_to: { type: cloudflare-pages, project: ... } | wrangler pages secret bulk |
Configuration Examples
Cloudflare Workers (automatic with format: wrangler):
targets: - name: api path: ./api format: wranglerCloudflare Pages (requires push_to):
targets: - name: app path: ./app format: dotenv include: - NEXT_PUBLIC_* push_to: type: cloudflare-pages project: my-pages-project # Your Pages project nameHow It Works
- Decrypts production secrets from encrypted files
- Resolves subdirectory templates (if present)
- Filters variables per target using
include/excludepatterns - For Workers: Uploads each secret using
wrangler secret put - For Pages: Uploads all secrets at once using
wrangler pages secret bulk
status
Show configuration and file status. This is the first command to run when troubleshooting.
hush statusExample Output
Hush Status
Config: hush.yaml Project: myorg/myrepo
Prerequisites: SOPS installed age key configured
Key Status: Local key: ~/.config/sops/age/keys/myorg-myrepo.txt 1Password backup: synced
Source Files: .hush.encrypted .hush.development.encrypted .hush.production.encrypted .hush.local (optional, not found)
Targets: root ./ dotenv -> .env.development app ./packages/app dotenv -> .env.development api ./packages/api wrangler -> .dev.varsTroubleshooting with Status
| You See | Meaning | Fix |
|---|---|---|
SOPS not installed | Missing prerequisite | brew install sops |
age not installed | Missing prerequisite | brew install age |
age key not found | No local key | npx hush keys setup |
age key configured but commands fail | direnv not loaded | direnv allow |
1Password backup: not found | Key not in 1Password | npx hush keys push |
check
Verify encrypted files are in sync with source files. Useful for pre-commit hooks.
# Basic checkhush check
# Warn but don't failhush check --warn
# JSON output for CIhush check --json
# Only check git-modified fileshush check --only-changedOptions
| Option | Description |
|---|---|
--warn | Warn on drift but exit 0 |
--json | Output machine-readable JSON |
--quiet | Suppress output |
--only-changed | Only check files modified in git |
--require-source | Fail if source file is missing |
Exit Codes
| Code | Meaning |
|---|---|
0 | All in sync |
1 | Drift detected (run hush encrypt) |
2 | Config error |
3 | Runtime error (sops missing, decrypt failed) |
Pre-commit Hook
npx hush check || exit 1Bypass with: HUSH_SKIP_CHECK=1 git commit -m "message"
resolve
Show what variables a specific target will receive, with filtering details. Essential for debugging why a variable is missing from a target.
# Check what variables api-workers receiveshush resolve api-workers
# Check with production environmenthush resolve api-workers -e productionOptions
| Option | Description |
|---|---|
-e, --env <env> | Environment: development or production |
Example Output
Target: api-workersPath: ./api/Format: wrangler (.dev.vars)Environment: development
✅ ROOT SECRETS (Matched Filters) (11): SUPABASE_URL (source: .env.development) STRIPE_SECRET_KEY (source: .env) R2_ACCESS_KEY_ID (source: .env) ...
🚫 EXCLUDED VARIABLES (8): EXPO_PUBLIC_API_URL (matches: EXPO_PUBLIC_*) FASTLANE_APPLE_ID (matches: FASTLANE_*) ASC_KEY_ID (matches: ASC_*) ...
📄 TEMPLATE EXPANSIONS (api/.hush): DATABASE_URL ← ${DATABASE_URL} PORT ← ${PORT:-8787}
📦 FINAL INJECTION (13 total): DATABASE_URL (template overrides root) PORT (template) SUPABASE_URL (root) ...trace
Trace a specific variable through all sources and targets. Use this to understand why a variable appears in some places but not others.
# Trace a variablehush trace DATABASE_URL
# Trace with production environmenthush trace STRIPE_SECRET_KEY -e productionOptions
| Option | Description |
|---|---|
-e, --env <env> | Environment: development or production |
Example Output
Tracing variable: SUPABASE_URL
Source Status: .env : ❌ Not found .env.development : ✅ Present .env.production : ✅ Present .env.local : (file not found)
Target Disposition (Environment: development): [root] : ✅ Included [app] : 🚫 Not included (not in include: EXPO_PUBLIC_*) [api] : ✅ Included [api-workers] : ✅ Included [landing] : 🚫 Not included (not in include: EXPO_PUBLIC_SUPABASE_*)skill
Install the Claude Code / OpenCode skill for AI-safe secrets management.
# Interactive: choose global or localhush skill
# Install globally (all projects)hush skill --global
# Install locally (this project only)hush skill --localOptions
| Option | Description |
|---|---|
--global | Install to ~/.claude/skills/ |
--local | Install to ./.claude/skills/ |
Global vs Local
- Global - Works across all your projects. Recommended for personal use.
- Local - Bundled with the project. Recommended for teams (commit
.claude/to git).
decrypt —force (Last Resort)
Write decrypted secrets to disk as plaintext files. Requires --force flag and interactive confirmation.
hush decrypt --forcehush decrypt --force -e productionWhy This Exists
Some edge cases genuinely require plaintext files on disk:
- Docker builds that can’t use
hush run - Legacy tooling that reads
.envfiles directly - CI systems without TTY support for
hush run
Safety Features
- Requires
--forceflag - Won’t run without explicit opt-in - Interactive confirmation - Must type “yes” to proceed
- Blocks non-TTY - Cannot be run by AI agents or in scripts
How It Works
- Decrypts encrypted source files
- Merges shared → environment → local overrides
- Interpolates variable references (
${VAR}) - Filters variables per target using
include/excludepatterns - Writes plaintext files to each target path
list (Caution)
List all variables with their actual values.
hush listhush list -e productionTroubleshooting
”no identity matched any of the recipients”
This error means SOPS cannot find your decryption key.
Most common cause: direnv not loaded.
Hush uses per-project keys at ~/.config/sops/age/keys/{project}.txt. SOPS needs the SOPS_AGE_KEY_FILE environment variable to locate them.
# 1. Verify direnv is installed and hookedbrew install direnvecho 'eval "$(direnv hook zsh)"' >> ~/.zshrc # or bashsource ~/.zshrc
# 2. Allow direnv in the projectcd /path/to/projectdirenv allow
# 3. Verify the env var is setecho $SOPS_AGE_KEY_FILE# Should output: /Users/you/.config/sops/age/keys/project-name.txt
# 4. Testhush statushush inspect”age key not found”
The key file doesn’t exist at the expected location.
# Check where hush expects the keyhush status # Look at "Local key:" line
# Option 1: Pull from 1Passwordhush keys setup
# Option 2: Get from team member and save manually# Save to the path shown in hush statusKey exists but wrong project
If you have a key but it’s for a different project:
# List all local keyshush keys list
# Pull the correct keyhush keys setup“SOPS is not installed”
brew install sops age # macOS# See Getting Started for Linux/Windowsdirenv not loading automatically
Make sure you’ve added the direnv hook to your shell:
# For zsh (~/.zshrc)eval "$(direnv hook zsh)"
# For bash (~/.bashrc)eval "$(direnv hook bash)"Then reload your shell or open a new terminal.