Error codes
Every code penv and our API answer with: exit code or HTTP status, cause, and fix, for the CLI, the API, SCIM and the AWS connect callback.
Codes from the penv CLI, our API, our SCIM endpoint and the AWS connect callback. Schema diagnostics: the .env.schema format.
CLI error shape
{
"error": "no_credential",
"message": "you are not signed in on this machine.",
"fix": "Run penv login. On a server or in CI, set PENV_TOKEN."
}fix is your next action. approval_required adds approval, url and expiresAt for the replay.
penv prints this JSON when stdout is not a terminal, with --json (global options), or under a detected agent; otherwise one line: error:, message, fix.
Exit codes
| Code | Name | Meaning |
|---|---|---|
0 | ok | The command did what it says. |
1 | error | Unclassified failure; the message names the cause. |
2 | auth | Not signed in, or the credential was rejected. |
3 | validation | The schema or the values did not pass. |
4 | confirmation | A person has to confirm. The JSON carries the command you replay. |
5 | no_credential | No credential is available and none can be obtained. |
6 | environment_refused | This identity may not read that environment, or its values are write-only. |
Once the child starts, penv run exits with the child's exit code, not its own.
Every code the command line answers with
Codes penv 1.0.0 raises, by exit. "Reinstall": curl -fsSL https://penv.cloud/install | sh (install the CLI).
Exit 1
| Code | Cause | Fix |
|---|---|---|
ambiguous | We answered ambiguous: a name in the address matches several projects or environments. | Rename the duplicate in the console. |
ambiguous_output | --output names one file for several targets. | penv gen per target. |
bad_url | PENV_URL is not an address penv sends a credential to (below). | Fix or unset it. |
cancelled | A picker was left, or a typed name did not match. | Run it again. |
cloud_failed | A client failure with no better name. | Retry; then penv check. |
hook_exists | penv scan found a git hook it did not write. | Add penv scan --staged to it. |
invalid_config | .penv/config.toml is not valid TOML. | Fix the named line. |
keychain | The OS keychain would not open. | Unlock it, or set PENV_TOKEN. |
live_leases | Credentials minted there are still live. | Revoke them, or wait. |
managed_install | npm, Homebrew, Nix, winget or Scoop owns the binary. | The manager's command |
no_command | penv run got nothing to run. | penv run -- <command> |
no_echo_off | The terminal will not stop echoing. | Pipe the value on stdin. |
no_git, not_a_repository | penv scan has no git, or no repository. | Name the files: penv scan <PATH>. |
no_keychain | No persistent keychain for an enrolled key. | Use PENV_TOKEN. |
no_org, no_such_org, org_required | Your account is in no workspace, none by that name, or several. | Create one, or pass --org. |
no_project, project_required, not_cloud | No cloud project yet, or this folder links none. | penv push, -p <project>, or penv pull to link. |
no_schema | No .env.schema here or above. | penv init |
no_secret | penv machine enroll got no one-time secret. | Pass the console's secret (penv machine). |
not_found | That project or environment does not exist on our side. | ls under penv project or penv env. |
quota_exceeded | Your plan's project limit is reached. | Billing and plans |
rate_limited | We or the release host throttle this identity. | Wait for the named reset (rate limits). |
server_error | We answered 5xx twice. | Retry later. |
server_refused | We answered a status penv has no name for. | Read the status in the message. |
spawn_failed | The penv run command could not start. | Check it is on PATH. |
child_failed, encrypt_failed, no_entropy, random_unavailable, unexpected_answer | Waiting on the penv run child, encrypting, or the random source failed, or we answered a full read as unchanged. | Retry. |
unknown_command, unknown_harness, unknown_shell | No such command, guard harness or completion shell. | penv help, penv guard, penv completions |
unknown_provider | @penv= or --provider names no provider penv has. | One the message lists. |
unknown_target | No language target by that name, or no build for this machine. | A listed target, or build from source. |
unreadable_file, unwritable_file | A file could not be read or written. | Check path and permissions. |
unreadable_input, unreadable_selection | No value on stdin or the terminal, or an unlisted choice. | Pipe it, or pick a listed choice. |
untrusted_ca_bundle | SSL_CERT_FILE is unreadable, holds no certificate, or is writable by you under an agent. | Unset it, or name a system bundle. |
undecryptable | We hold the value and cannot decrypt it, usually because your workspace key was revoked. It still exists. | Restore the key (BYOK); do not overwrite the value. |
unwritable_value | The value holds a ' with a ", \, $ or function call, a carriage return, or a line break with a " or \ (generated .env). | Change it, or read it with penv run. |
lsp_ended | The editor closed penv lsp without a shutdown request. | Restart the language server from the editor. |
value_on_the_command_line | penv set takes no --value. | Pipe it on stdin, or use the prompt. |
penv upgrade codes, all exit 1:
| Code | Cause | Fix |
|---|---|---|
binary_lost | The swap left no binary in place. | Reinstall. |
invalid_version, channel_unavailable | The argument is not latest, next or a version, or penv.cloud does not answer for that channel. | penv upgrade, or pin with PENV_VERSION and the installer. |
checksum_mismatch | The download does not match its checksum. | Retry; report a repeat. |
missing_asset, no_checksum | No binary, checksum, signature or digest for this machine. | Reinstall. |
no_current_exe | penv could not read its own path. | Reinstall. |
no_release, unreadable_release, release_unreachable | The release address answered 404, garbage, or nothing. | Retry later, or reinstall. |
signature_invalid, signature_missing | The checksum file lacks a penv release key signature. | Reinstall; report it. |
unsigned_build | This build carries no release key. | Reinstall. |
unsupported | The provider @penv= names does not declare what this command needs, so penv sends it no request. | Name a provider that does, with --provider or @penv=<provider>:org/project. |
unwritable_binary | The binary could not be replaced. | Write access, or reinstall. |
bad_url: https anywhere; plain http only for 127.0.0.1, localhost and [::1]. penv upgrade --check answers only invalid_version, unknown_target, untrusted_ca_bundle, the release-reading codes and unsigned_build (upgrade penv).
Exit 2
| Code | Cause | Fix |
|---|---|---|
agent_session | penv login, pull, push --prune, penv bundle or penv decrypt under an agent. | Run it from your terminal. |
approval_denied | A person refused the reveal. | Ask them, then penv reveal. |
cloned | Your host's identity was used from a second machine, so we locked it. | A credential leaked |
denied, expired | Sign-in denied, or your login or device code expired. | penv login |
forbidden | Your identity lacks the role. Servers and CI tokens never create, rename or delete. | Sign in as a person, or ask for the role. |
org_ambiguous | The org in @penv= names more than one workspace you belong to. | Rename one workspace in the console so the slugs differ. |
unauthorized | We rejected your login or token. | penv login, or replace PENV_TOKEN. |
Exit 3
| Code | Cause | Fix |
|---|---|---|
bundle_unreadable, invalid_bundle_key, no_bundle | PENV_BUNDLE_KEY is malformed, wrong, or has no bundle for that environment. | The key penv bundle printed, or bundle again. |
cannot_seal, proxy_failed, no_home, write_failed | A sealed run cannot place a placeholder, start its proxy, or write its certificates. | Fix the key's type, URL or @hosts; set HOME. |
hosts_unpinned, hosts_uncomputed | A computed @hosts has no pin, differs from its pin, or cannot be computed. | penv check --pin-hosts, after fixing the @hosts the message names. |
decrypt_failed, invalid_local_key, no_local_key | An enc:v1: value needs a local key this machine lacks, or PENV_LOCAL_KEY is not 64 hex characters. | Set PENV_LOCAL_KEY, or penv set the value again. |
dot_name | An org, project or environment name is only dots. | Give it a letter or digit, in the console and in .env.schema. |
dynamic | We generate that key's value on demand. | Edit it in the console. |
empty_value | penv set got nothing to write. | Pipe a value, or penv unset. |
exists, name_required | That environment name is taken, or a name has no letters or digits. | Another name (penv env ls). |
guard_failed | A harness file or guard folder did not merge (penv hook answers 1). | Fix it, then penv guard. |
invalid_environment | The environment name is not a usable file name, or is reserved (local, schema, example...). | Letters, digits, - and _. |
invalid_key | The key name is not upper snake case. | Rename it. |
invalid_schema | .env.schema does not parse. | penv check |
name_invalid | A pushed key name is not upper case. | Rename it in .env.schema and your value files. |
no_such_environment | penv push --env names an environment the project lacks; the message lists the others. | penv env new, or a listed one. |
no_value | The key has no value in that environment. | penv set |
nothing_to_push | The environment's value files are missing or empty; we are never contacted. | Write values, or penv set <KEY>. |
org_mismatch | penv push --org contradicts the @penv header. | Drop --org, or change the header. |
output_outside_repo, target_failed | penv gen output would land outside the repository, or a target folder will not load. | Fix the named path or folder. |
project_taken | Another project in your workspace already has that name. | Pick another name, or point @penv= at the existing project. |
reserved_name | penv run got a key whose variable changes how the command runs (NODE_OPTIONS, LD_PRELOAD, PATH...), or that is not a variable name. | Rename the parameter. |
unknown_key | penv check or penv why got an undeclared key. | Declare it; penv ls lists keys. |
invalid_reference, invalid_value, unresolved_value | A computed value failed: a bad penv() address, a random() length outside 8 to 512 or written as random(32) instead of random(length=32), a [url], [json.KEY] or [hash] entry that would expose a secret, or another reference. | Fix the part the message names. |
Exit 4
| Code | Cause | Fix |
|---|---|---|
approval_required, approval_expired, approval_redeemed | See the four approval codes. | Approve, or penv reveal again. |
approval_mismatch, no_approval, approval_unreadable | The id covers another key, is not on our side, or we did not say which key it covers (nothing spent). | penv reveal for this key. |
confirmation_required | Deleting a project or environment from an agent or a non-terminal. | Run the replay command in the JSON yourself. |
Exit 5 and 6
| Code | Exit | Cause | Fix |
|---|---|---|---|
no_credential | 5 | This host can prove nothing. | penv login, or set PENV_TOKEN. |
credential_withheld | 5 | The API root comes from .penv/config.toml, so penv withholds PENV_TOKEN, CI and AWS proofs. | penv login there, or set PENV_URL. |
offline | 5 | We are unreachable, and penv has no value files for this environment and, for development, no saved copy. | Reconnect. |
environment_refused | 6 | Your identity has no access to that environment. | Ask for a role, or change --env. |
redacted | 6 | The key is in a write-only environment and this identity is not a workload. The value exists. | Read it from a workload identity, or set it in .env.<env>.local. Do not overwrite the value. |
Since 1.0.0-beta.3
penv 1.0.0-beta.3 added these codes. The last column is what 1.0.0-beta.2 answers instead.
| Code | Exit | Cause | In 1.0.0-beta.2 |
|---|---|---|---|
invalid_version, channel_unavailable | 1 | penv upgrade got a bad argument, or we do not serve that channel yet. | penv upgrade takes no argument. |
project_taken | 3 | The project name exists in your workspace. | ambiguous |
org_ambiguous | 2 | Two workspaces you belong to answer to the @penv= org. | ambiguous |
name_invalid | 3 | A pushed key name is not upper case. | server_refused, 400 |
undecryptable | 1 | We hold the value and cannot decrypt it, usually because your workspace key was revoked. | server_refused, 409 |
redacted | 6 | The key is in a write-only environment and this identity is not a workload. | Key reported as missing; reveal gets server_refused, 409 |
Since 1.0.0-beta.4
| Code | Exit | Cause | Fix |
|---|---|---|---|
unsupported | 1 | The provider @penv= names does not declare what this command needs, so penv sends it no request. | Name a provider that does, with --provider or @penv=<provider>:org/project. |
undecryptable and redacted are never an absence: the value still exists. For undecryptable, restore the key (BYOK). For redacted, read it from a workload identity or set a local override. Do not overwrite the value.
What penv.cloud answers
Every /api/v1 route answers JSON; our error body is { "error": "<code>" }. The schema-shaped, name and size refusals add detail naming the wrong part of the body, and a schema_invalid about @hosts adds "field": "hosts", or "field": "hostsExpr" for a computed one. Operations: API reference. 401 and 403: authentication.
| Status | Code | Cause |
|---|---|---|
| 400 | name_required, keys_required, address_required, body_required, value_or_schema_required, token_required, signed_request_required, signed_challenge_required, credential_id_required, enrollment_required | The part of the request the code names is missing. |
| 400 | name_invalid, key_invalid | A key name or path, or the key an approval asks for, is not usable. A name holds letters, digits, _, - and .; a path holds no whitespace, control character or shell character. detail says which. |
| 400 | value_must_be_a_string | The value in the body is not a string. |
| 400 | schema_invalid | The per-key schema is not the shape penv schema --json emits. |
| 400 | ttl_must_be_an_integer | A lease or exchange lifetime (ttlSeconds) is not a whole number of seconds. |
| 400 | ttl_too_short | An exchange's ttlSeconds is under 60. |
| 400 | audience_not_workspace_id | An OIDC or AWS exchange named the workspace by its slug. Use the workspace id (the Organization ID under Settings, Organization) as the token audience or the signed x-penv-cloud-org header. |
| 400 | invalid_request | An app-connection request has a missing, repeated or malformed parameter, or a bad ttlSeconds (connect an app). |
| 400 | invalid_redirect_uri | The app's redirect_uri is not one registered for it. |
| 400 | invalid_grant | A connect code or app grant is wrong, expired, used, replaced or revoked. Presenting a replaced grant revokes the connection. |
| 401 | invalid_client | No registered app has that client_id, or the app was deleted. |
| 401 | expired | Your credential expired. |
| 401 | unauthorized | Not accepted; revoked and unknown read the same. |
| 401 | replayed | A keypair challenge nonce was used twice. |
| 403 | forbidden | Valid, without this permission or environment. |
| 403 | app_unavailable | The app is suspended (register an app). |
| 403 | app_unverified | The app is in testing mode and the person is not in the organization that owns it (register an app). |
| 403 | denied | A person denied the device-code sign-in. |
| 403 | refused | An OIDC exchange, delivery or plain, from a pull_request_target run. |
| 403 | lease_api_disabled | Leases are off for this environment. |
| 404 | not_found | No such project, environment, key, approval or route in scope. |
| 409 | dynamic | We mint the key on request; it is never a stored value. |
| 409 | change_approval_required | The environment requires approval and this change has none yet. We filed a request; requests in the body names it. |
| 409 | machine_change_requires_approval | The environment requires approval, and a machine identity cannot file a request. Make the change as a person, or turn off approvals for the environment. |
| 409 | change_conflict | You have an open request for a different change at this key. Save the change you asked for, or withdraw that request under Approvals; requests names it. Other keys in the same push were sent for approval; filed names those requests. |
| 409 | redacted | The environment is write-only and the credential is not a workload identity. The value exists. |
| 409 | cloned | The keypair generation counter does not match. The identity is revoked. |
| 409 | quota_exceeded | The plan's project limit is reached. |
| 409 | ambiguous | A project or environment slug in the address matches more than one on our side. |
| 409 | project_taken | Another project in your workspace already answers to that name's slug, on create or rename. |
| 409 | org_ambiguous | The org slug in an address names more than one workspace you belong to. |
| 409 | undecryptable | We hold the value and cannot open it, for instance because your workspace key was revoked. |
| 409 | exists | The project already has an environment by that name. |
| 409 | live_leases | A credential minted in what you delete is still live. |
| 409 | engine_unavailable | The engine behind a dynamic key did not answer. |
| 410 | expired | The device code expired before approval. |
| 413 | value_too_large | One value is over 256 KiB (262,144 bytes of UTF-8). detail names the key and its size. |
| 413 | too_many_keys | One push carries more than 1,000 keys. Push it in parts. |
| 413 | body_too_large | The request body is over 2 MiB for one value, or 4 MiB for a push. |
| 421 | wrong_region | The credential belongs to another residency cell. host names the API host that holds it. |
| 428 | authorization_pending | Nobody has approved the device code yet. Keep polling. |
| 429 | rate_limited | An IP or plan limit. retry-after gives the wait. |
| 429 | slow_down | Device-code polling is faster than the interval. |
| 429 | ceiling_reached | The abuse ceiling on dynamic leases. |
| 500 | internal | We failed on our side. Retry once. |
| 500 | mint_failed | The engine accepted the request and produced nothing. |
| 503 | unavailable | A dependency is unreachable or unconfigured. Retry once. |
penv retries any other 5xx from us once after a second, then reports server_error naming the status. Back-off for 429: rate limits.
The four approval codes
We answer every approval state of a penv reveal under an agent with a 409. Flow: coding agents.
| Server code | Meaning | CLI code | Exit |
|---|---|---|---|
approval_pending | Unanswered, or an open request for this key and session exists. | approval_required, with page and expiry | 4 |
approval_denied | A person refused it. | approval_denied | 2 |
approval_expired | The ten minutes ran out. | approval_expired | 4 |
approval_redeemed | The id was already spent. | approval_redeemed | 4 |
A 404 from the redeem route becomes no_approval. penv raises approval_mismatch itself, before it asks us to redeem, so a mismatched id is never spent.
SCIM errors
Our directory endpoint answers RFC 7644 error objects, the shape Okta and Entra parse. Content type: application/scim+json;charset=UTF-8. status is a string; scimType carries an RFC detail keyword when one applies.
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "A user with that userName already exists."
}We answer an unrecognized failure with a 500 whose detail is always "The request could not be completed.", so no stack trace reaches a directory token holder.
AWS connect callback
Success: 200 with the lifecycle secret on a fresh create, 200 {} on a replayed create, otherwise 204.
| Code | Status | Cause |
|---|---|---|
invalid_request | 400 | The body has the wrong shape. |
unauthorized | 401 | The token or External ID did not hold. |
conflict | 409 | The stack conflicts with what your workspace already has. |
upstream | 502 | AWS answered something we cannot act on. |
retry | 503 | retry-after is 5 seconds. |
unexpected | 500 | Nothing else fits. |
An unknown, expired, already claimed or exhausted token, a wrong External ID and a bad lifecycle secret all get the same 401, so the endpoint reveals nothing about which.