Skip to content

Secrets

The secret command group accesses vault secrets — credentials your code needs at runtime (API keys, tokens) without ever hard-coding them. Secrets are bound to the active conversation or workflow: the user grants access, the CLI surfaces the value to your code, and the grant expires when the conversation ends.

list

List the names of secrets available to the current context. Names only — never values.

hutly secret list

use

Run a command with the secret injected into its environment. This is the preferred way to consume a secret in code — the value is placed in the child process’s environment and never enters the parent process’s stdout.

hutly secret use STRIPE_KEY -- python pay.py

Here the python pay.py process sees STRIPE_KEY=<value> in its environment; nothing prints the value.

Not inside a workflow

secret use resolves the secret through POST /secrets/resolve as the calling principal. In a conversation that principal is the chat, and an unrecognised secret produces an approval prompt for the user.

A workflow has no user to prompt, so that path is deliberately disabled for workflow principals — the call fails with exit 3 (workflow lacks grant for NAME) until someone creates a grant by hand.

Declare the secret on the bash node instead, as a map of env-var name to vault item id:

wf.bash("./scripts/sync.sh", {
  name: "Sync",
  output: z.object({ pages: z.number() }),
  secrets: { STRIPE_KEY: "<vault-item-id>" },
});

The platform resolves each item and exports it before the script’s first line, consulting no grant at all. Declaring is also what makes the secret visible to module activation: a declared secret becomes a hole the installing organisation resolves against its own vault, and the id is rewritten for them at install time. A secret fetched from inside a script string is invisible to that — the module installs cleanly and then fails on its first run.

get

Print a secret’s value to stdout. Human use only.

hutly secret get STRIPE_KEY

In agent mode (HUTLY_AGENT_INVOCATION=1) this is refused with exit code 8 — agents should use secret use instead, so the value never lands in a transcript.

request

Request a new grant for a secret that isn’t yet authorised. This triggers a just-in-time prompt in chat where the user clicks Allow or Deny.

hutly secret request GH_TOKEN --reason "calling the GitHub API to open a PR"

The command exits 0 on Allow, 3 on Deny, and 4 on timeout.

Exit codes

Code Meaning
0 Success
1 Generic failure (KMS decrypt, network)
3 Denied or revoked (403)
4 Timeout (408)
5 Not found in vault (404)
7 Conversation ended (410)
8 Agent-mode refusal for secret get

A note on encoded forms

Base64/hex/gzip-encoded secrets are not scrubbed from output. Don’t transform a secret before printing it — that defeats the protection that keeps raw values out of logs and transcripts.