
I Tried Using fnox's Daemon to Eliminate the Several-Second Wait Every Time mise Loads 1Password Environment Variables
This page has been translated by machine translation. View original
I'm Yokoyama from the Data Business Division.
I usually manage my development environment tools and environment variables with mise.
It's very convenient because it automatically sets up tools and environment variables for each working directory, but mise re-evaluates environment variables every time you perform an operation in the terminal.
One day, I started having to wait about 3 seconds for the prompt to come back every time I pressed Enter in the terminal.
When I investigated, the cause was a mise update that changed the behavior so that "values with redact = true are not cached".
So I tried eliminating the slowness by putting in the daemon of "fnox", a secret management tool made by the author of mise, and I'll share the steps here.
Conclusion
- Starting with mise v2026.10.3, environment variables that include a value with
redact = trueare no longer stored in env_cache (the cache of the computed result of[env])- As a result,
op readwas called at every prompt, and I was kept waiting about 3 seconds each time
- As a result,
- With
hook_env.chpwd_only = true, I made mise recompute environment variables only when Icd - I replaced
op readwithfnox get, and had the fnox daemon hold the values in memory- From the second retrieval onward, values come back in about 0.01 seconds
When I measured it, it changed like this.
| Operation | Before | After |
|---|---|---|
| From Enter until the prompt returns | About 3 seconds | Around 0.01 seconds |
When doing cd into a working directory |
About 3 seconds | 0.07–0.08 seconds |
| Fetching the first value after starting the PC | About 3 seconds | About 3 seconds |
The final configuration consists of the following three files.
The contents of each setting are just examples, but I'll explain the details later.
~/.config/mise/config.toml (user-root configuration)
[settings]
# Run hook-env only on cd, stopping the recomputation on every Enter
hook_env.chpwd_only = true
[tools]
# 1.43.0 or later is required for reading from the daemon
fnox = "1.43.0"
[env]
# exec() runs with the PATH from before mise is activated, so call fnox with an absolute path
CLIENT_ID = { value = "{{ exec(command='~/.local/share/mise/installs/fnox/latest/fnox get CLIENT_ID') }}", redact = true }
CLIENT_SECRET = { value = "{{ exec(command='~/.local/share/mise/installs/fnox/latest/fnox get CLIENT_SECRET') }}", redact = true }
~/.config/fnox/config.toml (user-root configuration)
[daemon]
enabled = true
[providers.op]
type = "1password"
vault = "Private"
[providers.keychain]
type = "keychain"
service = "my-oauth"
[secrets]
# Write only the name at the source, not the value itself
# For 1Password, it is "item name/field name"
API_TOKEN = { provider = "op", value = "API Token/token" }
# For the keychain, it is the account name
CLIENT_ID = { provider = "keychain", value = "client-id" }
CLIENT_SECRET = { provider = "keychain", value = "client-secret" }
~/{project name}/.mise.toml (configuration for each working directory)
[env]
API_TOKEN = { value = "{{ exec(command='~/.local/share/mise/installs/fnox/latest/fnox get API_TOKEN') }}", redact = true }
From here, I'll go through what was happening and how I actually fixed it, in order.
What is fnox
| Item | Details |
|---|---|
| Repository | jdx/fnox |
| Author | jdx (the author of mise) |
| License | MIT |
| Version | 1.43.0 (as of October 2026) |
| Official documentation | fnox.jdx.dev |
fnox is a CLI where you gather the "sources" of secret values in a configuration file and retrieve them with fnox get or fnox exec.
Sources you can use include 1Password, the macOS keychain, and AWS Secrets Manager.
What I use this time is the daemon feature.
If you set [daemon] enabled = true, the daemon starts in the background when you run fnox get and keeps the resolved values in memory.
It doesn't write to disk, so it's nice that plaintext values don't remain in files.
Prerequisites
The environment in which I verified this is as follows.
fnox is installed through [tools] in mise's global configuration, so no separate installation is needed.
| Item | Version |
|---|---|
| macOS | 26.6.2 |
| mise | 2026.10.18 |
| fnox | 1.43.0 |
1Password CLI (op) |
2.30.3 |
| zsh | 5.9 |
The 1Password CLI is assumed to be integrated with the 1Password desktop app (in Settings, "Developer" → "Integrate with 1Password CLI").
What was happening
The original setup
Before the change, I had written the following in [env] of .mise.toml.
~/{project name}/.mise.toml (configuration for each working directory, before the change)
[env]
API_TOKEN = { value = "{{ exec(command='op read \"op://Private/API Token/token\"') }}", redact = true }
exec() is a mise template function that lets you use a command's output as the value of an environment variable.
redact = true is a specification for hiding the value in the output of things like mise env.
A single call of op read takes several seconds, so I had enabled mise's env_cache.
~/.config/mise/config.toml (user-root configuration, before the change)
[settings]
# Encrypt the computed result of [env] and cache it on disk
env_cache = true
With this, op read was called only when the cache expired (every hour if nothing is specified), and I normally never had to wait.
Being made to wait on every Enter
After updating mise, I started having to wait about 3 seconds every time I pressed Enter.
When I measured the time of mise hook-env, it took about 3 seconds every time only in the working directories that had API_TOKEN.
mise calls mise hook-env every time before zsh shows the prompt (precmd), so if this is slow, you get made to wait on every Enter.
When I looked at the env_cache cache file, its last modification had stopped at the time I updated mise.
Since the cache wasn't being written, it seems op read was being called every time.
Cause and countermeasures
When I traced back through mise's releases, I found that PR #13950 in v2026.10.3 (published on October 5, 2026) included the following changes.
- Environment variables that include even one
redact = truevalue are not stored in env_cache - Things that hold shell state, such as
__MISE_DIFF, now keep a hash instead of the plaintext of secret values
As countermeasures, I considered the following.
| Idea | Result |
|---|---|
hook_env.chpwd_only = true |
Recomputation on every Enter stops, but waiting about 3 seconds on every cd remains |
Change the contents of exec() to fnox get and have the daemon hold the values |
From the second time onward, it returns in 0.01 seconds |
So I decided to combine the two: hook_env.chpwd_only and the fnox daemon.
Steps
1. Enable hook_env.chpwd_only
First, stop the recomputation on every Enter.
Add one line to [settings] in mise's global configuration.
~/.config/mise/config.toml (user-root configuration)
[settings]
hook_env.chpwd_only = true
With this, mise hook-env runs only when you cd, and the precmd processing took around 0.01 seconds.
However, edits you make to .mise.toml aren't reflected until the next time you cd.
After editing, running cd . is the surest way.
At this point the wait on Enter was gone, but waiting about 3 seconds each time you cd into a working directory remained as it was.
I'll resolve the rest with fnox.
2. Install fnox
Add fnox to [tools] in mise's global configuration.
~/.config/mise/config.toml (user-root configuration)
[tools]
fnox = "1.43.0"
Reading from the daemon requires 1.43.0 or later.
mise install
3. Create fnox's configuration file
In fnox's global configuration (~/.config/fnox/config.toml), write the daemon settings and the sources of the secret values.
~/.config/fnox/config.toml (user-root configuration)
[daemon]
enabled = true
[providers.op]
type = "1password"
vault = "Private"
[secrets]
API_TOKEN = { provider = "op", value = "API Token/token" }
The value in [secrets] is the 1Password "item name/field name".
Since the value itself isn't written, there's no problem managing this file as part of your dotfiles.
Once it's set up, let's check whether the value can be retrieved.
To avoid displaying the value on screen, I only check the character count.
fnox get API_TOKEN | wc -c
# The first time, a 1Password fingerprint prompt appears and it takes a few seconds
fnox daemon status
# fnox daemon running
# pid: 1238
# cached_entries: 1
fnox can also read from the keychain.
If you use values stored in the keychain, add the keychain to [providers] and write as follows.
~/.config/fnox/config.toml (user-root configuration)
[providers.keychain]
type = "keychain"
# Keychain service name (corresponds to -s of the security command)
service = "my-oauth"
[secrets]
# value is the account name (corresponds to -a of the security command)
CLIENT_ID = { provider = "keychain", value = "client-id" }
CLIENT_SECRET = { provider = "keychain", value = "client-secret" }
4. Replace op read in .mise.toml with fnox get
Replace the contents of exec() in .mise.toml from op read to fnox get.
~/{project name}/.mise.toml (configuration for each working directory)
[env]
-API_TOKEN = { value = "{{ exec(command='op read \"op://Private/API Token/token\"') }}", redact = true }
+API_TOKEN = { value = "{{ exec(command='~/.local/share/mise/installs/fnox/latest/fnox get API_TOKEN') }}", redact = true }
I got stuck a little here.
At first I wrote fnox get API_TOKEN, but I got fnox: command not found.
It seems exec() runs while mise is still assembling the environment, so it's executed before the PATH to the tools installed by mise is set.
So I call the latest link in the installation destination with an absolute path.
I've left redact = true as is.
It still doesn't get stored in env_cache, but each retrieval returns from the daemon in 0.01 seconds, so it no longer bothers me.
Troubleshooting
fnox: command not found inside exec()
exec() runs before the PATH to the tools installed by mise is set.
fnox alone can't be found, so please call it with an absolute path, like ~/.local/share/mise/installs/fnox/latest/fnox.
I changed the value in 1Password but the old value remains
The daemon doesn't detect changes on the 1Password side.
After changing a value, please fetch it again with either of the following.
# Re-fetch just one
fnox get --refresh API_TOKEN
# Clear the entire daemon cache
fnox daemon clear
The daemon runs under launchd and doesn't stop even if you close the terminal.
So simply reopening the terminal will leave you with the old value.
Editing .mise.toml isn't reflected
Because I set hook_env.chpwd_only = true, [env] isn't recomputed until you cd.
Running cd . will reflect it.
Conclusion
I tried resolving the problem of being made to wait several seconds each time mise loads 1Password environment variables, using hook_env.chpwd_only and the fnox daemon.
Since the cause was an intentional specification change in mise, "values with redact = true are not cached", it seemed there was no way to restore the old behavior with mise-side settings alone.
Because the fnox daemon holds values only in memory, it can address this without leaving plaintext on disk, so I think it was a good fit as a countermeasure.
A side benefit was that authentication dialogs stopped appearing over and over.
I'm one of them, but I imagine many people launch multiple terminals with a terminal multiplexer or similar when working.
When I was fetching directly from exec(), each terminal queried the keychain or 1Password, so authentication dialogs appeared as many times as there were terminals.
By putting the fnox daemon in between, terminals opened later can also receive the value fetched the first time from the daemon.
Once you authenticate, you're no longer asked in terminals opened afterward, and the hassle of opening lots of terminals at once has been greatly reduced.
I hope this article is helpful to someone.