Voxa is configured mostly through environment variables, with no CLI flags: everything on this page except the interrupt policy table below is read from the process environment, which is populated (in order) from a .env file in the current directory, then from ~/.voxa/.env on top of it as overrides.
In hosted (zero-config) mode, every variable in this page is optional. The launcher generates and persists whatever it needs (an auth token, a pairing code, the relay URLs) the first time you run voxa, so a bare voxa with no .env at all works out of the box. You only need to set variables explicitly when you want to change the default behavior, such as self-hosting with your own Gemini key.
Core
| Name | Default | What it does |
|---|
VOXA_PORT | 8787 | The port the local server binds to. Also used for the tunnel target and health checks. |
VOXA_HOST | 127.0.0.1 | Read by the server config, but the voxa launcher hardcodes uvicorn to bind 127.0.0.1 regardless of this value. The server never listens on your LAN by default. |
VOXA_AUTH_TOKEN | auto-generated, persisted to ~/.voxa/auth_token | The shared secret the phone must present on its WebSocket connection. Required; the launcher creates one for you if it's unset. |
VOXA_MODE | attach | Which Claude controller drives sessions: attach runs a visible, interactive Claude session in tmux; drive runs a headless session through the Claude Agent SDK with a read-only watch log. |
Transport
| Name | Default | What it does |
|---|
VOXA_RELAY_URL | unset (Tailscale preferred) or https://api.voxa.space | The relay your laptop dials out to for pairing. When set, the relay dial-out transport is used instead of Tailscale or a tunnel. Point this at your own server_cloud deployment for a self-hosted relay. |
VOXA_LIVE_PROXY | unset, or wss://api.voxa.space/live in hosted mode | The metered Gemini Live proxy URL. When set, the laptop uses RemoteOperator (voice routed through the proxy) instead of talking to Google directly. |
VOXA_RELAY_CODE | auto-generated, persisted to ~/.voxa/relay_code | The pairing code that matches your phone to your laptop through the relay. |
VOXA_RELAY_TOKEN | unset | An additional auth token passed to the relay bridge client, for relay deployments that require one. |
VOXA_FORCE_RELAY | unset (false) | Forces the hosted-relay default even when Tailscale is available and would otherwise be preferred. |
Voice
| Name | Default | What it does |
|---|
GEMINI_API_KEY | "" | Your Google AI Studio key for Gemini Live. Required only in self-host / direct mode (when VOXA_LIVE_PROXY is unset); optional in hosted mode, where the cloud proxy uses its own key. |
GEMINI_LIVE_MODEL | gemini-2.0-flash-live-001 | The Gemini Live model id used by the laptop's direct operator. Override if you have access to a newer model. |
VOXA_BARGE_IN | 1 (on) | Whether you can interrupt the operator mid-sentence using Gemini's voice activity detection. Set to 0 to restore half-duplex behavior (wait your turn). |
VOXA_IDLE_TIMEOUT | 180 | Seconds of no speech or work before the voice session auto-disconnects, so an idle call doesn't keep burning minutes. |
Calls and rings
| Name | Default | What it does |
|---|
VOXA_INSTALL_HOOK | 1 (on) | Whether voxa installs the global Claude Code hook on launch. Set to 0 or false to skip it. |
VOXA_INTERRUPT_CONFIG_FILE | ~/.voxa/interrupt_policy.json | Where the per-project interrupt policy (the mute switch and the rest of the ladder's tuning knobs) is stored. Override to point at a different file, mainly useful for tests or a custom install layout. |
VOXA_DECISION_LOG_FILE | ~/.voxa/hooks.jsonl | Where Voxa appends every hook event and every interrupt decision it makes. Read it to see the reason and the human-readable why behind a ring, a notify, or a silent drop. |
VOXA_NOTIFY_RULES_FILE | ~/.voxa/notify_rules.json | The legacy notification-rules file from before the interrupt ladder existed. Only ever read once, to migrate a pre-existing file into interrupt_policy.json the first time Voxa starts; new installs never write to it. |
VOXA_RING_DEBOUNCE_SECONDS | 6 | Collapses rings that arrive within this window of each other into a single call, since the finish hook and the screen scraper can both report the same finish. |
VOXA_APPROVAL_SCRAPE_TIMEOUT | 2.0 | Max seconds to wait for a pane scrape (building the approval card) before sending the blocked push without it. A human is already blocking, so this caps how long they wait. |
VOXA_SCRAPER_GRACE_SECONDS | 10 | Grace period used by the screen scraper when confirming a finish. |
Interrupt policy
Unlike everything else on this page, these are not environment variables. They live in the JSON file pointed at by VOXA_INTERRUPT_CONFIG_FILE (~/.voxa/interrupt_policy.json by default), keyed per project (by cwd) plus a reserved __default__ section: edit the file directly to change them. See call flow for how they fit into the ring / notify / silent ladder.
Anything Voxa cannot parse here falls back to the built-in default rather than failing, so a typo costs you that one setting and nothing else.
Silencing a project
mute_finish and mute_blocked are the mute switch, and they are what the phone's own per-project mute toggle writes. Both demote ring to notify: you lose the call, never the information, so a muted project is still queued and still shows up when you next answer.
| Key | Default | What it does |
|---|
mute_finish | false | Set true to stop this project calling you when a turn finishes. Covers a clean finish and a failing build alike (a broken build is still a finish outcome). |
mute_blocked | false | Set true to stop this project calling you when Claude is blocked waiting on you, including the escalation that would otherwise ring a frozen agent after block_escalate_seconds. Mute wins over that escalation outright. |
Mute the whole machine by putting either key in the __default__ section, or one project by putting it under that project's cwd:
{
"__default__": { "mute_finish": false },
"/Users/you/code/noisy-project": { "mute_finish": true, "mute_blocked": true }
}
The rest of the ladder
| Key | Default | What it does |
|---|
ring_threshold | 70 | Reserved for a future sensitivity dial, and today it does exactly one thing: at 101 or higher (Voxa's reserved "never ring" sentinel, since priority tops out at 100) a landed turn that clears the trivial floor gets a notify instead of a call. It has no effect at all on blocked or broke, so it is not how you silence a project. Use mute_finish and mute_blocked above for that. |
presence_window_seconds | 90 | How recently you must have submitted a prompt anywhere in the fleet to count as "at the laptop." Within this window, a ring is demoted one rung to notify. |
coalesce_seconds | 30 | How long Voxa waits after the first pending interrupt before delivering the batch, so several sessions finishing close together become one call instead of several. |
min_seconds_between_rings | 600 | The minimum gap enforced between two calls. A ring that would otherwise arrive sooner is demoted to notify. |
block_escalate_seconds | 300 | How long a blocked session can sit unanswered, with nobody present, before it rings regardless of the presence and rate-limit guards. |
block_override | true | Whether a long-unanswered block is allowed to override those guards at all. Set false to let a block stay demoted indefinitely instead. |
escalation_tick_seconds | 15 | How often a background ticker re-checks blocked sessions for the block_escalate_seconds override, since a frozen agent stops sending hooks on its own. |
fallback_min_work_seconds | 600 | Part of the trivial floor: a landed turn shorter than this, and under fallback_min_tool_calls, stays silent. |
fallback_min_tool_calls | 15 | The other half of the trivial floor: a landed turn with fewer tool calls than this, and under fallback_min_work_seconds, stays silent. |
Self-host push
| Name | Default | What it does |
|---|
APNS_KEY_PATH | "" | Path to your Apple Push Notification service .p8 key file. |
APNS_KEY | "" | The .p8 key contents directly, as an alternative to APNS_KEY_PATH (useful on container hosts where mounting a file is awkward). |
APNS_KEY_ID | "" | Your APNs key id, from the Apple Developer portal. |
APNS_TEAM_ID | "" | Your Apple developer team id. |
APNS_BUNDLE_ID | "" | The app's bundle id (space.voxa.app for the official app). |
APNS_SANDBOX | false | Whether to use Apple's sandbox APNs environment. Set true for Xcode/dev-signed (sideloaded) builds; false for TestFlight/App Store builds. |
Push (agent-initiated calls) is only enabled once APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, and either APNS_KEY or APNS_KEY_PATH are all set. Leave any of them unset and Voxa still works fine as a voice client, it just won't ring you.
Connected Macs
| Name | Default | What it does |
|---|
VOXA_MACHINE_NAME | your hostname | The display name for this laptop in the phone's Connected Macs roster. |
VOXA_MACHINE_HEARTBEAT_SECONDS | 60 | How often the laptop refreshes its last_seen heartbeat in the roster. |
MACHINE_ROSTER_TTL_DAYS | 30 | How long a Mac can go without a heartbeat before it's pruned from the roster. |
Claude
| Name | Default | What it does |
|---|
ANTHROPIC_API_KEY | unset | Optional override for Claude auth. By default, the Claude Agent SDK reuses your existing Claude Code login, so this is only needed if you want to use an API key instead. |
VOXA_ISOLATE_CLAUDE | unset (off) | Runs the driven Claude session in an isolated config directory (no global hooks/plugins) instead of your normal Claude environment. Useful if one of your own hooks interferes with Voxa's driven session; your auth still comes from the Keychain either way. |
Misc
| Name | Default | What it does |
|---|
VOXA_ADMIN_TOKEN | unset | Gates the /admin-api/* dashboard. Leave unset to disable the dashboard entirely. |
SENTRY_DSN | unset | Enables Sentry error reporting when set. |
VOXA_ENV | production | The environment tag attached to Sentry reports. |
VOXA_DISCOVERY_TTL | 2.5 | How long (in seconds) discovered terminal state is cached before being refreshed. |
VOXA_BUSY_DECAY_SECONDS | 300 | For controllers with no screen to verify against, how long a "busy" flag is trusted after the last send before it's healed back to idle, so a wedged flag can't block dispatches forever. |