Command line changelog
Every user-facing change to the penv binary, newest first, with the release each one shipped in.
Every change you can see in the penv binary, newest first, with the release it shipped in. Everything below is the 1.0 line; for 0.16 and what replaced it, see upgrade from 0.16.
1.0.0 follows 1.0.0-rc.3, and stability lists what 1.0 keeps. Release delivery: upgrade penv.
1.0.0
19 October 2026. Release notes
Added
penv pushsends a computed@hostsashostsExpr; penv.cloud 1.0 stores and shows it. A server that refuseshostsorhostsExprgets one retry without them, with a warning.url()reads and rewrites parts of a URL.url($DATABASE_URL, host)reads,url($X, host=replica.internal.test)writes. The user, password, path and query stay sensitive unless[url] publicopts them in. Functions.json()reads, writes and builds JSON:json($X, db.hosts[0]),json($X, project_id=acme),json(username=$U, password=$P). Functions.hash()hashes and signs:hash($KEY, algo=sha256, length=8), andhmac-sha256orhmac-sha512withkey=, inhex,base64orbase64url. Functions.random(kind=, length=)takes named options. Kinds arealnum,hex,base64urlanduuid.b64dfilter:${KEY | b64d}decodes base64, the inverse ofbase64.[url],[hash]and[json.KEY]public lists in.penv/config.tomldeclassify the parts, hash keys and JSON paths you name.penv checkrejects an entry that would expose a secret. Config.- A public read declassifies only a stored value.
url()andjson()reads on thepubliclists declassify a part of a key's stored value, never of a value an expression built, sourl("https://${TOKEN}.x.test", host)stays sensitive. A URL'spathis sensitive by default, since a webhook URL carries its secret there; add"path"to[url] publicto opt in. - A computed
@hostsis allowed.@hosts=url($DATABASE_URL, host)is pinned under[hosts];penv checkand a sealed run refuse a key whose hosts differ from the pin, andpenv check --pin-hostswrites the pins.@hosts. - The editor completes and explains function arguments and suggests the closest option on a typo.
Breaking
random(N)is a validation failure. Writerandom(length=N). Upgrade notes.
1.0.0-rc.3
29 September 2026. Release candidate. Release notes
Fixed
- Masking no longer breaks Next.js middleware. With
mask = true, the generatedenv.tsand thepenv runpreload maskResponsebodies through a subclass ofResponse; aNextResponsebuilt on the original class then failed Next'sinstanceof Responsecheck, and every request answered 500 ("Expected an instance of Response to be returned"). A response built on the original class passesinstanceof Responseagain.
Changed
- Comments in files
penv genwrites are at most two lines each. - Every setting in
.penv/config.tomlsits under a comment of at most two lines: what it does, then the values it takes.penv genwrites one above each[targets.<name>.options]key (Values: node | vite | deno | workers (default node).) and keeps a comment you wrote there.
1.0.0-rc.2
29 September 2026. Release candidate. Release notes
Breaking
- A CI or AWS login names its workspace by id. We take only the workspace id as the OIDC audience and in the signed AWS
x-penv-cloud-orgheader, and penv refuses a slug in@penv=before any request when it would become either. Migration: replace the workspace slug in@penv=with the Organization ID from Settings → Organization in the console. A person'spenv loginis unchanged.
Added
penv runkeeps the command's own variables. A key whose variable would change how the command runs (NODE_OPTIONS,LD_PRELOAD,BASH_ENV,PATH,PIP_INDEX_URL,YARN_*,CARGO_*and the rest, in any case), or that is not a variable name, fails the run withreserved_name(exit 3), naming the parameter. The child never inherits a CI runner's OIDC request token, its runtime, results and cache tokens, the step files (GITHUB_ENV,GITHUB_OUTPUT,GITHUB_PATH,GITHUB_STATE),INPUT_*orSTATE_*.
Changed
penv loginasks you to type the code: penv opens the approval page and printsEnter this code in the browser: ABCD-EFGH. Only approve it if you started this login.After sign-in it lists the workspaces the login reaches. A workspace's sign-in policy is printed as the workspace wrote it, and an ended login says to sign in again.penv pushandpenv setcheck a write before sending it: names match^[A-Za-z0-9_][A-Za-z0-9_.-]*$up to 255 characters, values are up to 256 KiB, and the refusal names the key, never the value. A push of more than 1000 keys goes in batches of 1000 after every value's size is checked;--pruneis refused when a push takes more than one batch.machine_change_requires_approvalsays to make the change as a person in the console, where it becomes a change request.- Each machine-login refusal says what to change: a refused CI trigger (
pull_request_target) names the ones that work, an expired token says it expired, and an untrusted one points at connect a platform.unavailableis retried three times before it is named. - An AWS login is signed afresh for every try, since we accept a signed request once.
- A
.envreads a backtick escaped inside double quotes without a warning, as the console exports it.
Fixed
- Arrow keys move the selection in every choice prompt, including application cursor mode (tmux, screen) and an arrow whose bytes arrive in two reads.
j/kand Ctrl-P/Ctrl-N work too, and a prompt is never shown when stdin is not a terminal.
1.0.0-rc.1
27 September 2026. Release candidate. Release notes
It behaves as 1.0.0-beta.4 does; until 1.0.0 only fixes land. The installers, penv upgrade and npm i -g @penvhq/cli install it.
Added
- The stability promise: what a 1.0 minor or patch release keeps as it is (command names, flags, exit codes, JSON output, the
.env.schemagrammar,.penv/config.tomlkeys) and what it may change. It lives in Design.md §13; stability summarises it.
Changed
- The Copilot CLI guard is marked experimental wherever
initandcheckdescribe it: Copilot publishes no schema for its permissions config, so the guard sits outside the stability promise. sslmode=verify-caon a sealed Postgres URL is documented as what it already does: it checks the server's certificate and its host name, asverify-fulldoes.
Fixed
/installand a plainpenv upgradegave 1.0.0-beta.3 after 1.0.0-beta.4 shipped: the release workflow counted the old@penvhq/*@0.xpackage releases as plain penv releases. Onlyvtags count now, so they give this release.
1.0.0-beta.4
25 September 2026. Pre-release. Release notes
Working with penv.cloud
- Every OIDC, AWS and keypair exchange asks for its credential's lifetime (
ttlSeconds): 5 minutes under an agent, 15 otherwise. An agent never reuses a credential past 5 minutes. - Offline, only
developmentfalls back to the local value files. Any other environment stops withoffline(exit 5). - penv refuses a command the chosen provider does not declare with
unsupported, naming the provider and the capability. - Messages name penv.cloud, not penv-cloud.
Coding agents
penv hookletspenv revealthrough: under an agent, a person approves the reveal before anything prints.penv pullis still refused.- The
penv guard --checkclaim for cloud mode says we record every value we hand out as a read.
Install and help
install.ps1verifies the release signature with OpenSSL 1.1.1 or newer fromPATH, asinstall.shdoes.- A penv installed by npm is told
npm i -g @penvhq/cli@<version>, because npm'slatesttag holds no prerelease. penv --help,penv help--jsonand the shell completions read one summary per command.
1.0.0-beta.3
24 September 2026. Release notes
Editors
penv lspserves.env.schemaover the Language Server Protocol: the problemspenv checkreports, completion and hover for every decorator, go-to-definition for key references, and an outline. It reads no value file and sends no request. The penv extension for VS Code runs it and ships separately.
Upgrades
penv upgrade [latest|next|<version>]:nextfollows prereleases, a version pins or steps back, andlateststays the default. Refusals:invalid_version,channel_unavailable.- Once a plain release exists, a prerelease no longer becomes the release
/installandpenv upgradegive.
Working with penv.cloud
- A key we withhold from a person's login or a static token, on a write-only environment, is
redacted: present, never missing.runandbundlerefuse it unless a local layer such as.env.<env>.localsupplies it,pullwrites# penv:redacted KEYin place of the value,lsandwhysay it is withheld, andrevealrefuses it. - When we refuse a taken project name, an org slug two workspaces share, a value we cannot decrypt,
or a lowercase key name on push, penv names it:
project_taken,org_ambiguous,undecryptable,name_invalid. - An AWS login signs the workspace in
x-penv-cloud-org, which we require; without it we refused every AWS exchange.
Generated code
gen tsoutput type-checks undernoUnusedLocalsandnoImplicitOverride.
1.0.0-beta.2
23 September 2026. Release notes
Sealed runs
@hostsnames where a value may go. Under an AI agent, or withpenv run --sealed, the command holds a placeholder and a proxy insidepenv runputs the real value into requests to those hosts only. Echoed values come back as the placeholder.postgres://andredis://URLs go through local proxies that log in with the real credential.- penv can seal
AWS_SECRET_ACCESS_KEY: the proxy re-signs each request with SigV4.
Local-first values
- Value files layer in the order
.env,.env.local,.env.<env>,.env.<env>.local; the environment comes from@currentEnv. - Computed values:
${KEY}, filters,match,if,fallback,random(N), andpenv()to read another environment, project or org.@assertand@rotate. penv encryptandpenv decryptkeep the sensitive values beside.env.schemaencrypted, with the key in the OS keychain.penv bundlepacks one environment into.penv/<env>.bundle, whichpenv runreads withPENV_BUNDLE_KEYinstead of reading from us.penv why KEYsays where a value comes from and how penv treats it. It does not print the value.
Checks
penv checkreads the code: undeclared reads and unused keys are named;--strictfails on undeclared reads. A value file holding a secret that git tracks fails the check.penv scanfinds secret values committed to files. After a build, client output is scanned for secrets.- Masking is on by default in every
penv run, and inside Node, Bun, Deno and Python through a preload.
Generated code and deployment
penv genwrites go, rust, php, java and csharp as well as ts and py.gen tsexports oneenvfor server and client code..penv/config.tomlholds every setting; target settings moved into[targets.<name>]and old.penv/targets/folders migrate on the nextgen.- Providers:
@penv=<provider>:org/project,--provider, and[providers.<slug>] url. - ECS task roles, EKS IRSA and Pod Identity, an AWS Lambda layer, and a signed
scratchimage.
1.0.0-beta.1
22 September 2026. Release notes
Projects and environments
penv projectls | new | rename | rmandpenv envls | new | rename | copy | rmmanage the tree from the terminal.-p <project>is optional inside a linked folder. A rename of the project a folder is linked to rewrites its@penvline.rmasks a person to type the name; an agent or a pipe gets exit 4 and the command to run.penv env copycreates an environment with another one's keys and settings. Values are never copied.
Everyday
penv ls --env <name>. In a linked folderpenv lsreads the cloud, so a deleted.envno longer lists every key as missing.penv pullin a folder with no@penvline links it to a project the account already has, asking which one when there are several.- Styled output at a terminal: notes, warnings, refusals, arrow-key pickers, spinners on network calls, and tables. JSON, agent and piped sessions are unchanged.
- Grouped
penv --help.hook,schemaandcompletionsstill work but are hidden from it.
1.0.0-alpha.4.4 to 4.6
21 September 2026.
- A schema with a
@penvline reads the cloud even when.envexists; the local file stands in only while offline. penv pullwrites every key it can and lists the ones it left out. A value with quotes and line breaks is written in the quote it lacks, so only a value using all three quote marks is left out. A carriage return never blocks a pull.- The generated TypeScript spells every key out (
process.env.NAME), which bundlers can inline. - Messages say what happened, name the keys, and give the command that fixes it.
1.0.0-alpha.4
16 September 2026. Release notes
Push
penv pushrefuses before we hear anything, and names the fix. penv refuses a missing or valueless.envasnothing_to_pushinstead of creating an empty project, an--envthe project does not have asno_such_environment(listing the ones that exist), and an--orgthat contradicts the schema header asorg_mismatchinstead of ignoring it.- penv creates a brand-new project with the environment the push is for, not only
development.
Repository
- The CLI now lives at
penvhq/penvhq. The npm packages carry that repository, which is what provenance checks them against.
1.0.0-alpha.3
10 September 2026. Release notes
Signed releases
- A penv release key signs every release checksum file, and a release stays a draft until that signature exists.
penvandinstall.shverify a download against a key list compiled into the build. That list was first populated with this release.manifest.jsonships beside the binaries with every release, and this documentation is generated from it.
Coding agents
penv revealunder an agent session now files an approval in the console and exits 4 rather than printing anything.--approval <key>redeems that approval once, bound to the person who granted it and to the agent session that asked. A wrong key never spends an approval.
Windows
penv runresolves a bare program name throughPATHEXT, so the.cmdshims a package manager installs start on Windows.
Typed access
- Every target option now declares its default and the values it allows.
penv gen --optionslists them with a line of description each, and bothinitandgensay where your remembered answers live.
1.0.0-alpha.2
9 September 2026. Release
Installing and upgrading
- Two installers shipped:
install.shfor macOS and Linux,install.ps1for Windows. Both compare a download against the release digest and install nothing that does not match. @penvhq/clibecame an npm package that runs the platform binary, for people who want npm to own it.- Windows arm64 joined the published builds.
penv.cloudbecame the one release address the binary reads.penv upgradearrived, along withpenv completionsfor your shell.
Adoption
- The one dotenv escape now round-trips, so a value written with
\ncomes back the way it went in. penv initasks where generated files should go instead of guessing.- Guards and language targets work in any repository layout rather than only the shapes penv shipped with.
Typed access
- The TypeScript target applies the defaults declared in your schema.
- A
[options]key_casesetting gives you camelCase property names.
1.0.0-alpha.1
8 September 2026. Release
The first release of the Rust rewrite. Everything in it is new.
The binary and local mode
- One committed
.env.schemareplaces every config file. penv initwrites that schema from an existing.env.penv checkreports what is wrong with it, andpenv lslists what is in it.penv runvalidates, then puts the values in the child process only.
Cloud mode
penv loginsigns a person in through a device code, andpenv pushsends an environment up.penv pullwrites a plain.envback down.penv setandpenv unsetchange one key.penv revealprints one value.penv machineenrollgives a host that can prove nothing a key of its own.- Four credential kinds: a platform token, an AWS signature, a bound keypair, and a plain
pck_bearer. - An encrypted local copy of each environment, sealed under a key in the operating system keychain and bound to the credential that fetched it.
Coding agents
- penv detects an agent session. Its own output becomes JSON, and every sensitive value in the child's output is masked.
penv guardandpenv hookwrite the rules that keep an agent's tools away from.env.
Typed access
- Language targets for TypeScript and Python, generated from the schema rather than read at runtime.
Releases
- One archive per target with a checksum in the release.
Next: upgrade from 0.16.