ReferenceErrors and exit codes
Errors and exit codes#
What does each error mean, and what do I do?
On this page
Every refusal from the vault is JSON with the header Mehen-Error: 1, so it can never be confused with a relayed upstream response, which carries Mehen-Upstream: 1. The shape is the wire contract:
type ApiError = {
error: ErrorCode
message: string
next?: { url?: string; command?: string; say: string }
approval?: string
bindingCode?: string
choices?: { slug: string; account: string | null }[]
retryAfter?: number
expiresAt?: string // when next.url is a link that expires
deciders?: string[] // only with approval_required: who may decide, by name
transition?: ... // only with account_pending: the account security change in flight
}With --json, mehen prints the error object on stdout and exits with the code in the table below; without it, the message, the next line and any ask block go to stderr. mehen adds ask to any error that carries a link it accepted, with the enrolment that made the failing call on ask.context. A failure that starts on this machine has the same shape.
next is the fix, and you should always use it:
| Field | What to do with it |
|---|---|
ask | The block to write for your human, when a human must open something. Build the message from it. |
next.say | One sentence written for your human, for anything that has no ask. Relay it as it is. |
next.url | A page a human must open. Send it to them; never open or drive it yourself. |
next.command | A command you can run to continue, usually mehen wait <id>. Run it. |
approval, bindingCode, expiresAt | The approval id, the code the human will see on the approval page, and when it expires. |
choices | The OAuth clients that could serve, when mehen connect finds several. |
retryAfter | Seconds to wait before retrying a transient failure. |
Refusals include a reason. When the vault sent no next, mehen fills in the step for its code (the default next in the table below, for example mehen doctor for proof_invalid and mehen ls for not_found); some of those steps are a sentence of advice rather than a command, and a local error from mehen itself may have next: null. Some problems need a human to repair the setup. expiresAt comes with every link that expires and with every status mehen wait polls.
Never guess around a refusal. Do not retry it with a different host, a different item, a body-encoded token, an encoded path or a rephrased URL. The refusal is the vault working.
Exit codes#
One table for every mehen command and anything else that wraps the vault.
| Exit | Meaning | What you do |
|---|---|---|
0 | OK | Carry on. |
1 | Error | Read error and message. Something is missing or broken; report it. |
2 | Usage | Fix the command or the request. Also the local workspace selection refusals (workspace_unknown, workspace_mismatch, context_ambiguous, workspace_unbound, workspace_not_allowed, config_dir_retired, pin_outdated), which happen before anything is signed or sent; fix the selection. An activation mismatch is different: it is reported after a vault response and an attempted cleanup, so exit 2 alone does not prove no network call occurred. Read the error's outcome: it says whether the new device's revocation succeeded, and if it did not, keep the device id visible so a human can revoke it. |
3 | Approval required | Write the human the block from ask (the check is the binding code), then mehen wait <approval>, then repeat the same request. mehen check also exits 3 to say a request would need approval. |
4 | A human must act | Write the human the block from ask, then wait (next.command when given). Also: mehen wait timed out, a connection signed in as the wrong account, mehen connect found no OAuth client (client_missing), the human must sign in with Google in the workspace (identity_required), their account's security change is still settling (account_pending), this machine's owner is not an active member (owner_inactive), or the project is not shared with them (project_unshared). |
5 | Refused by rule | Retrying will not help. Tell the human what was refused and why; only they can change bindings, policies, ceilings, scopes or fences. Also: the human denied an approval or rejected an enrolment, and mehen check saying a request would be refused. |
6 | Identity | This machine's identity was not accepted. Check mehen status: not enrolled in this workspace, revoked, a bad proof (often a clock more than 60 seconds off), a device key file other users can read, or a key made for another vault. |
7 | Transient | Network, upstream or rate limit. Retry, after retryAfter seconds when given. No connection to the vault at all is also 7. |
Errors from the vault#
Every code the vault sends, with its HTTP status, the exit code mehen gives it, and the step mehen fills in when the vault sent no next of its own.
error | HTTP | Exit | Default next |
|---|---|---|---|
bad_request | 400 | 2 | Fix the request as the message says; mehen <command> --help shows the options. |
unauthenticated | 401 | 6 | The vault did not recognise this machine; mehen doctor says whether it is enrolled and what to do. |
proof_invalid | 401 | 6 | The vault did not accept this machine's signature, often because the clock is more than 60 s off; mehen doctor checks the clock and the key. |
device_pending | 403 | 4 | This machine's enrolment has not been approved yet; relay its link to the owner (mehen pending shows it). |
device_revoked | 403 | 6 | This machine was revoked. Stop and tell the human; only they can let it enrol again. |
forbidden | 403 | 5 | This machine is not allowed to do that. Tell the human; only they can widen its scope in the vault. |
not_found | 404 | 1 | Check the name with mehen ls; ask for a missing credential with mehen request. |
conflict | 409 | 1 | This clashes with something that already exists or is already open; mehen pending lists this machine's open asks. |
ambiguous | 409 | 2 | Name one item by its slug; if you are not sure which account is meant, ask the human. |
binding_refused | 403 | 5 | The vault will not send this credential there. Use a host and a plain URL from mehen show <item>; only a human can change where it may go. |
surface_refused | 403 | 5 | Remove the placeholder from the body, query or header it is in; the vault injects the credential itself. |
policy_denied | 403 | 5 | The item's policy refuses this. Do not work around it; tell the human what you tried, or use another route. |
approval_required | 409 | 3 | This needs a human's approval in the vault; relay the link, wait for it, then repeat the same request. |
mode_refused | 403 | 5 | The vault will not use this credential that way. Use mehen fetch or mehen run, and keep a request that needs approval small and uncompressed. |
environment_refused | 403 | 5 | That environment is protected or fenced. Use an unprotected one, or run with a literal --env <name> and expect an approval. |
remote_mismatch | 403 | 5 | This checkout's origin remote is not the one bound to the project in mehen.toml. Check you are in the right clone. |
needs_reauth | 424 | 4 | The credential no longer works; a human must reconnect or replace it in the vault. Tell them which one. |
refresh_retrying | 503 | 7 | The provider did not answer the vault's token refresh; retry shortly. |
intake_required | 404 | 4 | The credential does not exist yet; ask the human for it with mehen request. |
frozen | 423 | 4 | The vault is frozen. Stop and tell the human; they review the audit log and unfreeze it in the vault. |
rate_limited | 429 | 7 | Too many requests; slow down and retry shortly. |
too_large | 413 | 1 | Send or ask for less: the vault caps requests and responses. |
upstream_failed | 502 | 7 | The vault could not get an answer from the upstream; retry later, and check whether the request took effect first. |
internal | 500 | 1 | Something broke inside the vault. Retry once; if it happens again, tell the human the time it happened. |
account_pending | 403 | 4 | Account security changes are pending. Ask the human to check their account security page. |
owner_inactive | 403 | 4 | The machine owner is not active. Ask a workspace owner or admin to check their membership. |
identity_required | 403 | 4 | The human must verify their Google identity in the workspace before continuing. |
project_unshared | 403 | 4 | Ask the owner or a project manager to share the project with this machine's owner. |
cli_outdated | 426 | 1 | This machine's mehen is older than the vault supports; update it, then run the command again. |
bad_request#
HTTP 400 · exit 2 · from the vault
What it means. The request was malformed or failed validation: an unknown provider, a template of another kind, a new item without a template or proposed binding, a run naming variables the vault does not know, a plain value that looks like a secret, a request with nothing for the relay to do.
What you do. Fix it as the message says; mehen <command> --help shows the options.
unauthenticated#
HTTP 401 · exit 6 · from the vault · default next: mehen doctor
What it means. No proof, or this machine is not enrolled.
What you do. Run mehen status; the machine may need mehen enrol --workspace <slug>.
proof_invalid#
HTTP 401 · exit 6 · from the vault · default next: mehen doctor
What it means. The vault did not accept this machine's signature: clock skew over 60 seconds, a replay, or a URL, method or body that does not match what was signed.
What you do. Check the machine's clock; mehen doctor checks the clock and the key. Do not hand-craft requests; use mehen.
device_pending#
HTTP 403 · exit 4 · from the vault · default next: mehen pending
What it means. This machine's enrolment has not been accepted yet.
What you do. Write the human the enrolment block (mehen pending shows it again), then mehen wait <enrolment>.
device_revoked#
HTTP 403 · exit 6 · from the vault
What it means. This device was revoked.
What you do. Stop. Tell the human; only they can let it enrol again.
forbidden#
HTTP 403 · exit 5 · from the vault · default next: mehen status
What it means. This device's scope or its owner's rights do not cover the item, project or action (a guest's or a team's machine creating a project, a machine limited to named projects running mehen init, a workspace home proposed by a member's machine), the item is disabled or never sent by the relay, a method-override header or parameter was sent, or only an owner or admin may do this.
What you do. Tell the human; only they can widen a scope or a share.
not_found#
HTTP 404 · exit 1 · from the vault · default next: mehen ls
What it means. No such item, intake, approval, connection, project, run or route, or one outside this machine's reach: another member's private credential, a project not shared with your owner, a workspace credential from a guest's or a team's machine. Absent and out of reach answer the same, so nothing can be learnt by naming.
What you do. Check the slug or id with mehen ls; ask for a missing credential with mehen request.
conflict#
HTTP 409 · exit 1 · from the vault · default next: mehen pending
What it means. The request clashes with existing state: a name not available (in use anywhere in the workspace, including by a credential you cannot see), an item of another kind, a project or remote already taken, or an open intake or connect for the same slug with other details, from this machine or another; or a rotation proposing a different home.
What you do. Pick another slug, wait on the open link (mehen pending lists this machine's), or ask again with --new to replace your own.
ambiguous#
HTTP 409 · exit 2 · from the vault
What it means. Reserved: several items could serve. The vault does not raise it today, because every request names its item.
What you do. Pick one slug from choices; if you are not sure which account is meant, ask the human.
binding_refused#
HTTP 403 · exit 5 · from the vault
What it means. The destination is outside the item's binding, or the URL is one the vault will not guess at: an encoded slash, dot or backslash, .., //, a username in the URL, a scheme other than http or https, or an IP address or loopback name not bound exactly.
What you do. Use a host and a plain URL from mehen show <item>. If the host really is needed, ask the human to change the binding; only they can.
surface_refused#
HTTP 403 · exit 5 · from the vault
What it means. A run's placeholder appeared outside its item's declared header or query parameter, or at a host it is not bound to. It is recorded as a tripwire.
What you do. Remove the placeholder from the body, query or header it is in; the vault injects the credential itself.
policy_denied#
HTTP 403 · exit 5 · from the vault
What it means. The item's policy denies this method or path.
What you do. Do not work around it. Use the alternative the template names, or tell the human what you tried.
approval_required#
HTTP 409 · exit 3 · from the vault
What it means. A human must approve this exact request: a send, a delete, a production run, any use of a protected item. The error carries the approval id, the link, a binding code and expiresAt.
What you do. Tell the human in one message what you are trying to do, then write the block from ask; the check is the binding code. Run mehen wait <approval>, and when it is approved repeat exactly the same request from the same machine (the same item, method, URL, headers and body). If it is denied or expires, stop and ask the human what they want instead; do not retry in a loop.
mode_refused#
HTTP 403 · exit 5 · from the vault
What it means. The item's exposure ceiling does not allow this use, a lease was asked for that the item, the device or the run does not allow, or a request that needs approval cannot be shown in full (a compressed body, a body over 64 KiB, a command line over 4096 characters).
What you do. Use relay (mehen fetch) or placeholders (mehen run), and send anything that needs approval uncompressed and small.
environment_refused#
HTTP 403 · exit 5 · from the vault
What it means. A protected environment without a literal --env, a device allowed for it or an owner with the protected right; a protected credential used without a project context naming the protected environment that references it, or outside an approved protected run; an agent setting a protected variable; an OAuth client a protected environment relies on, used by anyone but an owner or admin; or a host fenced to another environment.
What you do. Use an unprotected environment, or type --env <name> and expect an approval; give mehen fetch the context with --project <prj_id> --env <name>. For a fence, run in the environment the host belongs to.
remote_mismatch#
HTTP 403 · exit 5 · from the vault
What it means. This checkout's origin remote is not the one bound to the project id in mehen.toml.
What you do. Check you are in the right clone.
needs_reauth#
HTTP 424 · exit 4 · from the vault
What it means. The OAuth connection is dead (revoked, expired, password changed, client secret rejected), or the item must be replaced.
What you do. Write the human the block (a reconnect link pinned to the same account, with expiresAt), then mehen wait. When the vault's link is the item's own page (a broken OAuth client, or a non-OAuth item that must be replaced), the block opens that page and has no wait: the human replaces the credential there, then you try again.
refresh_retrying#
HTTP 503 · exit 7 · from the vault
What it means. The provider did not answer the vault's token refresh: a transient OAuth failure.
What you do. Retry after retryAfter seconds.
intake_required#
HTTP 404 · exit 4 · from the vault · default next: mehen request --help
What it means. Reserved: the credential does not exist yet. The vault answers not_found today.
What you do. Ask the human for it with mehen request, write them the block, then mehen wait <intake>.
frozen#
HTTP 423 · exit 4 · from the vault
What it means. The whole workspace is frozen: every use is refused until an owner unfreezes it.
What you do. Stop. Tell the human; they review the audit log and unfreeze it in the vault.
rate_limited#
HTTP 429 · exit 7 · from the vault
What it means. Too many requests; also this machine's 20 open asks, or too many unavailable names tried in an hour.
What you do. Slow down, settle an open ask, and retry after retryAfter seconds when given.
too_large#
HTTP 413 · exit 1 · from the vault
What it means. The request or the upstream's response is too large; responses are capped at 32 MiB.
What you do. Send or ask for less.
upstream_failed#
HTTP 502 · exit 7 · from the vault
What it means. The vault could not reach the upstream, or the upstream answered in a compression the vault cannot check for leaked credentials. In the compression case the request itself was sent.
What you do. Retry later; in the compression case, check whether the request took effect before sending it again.
internal#
HTTP 500 · exit 1 · from the vault
What it means. Something broke inside the vault, or inside mehen.
What you do. Retry once; if it happens again, tell the human the time it happened. For a failure inside mehen, mehen doctor checks this machine.
account_pending#
HTTP 403 · exit 4 · from the vault
What it means. The human's account has a security change (a recovery, a factor removal) still being installed across their workspaces; anything that widens access waits until it has.
What you do. Wait; the human can see it on their account page.
owner_inactive#
HTTP 403 · exit 4 · from the vault
What it means. The member who owns this machine is suspended, removed or not yet confirmed, or its team is gone; a machine never outlives its owner's standing.
What you do. Stop. Tell the human; an owner or admin must act on the membership.
identity_required#
HTTP 403 · exit 4 · from the vault
What it means. The workspace requires a verified Google identity for the human's role, and their sign-in is not fresh enough for what was asked. It is raised to humans in the browser, and reaches a machine only through an operation that needed the human's standing.
What you do. Tell the human to open https://app.mehen.dev/w/<slug>/ and complete the sign-in it asks for, then repeat the step. You cannot clear it yourself.
project_unshared#
HTTP 403 · exit 4 · from the vault
What it means. The project in mehen.toml exists but is not shared with this machine's owner, or no longer is.
What you do. Ask an owner or a project manager to share the project with this machine's owner.
cli_outdated#
HTTP 426 · exit 1 · from the vault · default next: mehen update
What it means. An identified mehen is below the vault's minimum supported version (HTTP 426); freeze and self-revoke are exempt. The vault refused the request before doing anything.
What you do. Follow the returned next: after mehen has updated itself, it names the original command to run again; if the update did not run or failed, run mehen update --json, report any refusal, and never reinstall to get round a signature failure.
Errors from mehen itself#
Failures that start on this machine. They use the same shape and the same exit codes, and the vault never sends them.
error | Exit | What it means |
|---|---|---|
usage | 2 | Bad arguments; also mehen connect finding several OAuth clients, with choices. |
network | 7 | The vault could not be reached. |
timeout | 4 | mehen wait ran out of time while a human has not acted. |
stale | 5 | mehen wait found the approval or policy proposal stale: access changed under it (a share expired, a role changed, a protected reference was added, the decider lost their basis). |
denied | 5 | The human denied the approval. |
rejected | 5 | The human rejected this machine's enrolment, or five wrong hidden groups were typed on its page. |
mismatch | 4 | mehen wait reports it when a connect link's status says the human signed in as an account other than the one expected. |
client_missing | 4 | mehen connect found no OAuth client for the provider. |
expired | 1 | An intake, approval, connection or enrolment expired. |
cancelled | 1 | An intake, approval, connection or enrolment was cancelled. |
failed | 1 | An intake, approval, connection or enrolment failed; also another mehen holding a lock for 30 seconds (the message names its process id), and an unfinished migration of MEHEN_HOME. |
upstream_status | 1 | mehen fetch --fail was used and the upstream answered 4xx or 5xx. |
already_enrolled | 1 | The vault activated a second device for a workspace this machine already holds a device in. |
not_available | 1 | The Secure Enclave helper is missing. |
helper_failed | 1 | The Secure Enclave helper failed. |
key_insecure | 6 | The device key file, a file beside it or the state directory is a symlink, is owned by someone else, or can be read by other users; or this user's home directory in the system's user database cannot be found or is unsafe, so mehen cannot tell whether the host is managed. |
key_scope | 6 | The device key on disk was made for another vault. |
command_failed | 1 | A mehen env call made through vault_env failed. |
pin_outdated | 2 | A managed host's agent configuration is out of date. |
workspace_unbound | 2 | The checkout's mehen.toml names no workspace, and this machine is enrolled in more than one. |
workspace_mismatch | 2 | Two things name different workspaces or origins: the selector and the manifest, MEHEN_VAULT_URL and the enrolment, the key scope and the config, a vault_env cwd and the server's workspace, or a selection on a managed host that is not its pin; nothing is signed or sent. |
workspace_unknown | 2 | No enrolment on this machine is in the workspace named by --workspace, MEHEN_WORKSPACE or the checkout's mehen.toml. |
context_ambiguous | 2 | Several workspaces could serve and nothing selects one. |
workspace_not_allowed | 2 | The workspace named is outside MEHEN_WORKSPACES. |
config_dir_retired | 2 | The harness sets MEHEN_CONFIG_DIR, a retired variable, pointing inside Mehen's state or disagreeing with MEHEN_HOME. |
usage#
exit 2 · from mehen itself
What it means. Bad arguments; also mehen connect finding several OAuth clients, with choices.
What you do. Run next.command, the command's --help, and fix the arguments; with several OAuth clients, name one with --client.
network#
exit 7 · from mehen itself
What it means. The vault could not be reached.
What you do. Check the network connection and the vault URL (MEHEN_VAULT_URL), then retry. With mehen fetch --max-time, the request may still have reached the upstream: check before sending it again.
timeout#
exit 4 · from mehen itself
What it means. mehen wait ran out of time while a human has not acted.
What you do. Run next.command again; the ask comes back with the error so you can relay it again. A connect link the human signed in to with an unexpected account also ends this way: ask them what the callback page said.
stale#
exit 5 · from mehen itself
What it means. mehen wait found the approval or policy proposal stale: access changed under it (a share expired, a role changed, a protected reference was added, the decider lost their basis).
What you do. Do not repeat the old approval; follow next and ask again from the start.
denied#
exit 5 · from mehen itself
What it means. The human denied the approval.
What you do. Do not retry without asking them what they want instead.
rejected#
exit 5 · from mehen itself
What it means. The human rejected this machine's enrolment, or five wrong hidden groups were typed on its page.
What you do. Stop and tell the human; enrol again only when they ask you to.
mismatch#
exit 4 · from mehen itself
What it means. mehen wait reports it when a connect link's status says the human signed in as an account other than the one expected. In the current release the vault does not record that status: it refuses an unexpected account at the provider's callback with forbidden, overwrites nothing, and the link stays open, so the wait goes on until it times out or the link expires.
What you do. Ask the human what the callback page said, tell them which account was expected, and start a fresh connection with --new before relaying a new link.
client_missing#
exit 4 · from mehen itself
What it means. mehen connect found no OAuth client for the provider. request and setup carry the exact arguments for the client and the redirect URI.
What you do. Run next.command, the exact mehen request oauth-client ..., or repeat the connect with --request-client to ask for the client and the connection at once.
expired#
exit 1 · from mehen itself
What it means. An intake, approval, connection or enrolment expired.
What you do. When this machine asked, next.command is the exact command to run again (for an enrolment, mehen enrol --workspace <slug>); run it and relay the new link.
cancelled#
exit 1 · from mehen itself
What it means. An intake, approval, connection or enrolment was cancelled. An intake you replaced with --new is cancelled.
What you do. Ask the human before asking again; mehen pending lists a newer link when you made one.
failed#
exit 1 · from mehen itself
What it means. An intake, approval, connection or enrolment failed; also another mehen holding a lock for 30 seconds (the message names its process id), and an unfinished migration of MEHEN_HOME.
What you do. When this machine asked, run next.command. For a lock, retry shortly. For a migration, next.command is mehen doctor --json: run it and tell the human.
upstream_status#
exit 1 · from mehen itself
What it means. mehen fetch --fail was used and the upstream answered 4xx or 5xx.
What you do. Read the status and the body; the request reached the upstream.
already_enrolled#
exit 1 · from mehen itself
What it means. The vault activated a second device for a workspace this machine already holds a device in. Nothing was revoked or deleted.
What you do. Relay next.say, which names both devices and how to keep one.
not_available#
exit 1 · from mehen itself
What it means. The Secure Enclave helper is missing.
What you do. Enrol with --backend software, or ask the human to fix the helper.
helper_failed#
exit 1 · from mehen itself
What it means. The Secure Enclave helper failed.
What you do. Enrol with --backend software, or ask the human to fix the helper.
key_insecure#
exit 6 · from mehen itself
What it means. The device key file, a file beside it or the state directory is a symlink, is owned by someone else, or can be read by other users; or this user's home directory in the system's user database cannot be found or is unsafe, so mehen cannot tell whether the host is managed.
What you do. The message says which; follow next, and tell the human when it is theirs to fix.
key_scope#
exit 6 · from mehen itself
What it means. The device key on disk was made for another vault. Once a workspace is selected, a key scope naming another origin or workspace is workspace_mismatch instead.
What you do. Run next.command, mehen enrol --workspace <slug> --fresh-key, or set MEHEN_VAULT_URL back.
command_failed#
exit 1 · from mehen itself
What it means. A mehen env call made through vault_env failed.
What you do. The message says why and names the fix.
pin_outdated#
exit 2 · from mehen itself
What it means. A managed host's agent configuration is out of date. Selection fails closed; nothing is signed or sent.
What you do. This host uses an outdated managed-agent configuration. Stop and contact the harness owner. Relay the refusal and its next step; do not edit the pin, identity files or launch environment yourself.
workspace_unbound#
exit 2 · from mehen itself
What it means. The checkout's mehen.toml names no workspace, and this machine is enrolled in more than one.
What you do. Run mehen workspace bind in the checkout.
workspace_mismatch#
exit 2 · from mehen itself
What it means. Two things name different workspaces or origins: the selector and the manifest, MEHEN_VAULT_URL and the enrolment, the key scope and the config, a vault_env cwd and the server's workspace, or a selection on a managed host that is not its pin; nothing is signed or sent. Also an enrolment the vault activated in another workspace, with another owner kind or an incomplete scope than asked, or into a directory another enrolment took meanwhile: mehen has then contacted the vault, attempted to revoke the new device and written no config.
What you do. Give one selection and fix the other. After an enrolment mismatch, read the message: it says whether the revocation succeeded, and when it did not it names the device for a human to revoke.
workspace_unknown#
exit 2 · from mehen itself
What it means. No enrolment on this machine is in the workspace named by --workspace, MEHEN_WORKSPACE or the checkout's mehen.toml.
What you do. Run next.command, mehen enrol --workspace <slug>, or mehen wait <enrolment> for a pending one.
context_ambiguous#
exit 2 · from mehen itself
What it means. Several workspaces could serve and nothing selects one. Also a bare entry of MEHEN_WORKSPACES that matches workspaces at two vaults.
What you do. Name one with --workspace <slug>; next.say lists them. Never pick a workspace on the human's behalf. For the MEHEN_WORKSPACES case, the human writes the entry as <vault>/<slug>.
workspace_not_allowed#
exit 2 · from mehen itself
What it means. The workspace named is outside MEHEN_WORKSPACES.
What you do. Tell the human, who changes the harness; nothing inside mehen widens it.
config_dir_retired#
exit 2 · from mehen itself
What it means. The harness sets MEHEN_CONFIG_DIR, a retired variable, pointing inside Mehen's state or disagreeing with MEHEN_HOME.
What you do. Relay next.say, which names the MEHEN_HOME to set instead; tell the human.