Changelog
What each release actually changed, newest first. Upgrade instructions and what an update does not touch are on Updating; the exact pinned version of every binary is on Pinned versions.
0.8.13 — 9 October 2026
Section titled “0.8.13 — 9 October 2026”Two Windows bug reports (8 and 9 October) drove almost everything here, plus the fixes the following PUBLISH-TESTING audit found on its own. Nothing new was added; Windows catches up to what macOS and Linux already did, and a handful of Valet/Herd and reliability edges get closed.
Windows: a missing Visual C++ Redistributable is named before anything starts
Section titled “Windows: a missing Visual C++ Redistributable is named before anything starts”php.net’s PHP and Oracle’s MySQL builds link VCRUNTIME140.dll, and a fresh Windows 11 install
does not always have it. Before, that meant MySQL’s first start died with a bare exit code and
a Retry could hang on a hidden Windows error dialog. Now rex doctor’s new Runtime line, a
Services banner, and Start all itself all check the redistributable’s own registry entry
(VC\Runtimes\X64) before touching MySQL or PHP:
- Missing entirely — blocking: “PHP and MySQL need the Microsoft Visual C++ Redistributable (x64), and this PC does not have it — no database or PHP can start until it is installed. Install it from Microsoft, then Start all.”
- Registered but older than 14.30 (the Visual Studio 2022 toolset PHP 8.4 and 8.5 need) — a warning only: MySQL and PHP up to 8.3 still start.
The banner, rex doctor and the interactive installer all link Microsoft’s own
https://aka.ms/vs/17/release/vc_redist.x64.exe; a silent /S install stays silent and offers
nothing. Separately, rex itself used to exit with 0xC0000135 before printing anything on a
PC without the runtime — 0.8.12’s rex.exe linked the same DLL dynamically. It is now linked
statically, so the CLI runs regardless.
Windows: PHP 7.4, 8.0, 8.1, 8.4 and 8.5 start — not just the default 8.3
Section titled “Windows: PHP 7.4, 8.0, 8.1, 8.4 and 8.5 start — not just the default 8.3”One extension list, read off 8.3’s official zip, had been written into every PHP minor’s ini.
The zips differ — 8.4 and newer moved IMAP to PECL, zip is built in before 8.2, gd ships as
php_gd2.dll on 7.4 — and rexenv’s preflight refuses any startup warning, so those five minors
could never start on Windows. A user hit it on 8.4, because every earlier proof had run on the
8.3 default. Each minor now loads the extensions its own zip actually ships.
Windows: PHP 8.4 and 8.5 get IMAP
Section titled “Windows: PHP 8.4 and 8.5 get IMAP”Matching macOS and Linux, where every PHP build compiles it in: the official PECL php_imap
build is downloaded alongside the PHP zip for 8.4 and 8.5.
rex doctor stops misreading rexenv’s own PostgreSQL as another program
Section titled “rex doctor stops misreading rexenv’s own PostgreSQL as another program”Windows starts PostgreSQL through pg_ctl, which writes its own command line with
forward-slashed paths (C:/Users/…/rexenv/data/…). The doctor’s port-holder check compared
against a backslashed one, so it reported rexenv’s own postgres.exe as a foreign program
holding its port — with a Stop-Process command that would have killed it. Fixed in the
matcher.
A Caddy reload cut off by its own restart no longer reads as a broken site
Section titled “A Caddy reload cut off by its own restart no longer reads as a broken site”A reload that changes the Caddyfile (adding a site, not rotating a cert) can have its admin
connection severed by the admin server it is in the middle of replacing — the new config IS
applied, but the reload command itself reports exit 1. Creating several sites in a row on a
Windows VM turned up new sites stuck on setup incomplete while they were already serving.
A failed reload is now retried once before it is reported; a second, no-op reload confirms the
first succeeded, and a genuine config error still fails with Caddy’s own last line attached.
Sites from a git repository
Section titled “Sites from a git repository”A repository’s composer.json can name a PHP extension your site’s PHP build does not load,
not only a PHP version — rexenv now checks both at the clone phase, against the site’s real
php -m, before Composer runs:
this repository needs the PHP extension imagick (required by spatie/image), and PHP 8.4 as rexenv ships it does not load it — composer install would refuse it. Nothing was installed; the project cannot run on this site’s PHP as it is.
A migration that stops half-way used to fail every Retry with Composer’s own
1050 Table '…' already exists and no explanation — MySQL commits each CREATE TABLE on its
own, so the half-made tables survive while Laravel never records the migration as done. It is
now named instead:
A table this migration creates already exists, so an earlier run stopped part-way: MySQL keeps each table it created, while Laravel never recorded the migration as done. rexenv drops nothing and marks nothing as migrated. Export the database first (Databases → Export), then either drop that migration’s half-made tables or let the migration skip tables that exist, and Retry.
And Stop all (the footer, the tray, rex stop / rex restart, and the MCP tool) now
refuses while a site is still being provisioned, naming it, rather than stopping the database
under its own install or migration:
Nothing was stopped: shop.rex is still being set up, and stopping the stack now would cut off its install or migrations mid-way — a half-applied migration does not undo itself. Wait for it to finish, or Cancel it on its card, then Stop all.
Moving from Valet or Herd
Section titled “Moving from Valet or Herd”- The “your .test sites stopped resolving” banner is keyed to the moment Valet or Herd retakes a TLD, not just the bare TLD name — so dismissing it, re-taking the domain from Import, and losing it again a second time shows the banner again instead of staying dismissed.
- Settings no longer describes a domain Valet or Herd took back as “rexenv answers this” — it says the domain was taken back.
- The import’s progress card now shows a row the import queue rejected (its folder moved since the scan, say) as skipped and done, instead of leaving it reading “waiting” forever.
- An import whose first phase refuses outright before the progress card has subscribed used to settle on “checking the source database · 0%” and sit there; the card re-reads the job after subscribing, so it shows the refusal instead.
- After a database connect or revert, the Database tab’s import card now drops any of that site’s earlier, settled import jobs — including a neutral “this site already uses rexenv’s own copy” result — instead of showing it as a stuck error with a Retry that could only refuse again.
Reliability: a slow edge is no longer mistaken for a foreign proxy
Section titled “Reliability: a slow edge is no longer mistaken for a foreign proxy”Under a large database copy, Caddy could take just long enough answering the watchdog’s :443
probe that two polls in a row read as unreachable — folded into “foreign”, with a toast naming
“another local proxy” and a sudo kill command that would have killed rexenv’s own edge.
The watchdog now tells a slow-but-genuine answer from a different program actually answering,
and offers a kill command only when it can name that other program.
macOS: “Remove system changes” deletes the CA itself, not only its trust
Section titled “macOS: “Remove system changes” deletes the CA itself, not only its trust”Untrusting rexenv’s local certificate authority removed the trust setting but left the
rexenv Local CA item itself in your login keychain — inert, but unreachable by
brew uninstall --zap --cask rexenv, which cannot see inside Keychain Access. “Remove system
changes” now also runs security delete-certificate on every copy of the CA.
0.8.12 — 8 October 2026
Section titled “0.8.12 — 8 October 2026”A curl inside PHP that finds your other .rex sites on macOS; a one-click repair for WordPress
sites an old download left short of files; OpenLiteSpeed as a third per-site server on macOS
and Linux — and the fixes the 0.8.11 update runs found, on all three systems.
macOS: a site’s own curl reaches your other .rex sites
Section titled “macOS: a site’s own curl reaches your other .rex sites”Under PHP 8.1–8.5, curl_init("https://other-site.rex/") failed with “Could not resolve
host” while gethostbyname() on the same name answered 127.0.0.1. The builds’ libcurl was
linked to c-ares, which reads /etc/resolv.conf and /etc/hosts and never macOS’s
/etc/resolver/rex — the file that sends .rex to rexenv. It came in unasked: the build tool
turns c-ares on whenever it is in the build, and swoole depends on it. WordPress itself was
covered by rexenv’s mu-plugin; a plugin calling curl directly, and every non-WordPress app,
were not.
rexenv’s macOS builds of 8.1.34, 8.2.32, 8.3.32, 8.4.23 and 8.5.8 are rebuilt — the same
patch versions — with a libcurl that uses the system resolver, and the build now fails if curl
links c-ares again. 8.0.30 still has the problem: it is the one PHP rexenv does not build,
the mu-plugin stays for it, and rex doctor’s PHP curl note now names 8.0 alone.
The version numbers did not change, so a cached build is told apart by its recorded checksum:
one that no longer matches the pin is downloaded again, once. What the source does not tell
us is whether a PHP pool already running when you update moves to the new build before it is
restarted — so restart once (rex restart, or Stop all and Start all) before testing a curl
call. Linux and Windows use other publishers’ 8.x builds, which this release does not change.
WordPress sites created by 0.4.0–0.7.1: told, and repaired in one click
Section titled “WordPress sites created by 0.4.0–0.7.1: told, and repaired in one click”Those versions downloaded WordPress as a tarball that PHP read with every file name over 100
characters cut short, so the site was created without its long-named files — for 7.1, 25 core
classes in wp-includes/php-ai-client and 14 default-theme fonts and patterns — and nothing
said so. 0.7.2 fixed new sites; existing ones stayed broken unless somebody suspected it.
A site that is missing them now says so on its Overview, and Repair core files puts them
back: it re-downloads that version’s core, and puts back only the missing theme files, each
checked against wordpress.org’s MD5 and written only where nothing is. The database,
wp-config.php and your own files in wp-content are not touched. rex doctor has a WP
core line naming every affected site. The check needs wordpress.org’s file list, so offline
it says nothing on the Overview and not checked in rex doctor — never “clean”. Details on
WordPress.
OpenLiteSpeed, a third per-site server — macOS and Linux
Section titled “OpenLiteSpeed, a third per-site server — macOS and Linux”rex site server <domain> openlitespeed, or the New Site dialog’s OpenLiteSpeed (.htaccess +
LSCache). It reads .htaccess, ships the LSCache module on — so the LiteSpeed Cache plugin
behaves as it does on a LiteSpeed host — and sends .php to the site’s shared PHP pool. It is
rexenv’s own build, 1.9.3, on loopback ports 8400–8499; no macOS binary existed to ship.
There is no Windows build, and Windows refuses it at create and at switch:
OpenLiteSpeed has no Windows build — it exists for macOS and Linux only. Choose Nginx for this site.
Two limits, both on Web servers: OpenLiteSpeed reads a
.htaccess once, so rexenv restarts the site when the root one changes, and a subdirectory
edit needs rex site restart; and an environment variable whose value contains both ' and
" cannot be passed, so it is refused for an OpenLiteSpeed site.
A switch that cannot happen is refused before it downloads
Section titled “A switch that cannot happen is refused before it downloads”Switching a Windows site to OpenLiteSpeed, or a Linux site to Apache, used to start the download first — and fail with “1 of 1 downloads failed — fix the connection …” and a failed row in the download panel, instead of the sentence saying the server does not exist on that system. The refusals now come first.
macOS: no more “Background Items Added” pile-up
Section titled “macOS: no more “Background Items Added” pile-up”Every Start all over a running edge unloaded and re-registered its LaunchDaemon, and macOS posts a “Background Items Added” card for each registration — eleven were counted on one test machine. An edge whose files have not changed is now restarted in place, and the daemon’s keep-alive is a switch that Stop all lowers and Start all raises, so a Stop all/Start all pair registers nothing.
Stop all also stops an edge that was still coming up after a boot. It used to ask only
what the app believed, and the app believed it was stopped, so rex stop said done while the
edge kept serving.
Windows: a silent setup.exe /S replaces the running copy
Section titled “Windows: a silent setup.exe /S replaces the running copy”Run over an installed rexenv, setup.exe /S exited 0 and left the old rexenv.exe in place —
the DNS agent is rexenv.exe too, always running, and the silent installer skipped the locked
file without a word. Now a silent install ends the agent’s task and waits for every rexenv.exe
of yours to exit, replaces the files, and starts the agent again on the new binary; the app is
not reopened, because silent means silent. A file that still cannot be written fails the
install with a non-zero exit code instead of being skipped.
The uninstaller closes rexenv first — a running app put the agent’s task straight back —
then ends and deletes \rexenv\dns-agent, so the resolver no longer outlives the app. The
.rex rule and the certificate still need Remove system changes in the app; the uninstaller
says so. See Uninstall.
Adminer’s import cap reaches PHP on Windows. It was sent in a form the Windows PHP pool
ignores, so a large dump was cut by PHP at the default limit after nginx had let it through. It
is now also written as a .user.ini in Adminer’s folder, which every OS’s PHP reads.
Linux: a route from an older rexenv is not a missing one
Section titled “Linux: a route from an older rexenv is not a missing one”Updating from 0.8.10 opened onboarding’s Welcome screen over existing sites, reporting the
resolver missing while .rex still resolved — the new build did not recognise the old build’s
route as its own. A route that still routes now counts as installed, whoever wrote it, with a
notice: “Your .rex route was set up by an older rexenv. It works — re-apply it once (one
administrator prompt) so this version’s route is in place.” It shows under rex status’s DNS
line and in Settings with a Re-apply button; rex tld --repair rex does the same. The
update itself never prompts for it. See
DNS not resolving.
Smaller, every OS
Section titled “Smaller, every OS”- The update card gave one download two sizes — the header in 1024s, the sentence under it in 1,000,000s. Both use 1024 now.
- A serving site with an empty log was told to “start the site to see logs here”. It now reads “No recent activity yet — new lines appear here as they are logged.”; the start hint is only for a site that is not serving.
relaunch.log. After an in-app update, the helper that reopens rexenv writes what it waited for and how the launch ended torelaunch.login rexenv’slogsfolder. A reopen that did not happen used to leave no trace anywhere.rex status | head— anyrexcommand whose reader closes the pipe early — ended in a Rust panic on stderr. It now ends quietly, exit 0.
0.8.11 — 30 September 2026
Section titled “0.8.11 — 30 September 2026”The fixes the 0.8.10 release week found, on all three systems; a signed apt repository on Linux — and the first release published from rexenv’s own source repository, which is now public.
The source is public, and the releases come from it
Section titled “The source is public, and the releases come from it”rexenv/rexenv went public on 30 September 2026, under the
Apache License 2.0, and this is the first release drafted there instead of on the Homebrew tap.
The downloads lived on the tap until now for one reason: the source repository was private, and
a private repository’s release asset answers 404 to the unauthenticated request that both a
browser and brew make.
Nothing changes for an installed copy. The in-app update reads the same signed descriptor as before; what moved is the download address inside it, and every build since 0.6.0 accepts both hosts — so 0.8.10 updates to this release from the new one, and a descriptor published before the move still verifies. The Homebrew cask and the one-command installers stay in the tap and fetch from the source repository. 0.8.8, 0.8.9 and 0.8.10 are mirrored there byte for byte; older releases stay on the tap. Bug reports go to the same repository’s issues.
Linux: sudo apt install rexenv
Section titled “Linux: sudo apt install rexenv”rexenv has a signed apt repository, https://rexenv.github.io/apt, and the one-command
installer uses it: it adds the repository — the key travels inside the script — and installs
the rexenv package from it, so later versions also arrive with sudo apt upgrade. That is
the same package the in-app update installs, so the two never disagree about what is on the
machine. The repository carries the three newest releases for amd64 and arm64, and apt refuses
one signed by any other key.
Two edges, both handled by the script rather than left to you. The repository is published
right after a release, so while it lags a brand-new one the release’s .deb is installed
directly and the repository stays configured. And an installed rexenv that has no source
line gets the line added and nothing else — the package is not touched.
Install has the four lines to set it up by hand, and the key’s fingerprint.
Every OS: a site being created says “Setting up”
Section titled “Every OS: a site being created says “Setting up””For the whole of a first install, the row behind the New Site dialog read setup incomplete with a Retry button — the list said the setup had failed while the card in front of it was still installing. The row is saved when the job starts and marked finished only when it ends, and the list read that mark alone.
It now asks whether a job is running for that site, whoever started it — the dialog, rex site create or an agent. Running: an amber Setting up, and no Retry. The badge and Retry appear
only for a job that died or was cancelled. Not answered yet: neither, so the row never flashes
“failed” first.
From Git: the PHP requirement is read before Composer runs
Section titled “From Git: the PHP requirement is read before Composer runs”Cloning a repository that needs a newer PHP than the site runs used to fail inside composer install, with the reason two screens away in the log. rexenv now reads the cloned
composer.json’s require.php first and says which version to switch to:
this repository’s composer.json requires PHP >=8.4.1, and this site runs PHP 8.3 — composer install would refuse it. Nothing was installed; switch the site’s PHP version to 8.4 or 8.5 (Site → Settings) and Retry.
A constraint it cannot read never refuses — Composer’s own check still runs, and its refusal now names the wanted and the running version too. There is no hint before the clone: the requirement is in a file, and the file is not there until the repository is.
A stopped stack: the WordPress and Database tabs say so
Section titled “A stopped stack: the WordPress and Database tabs say so”With the stack stopped, a site’s WordPress tab spun Loading plugins… for as long as WP-CLI took to give up on a database that was not running, and its Database tab showed Adminer’s address above a blank frame. Both now name the service that is down — MySQL is stopped, or rexenv’s web services are stopped — say in one clause what the tab cannot do without it, and carry a Start all button. Nothing is asked of a service that is down, and the tab comes back by itself once it is up.
rex site list and rex site info say which kind of “not serving”
Section titled “rex site list and rex site info say which kind of “not serving””Both printed serving about a site whose setup never finished — no server block, no
certificate — because they read what the stack believed rather than what answers. The list’s
STATE column now has four words, because they have four fixes: serving, stopped (you stopped
it), incomplete (setup never finished) and down. site info gives one line per case, with
the fix in it — blocked — another server answers :443, not rexenv’s edge (rex doctor).
Watchdogs that reported what had not happened
Section titled “Watchdogs that reported what had not happened”- “DNS stopped unexpectedly — restarted automatically”, about a resolver that was answering throughout. The check was one datagram with half a second to reply, and the first miss restarted the agent; a busy machine answers late. It now asks up to three times, and restarts only after two silent polls in a row — about twenty seconds.
- “Another local proxy” on port 443 that was not there. One missed probe is exactly what
rexenv’s own edge reload produces, so every
rex site createcould raise the toast and clear it again. The edge is called blocked only after two consecutive misses. - “Removed outside the app”, twenty seconds after Remove system changes removed the edge itself. The step now tells the watchdog what it did.
- “The HTTPS edge needs Start all” at login, on macOS and Linux, while the edge’s own boot service was merely still starting — the rough edge 0.8.10 left open. Login-start now waits up to two minutes for an edge the system is already bringing up, and asks for a click only when nothing is going to start it.
macOS and Linux: a power cut no longer leaves an empty service file
Section titled “macOS and Linux: a power cut no longer leaves an empty service file”Found on Ubuntu: Start all, a power cut about thirty seconds later, and
/etc/systemd/system/rexenv-edge.service came back zero bytes — and systemd reads an empty
unit as masked, so the edge never started again until the next Start all. Whatever a privileged
step writes is now flushed to disk before its prompt returns, and the boot and login files
rexenv writes itself go through a write that survives the same cut. Windows is unchanged: the
registry, the certificate store and Task Scheduler are the system’s own durable stores.
- Redis, MariaDB, Apache and Xdebug install without the Xcode Command Line Tools. They come
as Homebrew bottles whose library paths must be rewritten, and that step was
install_name_tool— which is the tools, so on a Mac that had never seen a compiler all four failed. rexenv rewrites those paths itself now. What is proven: the rewrite on a real bottle library, checked against Apple’s own tools. What is not yet: the four installs on a Mac with no tools at all. Cloning from Git still needsgit, which on macOS arrives with those tools. - Replacing rexenv.app in Finder no longer produces “rexenv quit unexpectedly”. The
.rexresolver is relaunched the instant it exits, including halfway through the copy, and macOS kills a program started from a half-copied bundle. It now waits until the new copy verifies before it starts from it. Login Items lists the resolver as rexenv. - “Bootstrap failed: 5: Input/output error” on the first Start all after replacing the app, which left the edge down until a second Start all. The install now waits for its own removal to finish before it installs again.
- A copy macOS is running from a temporary path is not recorded. An app that was unzipped or copied rather than dragged from the disk image runs from a read-only path that disappears when it quits; rexenv refuses to write that path into its resolver’s or login item’s definition — “Move rexenv.app to Applications and relaunch; DNS is served in-process until then.”
Windows
Section titled “Windows”- A second account never takes the
.rexresolver task from the first. The\rexenv\dns-agenttask has one name per machine, and registering it again overwrote it silently — so a second administrator’s launch moved it to their own sign-in. The launch now refuses, names the holder, and answers.rexitself while it runs: “rexenv is one account per machine, so this launch will not take it over”. - A login start stays a login start when rexenv has to relaunch itself through Explorer to leave a job that confines it — a launcher, a scheduled task. Explorer passes no arguments, so that launch used to arrive as one you made: a window, and no Start all.
- Re-applying the
.rexroute keeps therexenv0link. Every re-apply deleted and re-created it, and systemd-resolved’s late clean-up of the old link could take the new one’s settings with it: measured, 3 of 50 re-applies came back with no DNS scope, and.rexstopped resolving. The link now stays and only its DNS settings are reset; removing it is the uninstall’s job. And the route counts as installed only while systemd-resolved is actually routing through it — a link that had lost its server used to read resolver installed. - The in-app update’s password dialog names the update: “rexenv needs administrator permission to install the update it downloaded — the new rexenv package.” The sentence comes from a file the package installs, so the update to this release still shows the setup sentence, and the one after it shows the new one.
- A
certutilthat is there but cannot run says so, with the reason and the fix —sudo apt install --reinstall libnss3-tools— instead of “io error: Permission denied (os error 13)”. - “WebKitWebProcess closed unexpectedly” after an update — a mitigation, not a fix. The app’s windows are now closed before the process exits. The report showed on 1 of 2 updates and on none of 4 plain quits, and the change sits in the side that is exiting, so the update to this release can still show it. Whether it is enough is not known yet.
Also in this release
Section titled “Also in this release”- Adminer can go back. Once a newer Adminer is chosen, its card offers Back to the version this rexenv ships — the same download-and-check as the update, in the other direction.
- An update published for another operating system is never offered, downloaded or accepted. Until now that rested on the publisher alone.
- A new sub-site on a subdomain multisite is created as
https://, like the main site. Existing sub-sites are not rewritten. rex site create shop.rexnames the siteshop, as the dialog does, rather than the whole domain.- A plugin that is a single file with no folder — Hello Dolly — no longer offers a terminal in a folder it does not have.
No database migration since 0.8.10.
0.8.10 — 29 September 2026
Section titled “0.8.10 — 29 September 2026”One login setting instead of two, databases that come back after an unclean reboot, and the fixes the Linux and macOS test runs found after 0.8.9.
Every OS: “Start rexenv at login” is one toggle
Section titled “Every OS: “Start rexenv at login” is one toggle”Until now the wish my sites are back after a reboot was split across two switches — Open rexenv at login and Start services when rexenv opens — and either one alone did something you did not mean: only the first on gave an idle app at sign-in, only the second on started the whole stack every time you opened the window by hand.
There is one toggle now, in Settings → Services. Its description is each operating system’s own sentence; on macOS:
rexenv opens in the menu bar when you sign in to your Mac (a macOS login item) and runs Start all — your sites come back after a reboot without a click. Never downloads or prompts at login.
On Windows it names the notification area and a startup app, which Task Manager’s Startup tab
can also turn off; on Linux the system tray and an autostart entry in ~/.config/autostart.
Opening rexenv yourself no longer starts anything. The second setting is retired and swept
from the database at launch, and rex config set start_services_on_launch is refused like any
key nobody has ruled on. If you relied on it, turn on Start rexenv at login.
The login start survives a race the drafted release lost: with macOS’s Reopen windows when logging back in on — the restart dialog’s default — macOS relaunched rexenv first, without the login flag, and the login item’s launch found it running, handed off a plain “open” and exited with the stack down. The handoff now carries the login, and the running copy runs Start all instead of raising its window. One rough edge is open: a login a few seconds after boot can show “the HTTPS edge needs Start all” while the edge’s own boot daemon is merely still coming up; it adopts the edge by itself within about a minute, and the sentence is wrong to ask for a click.
macOS and Linux: MySQL and PostgreSQL start after an unclean shutdown
Section titled “macOS and Linux: MySQL and PostgreSQL start after an unclean shutdown”A killed server — a reboot that ended it with SIGKILL, a hard power-off — leaves
mysql.sock.lock or postmaster.pid behind with its old process id inside. After the boot
that id belongs to some other program, and both servers ask only whether the id exists:
PostgreSQL refused with “lock file already exists — is another postmaster running?”, mysqld
aborted with “Unable to setup unix socket lock file”, the health watchdog respawned each three
times and gave up, and every site stayed down until two files were removed by hand.
A start now asks the stricter question — is that process id alive and is its command line
the server that wrote the file? — and removes the lock (and MySQL’s socket) when it is not. A
live server’s lock is never touched, so a start on a server that really is running still fails
the honest way. The log says what was removed and why:
removed a stale lock before starting — pid 2009 is now /usr/bin/rexenv, not the server that wrote it.
Linux: one password dialog for setup, one for Remove system changes
Section titled “Linux: one password dialog for setup, one for Remove system changes”polkit keeps no grant between rexenv’s dialogs, on purpose — so every root step was its own
dialog unless the steps were one command. First-run setup asked twice, two minutes apart: the
.rex route, then the certificate authority’s system-store file. The two ride one dialog
now, and the store’s leg is skipped when the store already holds the certificate. Remove
system changes had the mirror image — a second dialog for the system-store file alone, and
until it was answered curl and PHP still trusted the CA. It is one dialog too.
First run and Uninstall now count two prompts and one.
WordPress from a repository: Bedrock sites finish and serve
Section titled “WordPress from a repository: Bedrock sites finish and serve”The first real Bedrock provision — roots/bedrock through New site → WordPress → From Git —
cloned, installed core through Composer, wired .env and ran wp core install, and then failed
setting the admin password: that step spelled its --path by hand and pointed at web/, and
WP-CLI said “This does not seem to be a WordPress installation.” The salts stayed
generateme and the site never served. Fifteen places built that flag by hand; there is one
builder now, resolving the core under web/wp as the install step already did. rex wp on the
same site had failed one level up — the manager was handed the project root where the vhost
serves web/ — and works on the served root.
Every OS: two smaller sentences
Section titled “Every OS: two smaller sentences”- A re-run of setup on an install that already has sites — an update whose route script went stale reaches the onboarding pages too — ended with Create your first site over three existing ones. The finish page reads the sites count: Open your sites, and “rexenv will serve your 3 sites instantly”.
- A build that cannot check for updates at all said “couldn’t reach the update server” when
nothing had been reached. It says why instead — on Linux, a copy that is neither the
.debinstall nor an AppImage: “This rexenv is not a .deb or an AppImage install, so there is no update descriptor for it — nothing here says whether a newer rexenv exists.”
No database migration since 0.8.9. The .deb update from 0.8.9 comes back by itself, as 0.8.9
promised; Ubuntu may put up a “WebKitWebProcess closed unexpectedly” report over the new
window at that moment — it is the old app’s web view dying as it exits, not the update failing,
and it is open work.
0.8.9 — 28 September 2026
Section titled “0.8.9 — 28 September 2026”The fixes the first Linux release’s own test runs found — above all, that its .rex route
routed nothing.
Linux: .rex is answered by rexenv’s own route
Section titled “Linux: .rex is answered by rexenv’s own route”0.8.8 sent .rex through rexenv0, a dummy network link with .rex as its only routing
domain — and gave the link no address. systemd-resolved gives no DNS scope to a link without
a real address, so the route sent nothing to rexenv’s resolver. It looked fine on every machine
it was tested on, because each of them asked a host that was itself running rexenv — the test
VM’s upstream DNS is a Mac, WSL’s is Windows — and those answered *.rex on rexenv’s behalf. On
a machine with no rexenv next door, .rex did not resolve.
The link now carries one address, 192.0.2.53/32 — from TEST-NET-1, a range reserved for
documentation that no real network uses. A route written by 0.8.8 reads as not installed, so
after updating rexenv offers its setup step once more; accept it and the route is rewritten.
The check that had passed now asks systemd-resolved which link answered, not only what the
answer was. First run has the whole route.
Linux: the app comes back after an in-app .deb update
Section titled “Linux: the app comes back after an in-app .deb update”The update installed, and then the app stayed closed: the relauncher was started from the
running binary, which dpkg had just replaced — the kernel reports it as
/usr/bin/rexenv (deleted). It now starts from the new /usr/bin/rexenv. The fix lives in the
side of the swap that is already running, so 0.8.8 → 0.8.9 still needs opening by hand once.
An AppImage was not affected.
Every OS: a refused update says it was refused
Section titled “Every OS: a refused update says it was refused”Two fixes to the same card:
-
Each update document keeps its own replay record. One record served every descriptor, and the number in it only moves up — so a copy that had once read another platform’s document, as early Windows builds and development
.debpackages did, then refused its own platform’s releases as replays, indefinitely. -
A refusal is no longer reported as a network failure. The card read “couldn’t reach the update server” for all of it; a Windows machine showed that sentence for a week while it was in fact refusing every release. It now says:
The update server answered, but rexenv refused what it published: the reason. Nothing here changes until a descriptor this copy accepts is published.
Windows: helper commands no longer come back empty
Section titled “Windows: helper commands no longer come back empty”The step that stops services inheriting rexenv’s own handles ran beside other threads while they were starting processes, and cleared the output pipes those had just made — so a command rexenv ran at the same moment could exit having printed nothing. It now runs once, as the process starts, before there is a second thread.
0.8.8 — 27 September 2026
Section titled “0.8.8 — 27 September 2026”The first release with Linux, and the first built entirely by CI — every platform from one tag.
Ubuntu 22.04 or newer, x86_64 and arm64, as a .deb or an AppImage, each with a .sha256
beside it. 22.04 is the first LTS with the WebKitGTK the app’s window needs, and the release is
built on it. Install and First run have a Linux tab each.
- DNS through a dummy network link, not a systemd-resolved drop-in. The drop-in — the obvious
copy of macOS’s
/etc/resolverfile — was tried first, and it sent every name on the machine to rexenv’s resolver. A link whose only routing domain is.rexsends nothing else. - The local CA in both stores. Browsers read their NSS databases and
curland PHP read the system store, so rexenv writes both — including the snap Chromium’s own database and every Firefox profile’s, because a snap cannot see your~/.pki. CA trusted in Settings means both hold the current CA. - polkit for every system change, through your desktop’s own password dialog. From the
.debit names rexenv and what it is about to change; the package depends onpkexec | policykit-1, because Ubuntu 26.04 has nopolicykit-1package. - In-app updates: the
.debthroughdpkgbehind one password dialog, an AppImage swapped in place with none. Each package kind and architecture has its own signed descriptor. - PHP 7.4 is rexenv’s own static build, for both architectures, as on macOS.
- MySQL and PostgreSQL start on 24.04 and 26.04, where Ubuntu renamed libraries they load
(
libaio1t64,libxml2-16): a private symlink under rexenv’s own data folder bridges the old name, and the.debdeclares every library either engine needs. - Not in this build: Apache, Xdebug, Redis and MariaDB, each refused with a message naming Linux. FrankenPHP ships.
Every OS, built by CI
Section titled “Every OS, built by CI”The universal disk image, the Windows installer and update zip, and four Linux files now come from one tag on GitHub Actions — one draft release with every artefact, or none. Nothing in a release is built on a developer’s machine any more.
Adminer 6.1.1 on fresh installs
Section titled “Adminer 6.1.1 on fresh installs”The pin a new machine downloads moved to 6.1.1, the version the update path was already offering — until now a fresh install got a database console one release behind.
0.8.7 — 23 September 2026
Section titled “0.8.7 — 23 September 2026”rexenv runs on macOS 13 and 14. And the .rex resolver stops second-guessing you.
macOS 13 and 14
Section titled “macOS 13 and 14”Until now the floor was macOS 15, because one pinned binary needed it. From this release the app runs on macOS 13 (Ventura) and 14 (Sonoma) — with a different pin for the components whose current build does not load there, and an honest refusal for the two that have no build at all.
What decides it is the macOS the app is started on, worked out at every launch and never stored. On 15 or later nothing changes: every component at its newest version. On 13 the app uses MySQL 8.4.3 / 8.0.40, MariaDB 12.0.2 / 11.4.8, Redis 8.2.1, Apache 2.4.65, Xdebug 3.4.5 (PHP 8.1–8.4 — none for 8.5) and cloudflared 2025.4.0; PostgreSQL and PHP 8.0 are not available there. On 14 only two rows differ from 15: cloudflared 2025.4.0, and PostgreSQL 16.4.0 as the one version. Every number is on Pinned versions, printed on the row.
The first launch on such a Mac says so once, in the wizard:
This Mac runs macOS 13.6 — rexenv works here with older versions of some components and without PostgreSQL and PHP 8.0. Everything is available on macOS 15 or later.
After that, anything not available stays listed with the reason wherever you could choose
it — the Databases page, the New Site dialog, rex db versions, the agent tools — all with the
same sentence:
Needs macOS 14 or later — this Mac runs macOS 13.6.
Upgrading the Mac later moves you forward on the same data. A 13 → 15 upgrade was done on the test machine before shipping: the next launch started MySQL 8.4.6 on the database 8.4.3 had written (MySQL’s own in-place upgrade ran), MariaDB 12.3.2 on 12.0.2’s, Redis 8.8.0 on the old snapshot, and PostgreSQL and PHP 8.0 became ordinary rows. Nothing moves the other way.
The update path knows about the floor too: each release descriptor carries the macOS it needs, and the updater checks it against the running Mac before installing anything.
The .rex resolver: two things it no longer does
Section titled “The .rex resolver: two things it no longer does”The resolver that answers .rex runs as a small background agent so your sites keep resolving
after you quit the app; the app watches it and steps in if it dies. Two cases where that watch
was wrong, both found on the release’s own test runs:
- It stopped calling a working resolver “down”. When the app handed the port back to the
agent and the agent took it a moment after the app last looked, the app latched DNS: down
and never looked again —
.rexresolved fine for two hours while Settings andrex statussaid otherwise. A resolver the app has given up on is now re-checked every few seconds and adopted the moment it answers. - It stopped resurrecting a resolver you removed. After Settings → Remove system
changes, the watch read the uninstalled agent as a crash: three “DNS stopped unexpectedly
— restarted automatically” toasts, and then the app quietly served
.rexitself — the thing you had just asked it to stop. A removal is now a state the watch leaves alone;rex statusreadsDNS DOWN (removed)and nothing answers the port.
Typing acme.rex in Firefox opens the site
Section titled “Typing acme.rex in Firefox opens the site”Browsers check what you type against the public list of top-level domains before they look
anything up, so a bare acme.rex went to the search engine while acme.rex/ opened. Firefox
has a per-suffix setting for exactly this; rexenv now writes it into each Firefox profile when
a domain ending is set up, and never flips one you set to false yourself. Chrome, Safari and
Edge have no such setting — for those, Settings and the site page say what works: add a slash,
acme.rex/, or https://.
0.8.6 — 21 September 2026
Section titled “0.8.6 — 21 September 2026”Windows fixes, and a new Adminer on fresh installs.
Windows: no more empty console windows
Section titled “Windows: no more empty console windows”A Windows release build has no console, so every command-line helper it started got a console
window of its own. All 22 of them — the database clients and dumps, WP-CLI, nginx -t,
pg_ctl — were written on macOS, where this never happens. On 0.8.4 and 0.8.5, Start all
left an empty terminal titled …\pg_ctl.exe on the desktop with PostgreSQL attached to it:
close it and the database stopped. Tunnels left one titled …\php.exe.
Every helper now starts hidden, and a source check in rexenv’s own test suite refuses any helper started the old way.
Windows: the installer says what is running
Section titled “Windows: the installer says what is running”Installing over an installed copy used to say “rexenv is running! Click OK to kill it” — even
to someone who had quit the app, because the .rex DNS answerer is the same rexenv.exe. It
now says:
rexenv is still running: the app, or the small background resolver that keeps your .rex sites answering after you quit.
Click OK to stop it and continue. The resolver starts again on its own once the new version is in place, and your sites and databases keep running.
The answer is the same as before: OK.
Windows: a missing DNS task is registered again
Section titled “Windows: a missing DNS task is registered again”The \rexenv\dns-agent scheduled task is what starts the .rex answerer at logon. When it went
missing, the health check could only log that it failed to restart it — and .rex would have
been dead at the next boot. rexenv now registers it again from its own copy of the definition
it wrote. What removed the task in the first place is still not known.
Adminer 6.1.0
Section titled “Adminer 6.1.0”The pin was 5.4.2 while the update path offered 6.1.0, so a fresh install got a console eight releases older than an updated one. Both now get 6.1.0. See Pinned versions.
0.8.5 — 20 September 2026
Section titled “0.8.5 — 20 September 2026”Two Windows bugs that 0.8.4 shipped, both found on the machine they were reported from within minutes of that release, and both invisible to every check that does not run a real cluster in a real window.
Windows: rexenv stopped killing its own PostgreSQL
Section titled “Windows: rexenv stopped killing its own PostgreSQL”0.8.4 started the server through pg_ctl, which detaches: the app was left holding a pid and
no child, and it recorded that as a process it had merely adopted. An adopted process is
never “starting” — so while pg_ctl -w was still taking its seconds to return, the health
watchdog fired, decided the engine was dead, tried to respawn it, and the port check refused
the app’s own server:
[restart-failed] PostgreSQL: port 15432/tcp … already in use by postgres.exe (pid 2064)
The cluster was serving the whole time; the Databases screen showed it down. The handle now carries the pid and the moment rexenv spawned it, which is what marks the server as ours — so the startup grace applies and the app is allowed to stop what it started.
Windows: the Database Browser renders Adminer
Section titled “Windows: the Database Browser renders Adminer”0.8.4 fixed the header Adminer sends. Necessary, not sufficient — two more things had to be right, and each one alone was enough to leave the panel blank with nothing logged:
- rexenv’s own policy blocked rexenv’s own frame. The app’s CSP named
frame-src rexdb:, but on Windows the same handler is served athttp://rexdb.localhost/, which that directive does not match. Both spellings are named now —connect-srchad already carriedhttp://ipc.localhostfor exactly the same reason. - The frame’s address was the macOS one. The
srcwas built asrexdb://localhost/?…on every platform. WebView2 does not know the scheme form, so the URL resolved to nothing and the frame never asked for anything at all — the nginx log recorded no request for the click. The origin is now a platform fact, beside the path separator.
0.8.4 — 20 September 2026
Section titled “0.8.4 — 20 September 2026”An update no longer closes the app behind your back
Section titled “An update no longer closes the app behind your back”Installing an update ended in the app quitting itself, the instant the swap landed. It now installs, stops, and asks: a one-button dialog names the version and says what a restart costs — sites, databases and DNS keep running, open terminals and running jobs do not — and the app closes only when you press OK. Dismiss it and the new version starts the next time you open rexenv; the build is on disk either way.
See Updating.
Windows: PostgreSQL starts on a machine with UAC turned off
Section titled “Windows: PostgreSQL starts on a machine with UAC turned off”It never had. postgres.exe refuses to run under an Administrators token —
Execution of PostgreSQL by a user with administrative permissions is not permitted
— and with UAC off every process on the machine carries one. The engine died on every
start, the watchdog respawned it three times and gave up, and all the screen said was
PostgreSQL did not start within 15s. initdb survives the same machine because it
re-executes itself under a restricted token; postgres.exe has no such code, and pg_ctl is
where PostgreSQL keeps it. So rexenv starts and stops the server through pg_ctl — the way
PostgreSQL’s own tooling does — rather than reimplementing the token dance.
Three separate things then had to be right before that worked on a real machine, each found by
running it rather than by reasoning about it: the spawn needs an explicit working
directory (inheriting one gave “The current directory is invalid” before a single setting
was read), the app must wait on the process’s status rather than its output (the detached
server inherits the pipes, so waiting for EOF hung the start for three minutes with a healthy
server listening), and pg_ctl must write its own log file (both pointing at one file
failed every start with “The process cannot access the file because it is being used by
another process”).
Windows: the Database Browser stopped rendering empty
Section titled “Windows: the Database Browser stopped rendering empty”Adminer’s wrapper bakes a frame-ancestors list for the direct HTTPS vhost, and that list was
replayed unchanged through the rexdb: proxy — where the parent is the app itself, served at
http://tauri.localhost on Windows. The engine refused to frame Adminer, silently. The proxy
now substitutes the origins rexenv’s own webview is served from and touches nothing else in
the header; the direct vhost keeps its strict list.
Two more causes survived into this release and were fixed in 0.8.5 above: on Windows the panel was still blank after this change.
0.8.3 — 19 September 2026
Section titled “0.8.3 — 19 September 2026”A link in an HTML email does nothing when you click it
Section titled “A link in an HTML email does nothing when you click it”Every link, in every HTML mail, for as long as the Mail screen has existed. The preview frame
carried sandbox="" — which reads as maximum isolation and, in the same breath, switches off
top-level navigation and popups. The mail rendered perfectly and was inert.
The obvious repair is wrong too, and it was measured rather than reasoned about:
sandbox="allow-same-origin" lets the app read the frame’s document but WebKit still never
delivers the frame’s clicks to the parent, so the link navigated the preview instead of
your browser.
So the isolation moved into the document, where the browser engine enforces it whatever
the parser misses. Before a captured mail is shown, rexenv prepends a Content-Security-Policy
meta as the first child of <head> — script-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none' — then sweeps script, iframe, object, embed,
base, form and meta refresh, strips every on* attribute and every javascript:,
vbscript: and data:text/html URL, and drops anchor targets so nothing can ask for the
app’s own window. Styles and images stay: that is what makes an email look like the email.
The frame is then unsandboxed, and every click in it is cancelled first — link or not, the
preview never navigates. An http or https link opens in your preferred browser, through
the same path as every other external link in the app (the OS handler when you have no
preference set). Anything else is named rather than opened, because an email is the one
piece of HTML in this app that a stranger wrote:
That link isn’t a web address, so rexenv left it alone:
mailto:someone@example.test
See Mail.
Windows
Section titled “Windows”After an in-app update, Apps & Features shows the new version instead of the old one. The swap, the relaunch and the sweep of the previous copy had all worked; the uninstall entry was rewritten with the version the update came from, freshly — measured on the 0.8.2 → 0.8.3 update, where it moved to 0.8.2, and on the 0.7.9 → 0.8.2 update before it, where it stayed at 0.7.9. The cause is that the version was read back off the executable after the rename, and a path that has just been renamed answers for the file that used to be there.
It is now read from the staged executable before anything moves — the same file the staging check has just verified — and a read that yields nothing is logged rather than passing in silence. The entry is only ever rewritten when it already exists: a dev build or a fixture swap has none, and creating one would put a half-filled rexenv row into a developer’s own Apps & Features. A failure to write it is logged, never fatal — the app is already swapped by then, and a stale number in a settings page is not worth failing an update over.
0.8.2 — 19 September 2026
Section titled “0.8.2 — 19 September 2026”PHP’s defaults are rexenv’s own now
Section titled “PHP’s defaults are rexenv’s own now”On every PHP version and both platforms:
| Setting | rexenv | PHP’s compiled default |
|---|---|---|
memory_limit |
1G |
128M |
upload_max_filesize |
5G |
2M |
post_max_size |
8G |
8M |
max_input_vars |
5000 |
1000 |
max_execution_time |
60 |
30 |
These are written into every pool config, which is the whole of the change: a key you had
not set was previously left to PHP’s compiled value, and 128M / 2M are the numbers every
WordPress import trips over. max_input_time stays -1, meaning “use max_execution_time”.
Your own value still wins — the defaults fill in only the keys you never touched — and they reach every pool rexenv starts after the upgrade, so existing sites get them with no per-site action. Leave a field empty in Settings → Services → PHP versions → ini settings and the default is what the field shows you as its placeholder.
nginx follows rather than becoming the new limit: its global client_max_body_size is
derived from the same defaults — the larger of the two body numbers, so 8G — and a version
with a stored value gets a per-site limit tracking that instead. Neither can 413 a body PHP
would have accepted.
Windows: the in-app update swaps files, not the folder
Section titled “Windows: the in-app update swaps files, not the folder”0.8.1 staged a whole new install directory and renamed the installed one aside, the way the
macOS build renames rexenv.app. The first real in-app update is where that broke (0.7.9 →
0.8.1, on a clean VM and on the Dell alike): the install directory is also the root of the
app-data tree — the database, the logs and every downloaded binary live under it — and a
directory with an open handle anywhere beneath it cannot be renamed. It failed with the
folder is not writable, while the fixture, whose install directory held nothing open, had
passed.
A running .exe can be renamed, and the process keeps running from the renamed file —
the measurement the design rests on. So the swap is now a rename pair per file: each
installed file moved aside, the new one moved in, every move undone if one of them fails, and
your data tree never touched. Nothing is deleted; the previous copy stays until the new app
has launched and confirmed its own version. uninstall.exe is written by the installer
rather than by the build, so it is copied in from the installed directory before the swap.
What the per-file window costs: there is a real moment when the directory holds a mixture, so
the .rex DNS answerer is re-launched afterwards rather than expected to survive it. A
per-machine install is still refused by name, with the per-user reinstall named — never a UAC
prompt.
0.8.1 — 19 September 2026
Section titled “0.8.1 — 19 September 2026”Windows only. Every file this release changes is under
src-tauri/src/platform/windows/, so nothing rexenv does on macOS is different — if you are
on a Mac there is nothing here you need.
The in-app updater called your own install somebody else’s
Section titled “The in-app updater called your own install somebody else’s”On Windows, if your account is an administrator, 0.8.0’s Settings → About → Updates refused to update and said:
C:\Users\<you>\AppData\Local\rexenvbelongs to another account, so rexenv could not clean up after replacing it. Have that account update rexenv, or take ownership first:
$ takeown /R /F C:\Users\<you>\AppData\Local\rexenv
The folder was yours. The installer put it there under your own account and no UAC prompt —
it is a per-user install. What went wrong is a Windows detail rexenv had not accounted for:
the owner Windows stamps on a file is the token’s default owner, and for an account in
the Administrators group that is BUILTIN\Administrators (S-1-5-32-544), not the account
itself. rexenv compared the install directory’s owner against the user’s SID alone, so an
admin account’s own install came out foreign and the refusal fired.
It now accepts either: the user’s SID, or the SID that account’s token stamps as owner —
read from GetTokenInformation(TokenOwner). Measured on a clean Windows 11 VM, where
%LOCALAPPDATA% and the install directory the installer made were both owned by
Administrators.
Nothing else in the refusal changed: a genuinely foreign install, an unwritable parent, a per-machine install and a disk without room are all still refused, and none of them has ever turned into an admin prompt.
0.8.0 — 19 September 2026
Section titled “0.8.0 — 19 September 2026”The first release with a Windows build, and the release that made rexenv installable on a Mac that has never had Xcode’s command line tools. It is also the first that imports sites from Local.
A clean Mac could not install a single component
Section titled “A clean Mac could not install a single component”Preparing a downloaded binary needs to know what it links against. rexenv asked otool -L.
/usr/bin/otool is a Command Line Tools shim: on a Mac that does not have the tools it
pops the “install developer tools?” dialog and exits non-zero. Every development machine has
them, which is exactly why 0.7.0 through 0.7.2 shipped with it and nobody noticed — it took a
clean-VM test to find.
rexenv now reads the Mach-O load commands itself, which needs nothing installed. The one
thing that still wants the tools is unchanged and was never part of this: the Homebrew-bottle
bundles — Redis, MariaDB, Apache and Xdebug — whose dylibs must be relinked with
install_name_tool. Those fail with a message naming xcode-select --install.
Trusting a new CA now untrusts the old ones
Section titled “Trusting a new CA now untrusts the old ones”Every fresh app-data folder mints a new local CA. The ones earlier folders trusted stayed in your login keychain as trusted roots for keys that no longer exist — four of them on one machine that had been reset by hand. Trusting a CA now sweeps the stale rexenv ones in the same step, while the trust dialog’s authorization is still warm, and Remove system changes removes all of them rather than only the one the current folder holds.
The sweep is best-effort by design: if it fails, the trust still succeeded, and a cancelled sweep never reads as “rexenv could not trust its CA”.
rex refuses a flag it does not know
Section titled “rex refuses a flag it does not know”An unrecognised flag used to be dropped, and the command ran with the default the flag
existed to override. On site create that quietly built the wrong site. On wp search-replace it was worse: --dry-runn --yes turns a rehearsal into a real replace across
the database, with no prompt — the typo removes the dry run and the --yes removes the
question.
Every flag-taking command now checks its own list first and stops:
rex: unknown flag `--dry-runn` for `wp search-replace`It exits 2 and does nothing. See the CLI reference.
Windows
Section titled “Windows”rexenv now ships a Windows installer. It is a per-user NSIS install — it lands in
%LOCALAPPDATA%\rexenv and asks for no administrator rights to install — and it is
unsigned, so SmartScreen shows “Windows protected your PC” and “Run anyway” only appears
after you click More info. Windows 11 x64; arm64 is not supported, because the official
PHP builds for Windows are x86/x64 only and PostgreSQL’s portable build is x64 only, so there
is nothing native to ship.
Five things are deliberately not in the Windows build, and rexenv refuses them by name rather than offering them and failing:
| Not on Windows | Why |
|---|---|
| Apache | would mean Apache Lounge — a third-party trust decision nobody has made |
| FrankenPHP | no Windows pin yet |
| Xdebug | its DLLs must match each PHP’s NTS build and compiler exactly |
| Redis | no official Windows build exists |
| MariaDB | no Windows pin; MySQL 8.4 and 8.0 both ship, so there is a database either way |
First-run asks twice on Windows where macOS asks three times — one UAC prompt for the
.rex NRPT rule, and Windows’ own certificate dialog for the local CA, which goes into your
user’s Root store and never the machine’s. There is no third prompt: the bundled Caddy binds
80 and 443 on 127.0.0.1 unelevated, which also means no firewall alert.
Import from Local
Section titled “Import from Local”The import screen — now its own Import page in the sidebar, no longer a card under Settings → DNS & SSL — reads Local (WP Engine’s, formerly Local by Flywheel) beside Valet and Herd. As with those two, Local is only ever read: its folders are linked in place, its databases are copied, and none of its servers is started or stopped by rexenv.
What is different about a Local site:
.localis re-homed. rexenv refuses.local— macOS uses it for Bonjour — soshop.localimports asshop.rex, and the row says was shop.local. If that name is taken, the row asks for another rather than choosing one for you.- A PHP version rexenv does not ship asks first. Local pins full versions such as
7.3.5; that row offers a picker and stays unticked until you choose. Valet and Herd rows pinned to an unshipped minor get the same picker. - The database comes from Local’s own server, over its socket. Local runs one per site, and
only while that site is started in Local; a stopped one fails with Local named, and the fix is to
start it there and retry. The copy is named after the site —
local_shop_rex— because every Local database is calledlocal.app/sql/local.sqlis never used: it is a snapshot from whenever Local last exported. - The copy’s URLs move; Local’s do not.
http://shop.localbecomeshttps://shop.rexin rexenv’s copy only, through WP-CLI’s serialization-aware search-replace. - Connecting is ticked by default. Until its config points at the copy, a Local site reads no
database under rexenv at all, so also connect Local sites connects each one in the same run,
with
wp-config.phpbacked up first. While a site is connected, Local reads rexenv’s copy too. - Networks import as networks. A subdomain or subdirectory multisite moves every subsite to the
new name, and connecting it moves
DOMAIN_CURRENT_SITE. A network with a subsite on its own mapped domain is listed and not imported. - Local’s router is named. When it holds
:443, rexenv says so, with Local’s own Router Mode → localhost as the way to free the port without quitting Local.
Import is a macOS screen. The Windows build does not look for another tool’s sites, and says it did not look rather than reporting that nothing was found. Migrating from Valet, Herd or Local has the details.
0.7.2 — 13 September 2026
Section titled “0.7.2 — 13 September 2026”One WordPress fix, and nothing else. A WordPress site rexenv created before 0.7.2 can be
missing core files — about 25 of them on a WordPress release that ships
wp-includes/php-ai-client — and nothing said so when the site was made.
What went wrong
Section titled “What went wrong”rexenv asked WP-CLI for wp core download, which fetches WordPress’s .tar.gz. WP-CLI unpacks
that with PHP’s PharData, and PharData read the archive with every file name cut at 100
characters: 40 names came out truncated — …/WithRequestAuthenticationInterface.php as
…Interface., a default theme’s …Italic.woff2 as …Italic.wof — and six files vanished where
two cut names collided. macOS creates those names without an error, so the site was created and
the damage was silent. It surfaced because Windows cannot create them at all.
Every core download rexenv makes is now WordPress’s .zip build, which unpacks with every name
intact: a new site, a localized build, and Re-install core. The locale and version are validated
before they reach a download URL.
What Re-install core repairs, and what it does not
Section titled “What Re-install core repairs, and what it does not”It reads the installed version from wp-includes/version.php and forces that version’s
no-content build over the site folder. wp-content, wp-config.php and the database are not
touched — a plugin of yours and an edit to a default theme both survive it. Three limits:
- The no-content build carries no
wp-content, which is also why it cannot put back a default theme’s cut font file. - It leaves the cut-name leftovers (
…Interface.,…Italic.wof) where they are, and Verify core checksums does not report them: a repaired site can pass and still carry them. - A site whose
version.phpnames no release — a beta, say — is refused:cannot tell which WordPress version <site folder> runs — wp-includes/version.php is missing or names no release version, so there is nothing to reinstall it from.
On some WordPress builds the check also lists wp-includes/php-ai-client files as foreign, until
wordpress.org’s checksum manifest covers them. That is the tool’s honest output, not this bug —
the files that doesn't exist are.
Neither control is on the rex CLI; both are in the app.
0.7.1 — 13 September 2026
Section titled “0.7.1 — 13 September 2026”Licence notices only. Nothing rexenv does changed: apart from the version number, the one thing that differs from 0.7.0 is the text the app shows under Settings → About → Licenses & credits.
That list was incomplete. In 0.4.0 through 0.7.0 it left out the MySQL client libraries
mysql_async 0.37.0 and mysql_common 0.37.3 and the thirteen crates they pull in — all
built into the app since 24 August 2026 — and two crates in the rex CLI’s own dependency
graph, memchr 2.8.3 and zmij 1.0.23, which had never been listed at all. 0.7.1 names all
17, and its table now counts 410 Rust crates across both Apple Silicon and Intel.
Nothing generated the list, so nothing could notice it drifting: the table and its counts were typed, and the documented command for regenerating it read only the app, never the CLI. The app’s own verification now checks it and fails when the table and the real dependency graphs disagree in either direction — Rust for the app and the CLI on both architectures, the npm packages, and the vendored Composer packages.
0.7.0 — 11 September 2026
Section titled “0.7.0 — 11 September 2026”A site’s database can be PostgreSQL — for Laravel and blank PHP, never WordPress — and making that true meant rexenv now builds PHP 8.1–8.5 itself. Agents also reach every command the app has, and a site’s folder opens in your own terminal app.
PostgreSQL sites
Section titled “PostgreSQL sites”Pick PostgreSQL in the New Site dialog, or rex site create <domain> --db postgres. The dialog
offers it only where it would work and says why when it does not; everything else gets the app’s
refusal, quoted on PostgreSQL, and a refused
create leaves no docroot or certificate behind.
A Laravel site’s .env is written with DB_CONNECTION=pgsql and every other connection key,
replacing the skeleton’s SQLite default. A blank PHP starter seeds the same starter_items
table through a pgsql: DSN. Start all starts PostgreSQL when a site’s database lives
there — before, a PostgreSQL site could come up with nothing listening on 15432 — and a
delete drops the database WITH (FORCE), so an open connection cannot keep it alive.
Two things to know before choosing it: the server has one passwordless postgres superuser, so
a PostgreSQL site’s credentials reach every database on it; and importing from Valet or Herd
still does not bring PostgreSQL data.
rexenv builds its own PHP
Section titled “rexenv builds its own PHP”The PHP 8.1–8.5 builds rexenv used to pin advertised a PDO PostgreSQL driver they did not have.
A connection was accepted and then stalled for about a minute until PostgreSQL closed it — and
Laravel’s pgsql driver is exactly that call. rexenv’s own builds carry the real driver and
are checked with a real connection, plus module names, function names and configure flags
against the builds they replace. The one deliberate difference: Swoole’s PDO PostgreSQL and
SQLite coroutine hooks are gone, because they cannot coexist with the real driver.
8.0.30 stays static-php.dev’s build and has no PostgreSQL driver. The 8.2 and 8.3 pins move
to 8.2.32 and 8.3.32 — the patches upstream had already moved to — so a machine that had
updated to them lands on rexenv’s build of the same version rather than upstream’s. Each
rexenv build installs its licence texts beside the interpreter, and a signed manifest may now
name php-licenses so an update to a rexenv build brings them too. See
PHP versions.
Agents reach every app command
Section titled “Agents reach every app command”Every command in the app is now reachable by an agent or ruled out by name, and a new command
fails the build until someone rules on it. Thirty were closed as new actions on existing tools:
the mail catch-all, TLD status and policy, opening a site in your terminal, the stack’s health
reads, WordPress releases and WP-CLI packages, clearing logs, a Git watch’s log, engine
versions, Adminer, downloads and start-at-login. A half-built site_create now tells the agent
why it failed, and site_retry answers with the settled outcome instead of running. The
Agent access card shows the level and its duration at a glance, and the activity feed folds
away. See AI agents.
Your own terminal
Section titled “Your own terminal”A chevron beside every Terminal control — the site’s quick tile, each plugin row and theme
card, and the Terminal tab’s toolbar — opens that folder in Terminal, iTerm, Warp, Ghostty,
WezTerm, kitty or Alacritty. That shell is your plain login shell, with no bundled PHP and no
wp on PATH; the built-in tab is still the one that runs the site’s PHP. See
Add plugins and themes.
Also in this release
Section titled “Also in this release”- The menu-bar menu has About rexenv, and a menu held open updates in place instead of closing itself a few seconds later.
- The Sites page opens on All again, rather than switching to Running on its own.
- A failed update check reads as a sentence — “rexenv couldn’t reach the update server. Check your internet connection and try again.” — with a 6-second deadline for Check now.
- Deleting or renaming a site removes its job logs. Logs left behind by earlier versions
are not swept. A site’s Logs list also stopped offering a neighbour’s Git job output —
foo.rexcould seefoo.rex-2.rex’s. - First-run downloads no longer stick at
queuedwhen you move past the Install step while they are still running. Upstream has not yet re-run the wizard by hand on a fresh install to confirm it. - An administrator or Keychain dialog no longer holds the app up. While one was open, the window could freeze and commands that needed rexenv’s database waited for you to answer.
- A Laravel site whose
create-projectfailed can be retried. Retry used to read the half-made skeleton as installed and fail forever. - Adminer’s update button reads Updating to and the version while it works, instead of collapsing to an ellipsis.
0.6.1 — 7 September 2026
Section titled “0.6.1 — 7 September 2026”The first version an installed rexenv could reach without you downloading anything. It is a small release by content — Settings → About gained a Changelog row that opens this page — and a large one by proof: 0.6.0 shipped the self-update machinery, and 0.6.1 is the release that machinery actually installed, end to end, on a real Mac.
That ordering is not an accident. An offer is a live comparison — strictly newer, by numeric segment — so the release that ships self-update is the one release that can never use it. The first in-app update any install can perform is 0.6.0 → 0.6.1.
The Changelog link points at this site, not at a GitHub releases page: rexenv/rexenv was
private at the time (it went public on 30 September 2026) and its release page was a 404 to
anyone not signed in, and the tap’s releases page is an artefact list rather than an account of
what changed. Pointing at the site is what keeps
the link working when the repo’s visibility changes.
Also in this release: the DNS agent reports which build it is, so a stale agent left running by an earlier version stops being invisible when you are trying to work out why a name does not resolve.
0.6.0 — 7 September 2026
Section titled “0.6.0 — 7 September 2026”The release that gives every later release an update path. rexenv can now update itself:
it checks a signed descriptor, offers the new version, verifies its signature and checksum,
replaces rexenv.app in one atomic step, then quits and reopens on the new version.
What the click actually does
Section titled “What the click actually does”The consent sentence sits above the button, in the order the steps happen:
Downloads rexenv 0.6.1 (29 MB), checks its signature and checksum, replaces rexenv.app in one step, then quits and reopens on 0.6.1. Your sites, databases and DNS keep running throughout — services outlive the app. Open terminals and running jobs close with it, exactly as they do when you quit.
The staged copy is verified before anything is exchanged — version, bundle identifier,
executable name, the rex sidecar, both architectures present in every Mach-O, and
codesign --verify --deep --strict. The exchange itself is a single atomic swap, so no
instant exists where the install path holds nothing. Nothing is written inside the running
bundle and nothing is copied over it: replacing a running binary in place invalidates
pages the kernel is executing, and a copy-over leaves a hybrid of two builds. The previous
bundle stays on disk until the new app has launched and confirmed its own version.
What it refuses, and why
Section titled “What it refuses, and why”Every refusal names the consequence and, where a fix exists, ends with a command you can read before you run it. No refusal turns into an admin prompt.
| Situation | What rexenv says |
|---|---|
| Running from a disk image | Drag rexenv.app into Applications, open it from there, then update. Nothing was changed. |
| Never moved out of Downloads | macOS is running rexenv from a temporary read-only copy — move it to Applications first. |
| Opened through a symlink | The copy running and the copy that would be replaced may not be the same one. |
| Applications not writable | sudo chown -R "$USER" /Applications — or ask an admin to update rexenv. |
| The bundle belongs to another account | Have that account update rexenv, or take ownership first. |
| Not enough free space | Names the megabytes needed and the megabytes free. Nothing was downloaded. |
The space check wants room for the archive, the extracted copy and the previous bundle — three times the download, checked before the first byte, because a refusal that arrives after a 29 MB download has wasted your morning.
The check is a read; the install is a click
Section titled “The check is a read; the install is a click”The check rides the launch sweep, plus a six-hour loop for a machine left running — a menu-bar app outlives its window by design. The request carries the rexenv version and nothing else: no architecture, no identifier. Turning automatic checks off in Settings → About stops the request itself rather than what is done with the answer, and Check now still works.
- Skip this version stores a version, not a flag, so a later release still offers itself.
- The footer says
checked N agofrom a value written only after a successful check — a failed check keeps the old timestamp instead of ageing into a lie. Nothing ever claims “up to date”. - The menu bar carries an Update to … item exactly when a verified offer exists, and it only opens the card.
rex statusprints one line —update rexenv <version> can be installed from Settings → About— when there is an offer, and--jsonand the MCPstack_statustool carry the same field. All three read an in-process snapshot: a status call verifies no signature and touches no network.- There is no
rex updateand no MCP tool that installs one. An agent asking for one would be asking to replace the process that enforces the dial that bounds it.
The descriptor is a second signed document alongside the PHP one — app-manifest.json and its
signature, on rexenv/runtimes — verified with an ed25519 key compiled into the app, re-checked
on every read, carrying its own monotonic serial as rollback protection, and pointing only at
artefact URLs under a compiled-in path prefix. TLS is not the gate: a digest check compares
bytes against whoever supplied the digest, so an attacker-chosen URL paired with an
attacker-chosen hash matches perfectly.
0.5.0 — 5 September 2026
Section titled “0.5.0 — 5 September 2026”The release where rexenv grew a menu-bar life and an agent surface. 205 commits since 0.4.0; this entry is written from them, not from the tag.
The menu bar
Section titled “The menu bar”Closing the window used to quit the app, and quitting took the control plane with it: rex
and the MCP endpoint are remote controls for the running app, their sockets are opened by its
process, and a public tunnel is supervised by it. So a closed window meant a dead CLI and a
stopped share while the sites kept serving. Now the crowned-R icon in the menu bar is home —
status line, Start all and Stop all, up to eight recent sites, an MCP on/off checkmark, and
Open rexenv. A dock tile appears only while the window is up, so you can Cmd-Tab to it.
Start on login goes straight to the menu bar with no window, unless first-run setup is
unfinished. rex open brings the window forward. See
Where rexenv lives once it is open.
One rexenv per data folder, and the CLI socket is the lock. A menu-bar app made a second copy
of itself invisible — it booted fully, adopted the services, and stole the first copy’s
socket, after which rex talked to a headless orphan. The second launch now exits after
bringing the first to the front.
Agents: parity, and one dial
Section titled “Agents: parity, and one dial”Every rex surface has an MCP tool — 50 of them — on the user’s own sites, not only on scratch
sites: create, configure, restart, delete; the eight wp_* tools; artisan and Composer for
Laravel; logs, the inbox, the database; the stack, PHP, settings, the TLD; the git-asset group
as one tool; blueprints; sharing; the Valet import and connection rewrite. Each decides its
level per action.
What decides is one Agent access dial — Read / Changes / Full — with a duration of
this session, 7 days or always. It replaced per-site, per-scope, per-client grants that
needed six clicks for one site’s ordinary work, plus the database-access prompt, the mail
sub-toggle and the auto-allow switch. Read is on whenever the endpoint is and now covers every
site’s mail and a SELECT-only view of its database. Full is the only level that hands over a
shell as you, and publishing a site lives there rather than behind a second prompt. Where
macOS asks for a password, rexenv does not ask again. A login link — real or scratch — is
Read, because a link is not a credential; a password is never handed to an agent. The refusal
names the dial, both levels and where to change it, in as many words. The full picture is on
AI agents.
The feed is a file too: mcp.log, with its own AI agents (MCP) tab in Logs and an Only
this site filter on a site’s Logs. Scratch cap and lifetime are settings now — five sites and
24 hours by default, scratch_cap and scratch_ttl_hours from rex config. The bridge
answers a pending call in-band when rexenv quits, so the model is told rather than
disconnected. rex mcp gained progress notifications for clients that ask, and tool
annotations for every registry.
Stop one site
Section titled “Stop one site”Every site was running or none was. Now a site has its own switch — Stop site, rex site stop <domain>, --all for every site, and the same for an agent. A default site is not a
process, so stopping one changes the serving surface and touches no shared nginx or PHP pool:
the site’s nginx block answers 503 with no PHP, the edge keeps the route and certificate and
serves rexenv’s own This site is stopped page, and only a FrankenPHP or Apache override
backend — a process that serves one site — is actually stopped.
What it cost to get right: the first version emitted no nginx block for a stopped site, and nginx answers a name it has no block for from its default server. A public tunnel proxies straight to that nginx, so sharing a stopped site published a neighbour’s site, found live within a day. Every name rexenv knows now answers for itself in every tier. Sharing a stopped site is allowed with a warning, never silently. The Sites list also filters by type and opens on the sites that are running. Sites.
Extra domains
Section titled “Extra domains”A site serves, and holds a certificate for, every name it answers on — the Domains card, or
rex site domains <domain> --add <name>. A hostname reaches exactly one site, and a name that
is already some site’s primary or extra is refused with the owner’s name. A Valet link farm
imports as one site with extra domains instead of one site and a wall of overlap errors. An
extra domain on a WordPress site reaches it and then redirects to the primary, and the card
says so. rex site info answers with every name; every site verb finds a site by any of
them; renaming a site keeps its other names working. Two fixes on the way: a certificate
reissue forgot the extra domains, and an imported extra domain was recorded but not served.
rex doctor and the DNS card in Settings name every TLD your sites use that this Mac cannot
resolve, with the cause. rex tld --repair <tld> puts back a missing resolver file and refuses
one another tool owns; rex tld --remove <tld> is the explicit, ours-only, not-in-use-only
opposite. The import scan lists a leftover resolver file with no site behind it, so there is
something to consent to. A .rex domain won’t resolve.
Mail: one switch, three carriers
Section titled “Mail: one switch, three carriers”Catch all outgoing mail in Settings is one switch, on by default and on for sites that
predate it. It now reaches the two apps that escaped: a WordPress SMTP plugin — measured,
isSMTP() opened its own socket and the shim never saw the message — through a mu-plugin that
hooks phpmailer_init last; and Laravel, which never consults sendmail_path, through
nine MAIL_* variables in the pool and the artisan runner that outrank .env. Also a
FrankenPHP override site. Flipping the switch installs or removes the mu-plugin and restarts
the pools, so the toggle is pending until the machine agrees. The card names what it cannot
catch: a Laravel app that ran config:cache, a plugin mailing through a provider’s HTTP API,
and your own terminal. Mail.
Runtimes: floors, measured
Section titled “Runtimes: floors, measured”- nginx 1.30.4 is rexenv’s own build. The floor check found 0.4.0’s nginx declaring a macOS 26 floor on its Intel slice against a stated floor of 15, on the one binary every default site’s requests go through — an Intel Mac on macOS 15 could install rexenv and then not serve a site. Upstream had no way out (Intel is 26 from 1.26.3 up), so rexenv builds nginx the way it already builds PHP 7.4: macOS 12 on both chips, asserted per artefact, PCRE2 compiled in, no ssl module (the edge owns TLS), no gzip, linked against nothing but libSystem. Being the builder makes rexenv the distributor, so the licence tarball rides the binary and a download refuses to complete without it.
- PostgreSQL 18.6.0 / 17.11.0 / 16.15.0, re-pinned for the floor rather than the patch: the previous pins were compiled for macOS 26 on every binary, which this site said and the app could not test. The new ones are measured at macOS 15 across every executable and library in all six tarballs, so PostgreSQL no longer needs macOS 26. cloudflared is now the only binary at the app’s stated floor. PostgreSQL.
Tunnels
Section titled “Tunnels”A share ends with the app even when the app is killed: a guard process watches rexenv’s
pid and start time and stops the tunnel when it goes, where a crash used to leave a site
public until the next launch. A share writes its start and its end to rexenv.log — site,
public URL, pid, origin — after one was found running that nobody remembered starting and
the only evidence was in a per-tunnel file. An agent’s share stops itself after 30 minutes by
default and 60 at most, and its timer ends only the share it started. A share rexenv stopped
for you now reaches the screen. Public tunnels.
rex open, site stop|start [--all], site restart [--pool], site domains, service restart, tld --repair|--remove, wp user delete with a required decision about the posts,
repo watch --tail, site create --starter-db. Progress streams over the socket for a client
that asks, and a client that hangs up mid-stream no longer abandons the command half-way. Three
fixes worth knowing: site create ignored a misspelt flag and built the wrong site; wp search-replace ran for real on a typo’d --dry-run; wp user delete could not delete anyone.
CLI reference.
- A WordPress site stays WordPress while its database is down:
wp core is-installedexits 1 for “not WordPress” and “database unreachable” alike, so the WordPress tab and magic login vanished on Stop all and did not return on Start all until you left the page. rex doctornames the PHP minors whose bundled curl cannot resolve.rexhosts, instead of leaving it in a plugin’s error. WordPress is patched; rawcurl_init()is not.- A port held by Valet says Valet and offers
valet stop; the Valet/storagemapping is reproduced on import. - Schema v44. Cmd+Q meets the same quit gate as the menu.
0.4.0 — 27 August 2026
Section titled “0.4.0 — 27 August 2026”The release where a build can say what built it — rex --version prints
rex 0.4.0 (d363e24) off the mounted disk image, without the app running. Every earlier
release’s source commit was an inference from the tag and the build timeline.
Multisite through a public tunnel
Section titled “Multisite through a public tunnel”A subdirectory network kept losing its sub-site paths: siteurl, home and admin_url were
rewritten to the tunnel’s origin without the /sub1, so a sub-site 404’d and a login at
/sub1/wp-login.php landed on the MAIN site’s dashboard. Fixed, and only on the host the
tunnel actually pins.
A subdomain network cannot work through a quick tunnel at all — one hostname, one pinned
Host, no wildcard to ask for — so while it is shared it is served as a subdirectory network:
<origin>/s1/… becomes a request for s1.<network>. Local behaviour is unchanged: s1.msd.rex
still serves natively and msd.rex/s1/ is still a 404. Sub-sites on their own public hostnames
still need a wildcard a quick tunnel cannot issue.
Sharing a network through a tunnel.
Themes can also be network-enabled from the app now — the toggle reads the network’s own
allowedthemes option, because wp theme list has no network-enabled status to render a
position from.
Blank PHP sites open on a page that answers something
Section titled “Blank PHP sites open on a page that answers something”<?php phpinfo(); answered a question nobody asks on their first page load and none of the
ones they do. A blank PHP site now gets a generated index.php naming its PHP version, web
server and docroot — and the New Site dialog’s Database field is a real choice for it: MySQL,
MariaDB or None. Pick an engine and the site gets a database, a seeded starter_items table
and a db.php returning a ready PDO; pick None and both the database and its engine download
are skipped. Neither generated file is ever overwritten, so a retry cannot revert your morning.
The starter database is a dialog answer — rex site create has no flag for it yet.
What a blank PHP site opens on.
Terminal
Section titled “Terminal”Three fixes that add up to a terminal you can work in:
- It spawns a login shell. rexenv is launched by launchd, so it inherited a bare
/usr/bin:/bin:/usr/sbin:/sbinand a non-login shell read only~/.zshrc—code,rex, Homebrew’s git and every version-manager shim answeredcommand not foundinside rexenv while working fine in Terminal.app. - A session survives leaving the tab. Switching tabs unmounted the terminal and killed the
PTY: a
composer installdied on an alt-tab and its output was gone on the way back. Four live shells are kept; an evicted one reopens on the next visit. - A plugin row or theme card opens a terminal in that asset’s own folder, as a separate
session — not a
cdtyped into a site shell that may be mid-install.
AI agents (MCP): reading a real site’s database
Section titled “AI agents (MCP): reading a real site’s database”The agent surface gains db_query. On a scratch site it works immediately; on a site you
made it is SELECT-only and needs your approval, given in
Settings → AI agents (MCP) → Database access, lasting 7 days, listed and revocable there.
The ask is recorded where the query is refused — there is no “request access” tool, so nothing
prompts you about access nothing tried to use. Requests die with the app rather than persisting,
because a consent prompt whose context is gone is not consent. The prompt names password hashes
and tokens in wp_options rather than saying “read access”.
A session-scoped auto-allow answers that prompt with yes. It skips a prompt, never a boundary — the rule that an agent may not change a site you made is untouched — it lives in memory rather than settings so it dies on quit, and it still writes a grant, flagged auto-granted so “did I approve this?” has an answer. New page: AI agents (MCP).
- About from the app menu opens rexenv’s own About with a Build card — version, commit, build date, platform, Tauri — rather than the bare macOS panel that can show a name and a version and nothing else.
rex site relink <domain> <path>re-points a linked site at a folder you moved: it records and reloads, and touches no file.site moveremains the owned-docroot command and still refuses a linked folder.rex site retry <domain>finishes a site whose setup stopped part-way.rex config get|setreaches a small allow-list of settings, deny-by-default, every refusal saying why. The signed PHP update manifest and the MCP toggles are refused outright.rex mail list [--unread] [query]andrex mail mark-read— two backend arms norexverb had ever sent.rex site deleteon a linked site no longer claims it removed files it correctly had not. The confirm prompt had always said the folder is left in place;--yesskipped the prompt, so the user most likely to be scripting saw only the false sentence.- The Xdebug refusal now separates its two causes: a static build that physically cannot load any extension (7.4, 8.0) versus a minor nobody has pinned a bottle for.
- A service that fails to start names the macOS version mismatch when that is the reason.
Requires macOS 15 or later, unchanged.
0.3.0 — 20 August 2026
Section titled “0.3.0 — 20 August 2026”The release where two components stopped being frozen until the next rexenv build — and both of them stayed a button you press.
PHP patch updates, from a signed manifest
Section titled “PHP patch updates, from a signed manifest”8.3.31 is what a build pins. When PHP ships 8.3.32, Settings → PHP versions can now
install it without waiting for a rexenv release: one button, one minor at a time, restarting
that minor’s pool and putting it back on the previous patch if it does not come up.
The trust anchor moves exactly one step — from a checksum compiled into the app to a signed
document whose public key is compiled into the app. Five limits hold the blast radius: only
php, php-fpm and adminer may be named (never caddy, which is a root LaunchDaemon),
https:// from allowlisted hosts only, the signature is re-checked on every read rather than
remembered, only a patch of a minor your build already ships, and the pin is the floor —
an update can only move you upwards. An older manifest is refused as a replay.
Details, including what the signature does not buy: Patch updates.
“8.4.24 exists”, said where it is true
Section titled ““8.4.24 exists”, said where it is true”Separately from the button, the PHP rows read php.net’s release list and say what exists. That
is not the same claim as “update available”: the 8.x builds come from static-php.dev, which
trails php.net — measured at 17 days on 16 August 2026. The row says exists, never
“up to date” (unprovable before the first successful check), and rex php list gained a
NOTE column carrying the same two facts — serving 8.3.30 and 8.3.33 exists — because a
CLI that prints only the pin is the same silent lie in different clothes.
Adminer updates, and a console that matches your theme
Section titled “Adminer updates, and a console that matches your theme”The Databases screen carries an Adminer card under the engine table, on the screen the console opens from. Updates come from the same signed manifest, capped at major 6 — the newest major whose plugin API was probed against rexenv’s wrapper — and a downloaded build is run through the bundled PHP in a throwaway directory first: rexenv’s login gate and frame protections live inside Adminer’s plugin API, so a build that no longer binds would turn them off silently. See Updating Adminer.
The console also takes its palette from rexenv rather than from macOS. A light-themed app framing a dark console was a real screenshot, not a hypothetical.
WordPress
Section titled “WordPress”- Premium plugin updates are visible. Vendors’ updaters register behind
current_user_can( 'manage_options' ), and a WP-CLI run has no user at all, so every paid plugin was missing from the update data. The check now grants three named capabilities to that one process — nobody is impersonated, no session or cookie exists. Measured on a 47-plugin site: 2 of 10 paid plugins reported an update before, 5 after. - Paid plugins show an icon instead of a letter tile, borrowed from their free half where the directory has one.
- A zip of something already installed offers Replace instead of dead-ending on WP-CLI’s
Error: No plugins installed.A git-managed target gets a danger confirm first, because--forceunpacks over the working tree. - Rows say the thing’s name, with the slug kept beside it, and the plugin filter matches
both — typing “loopback” against a row reading
rexenv loopback DNSused to answer “No plugins match”. - A successful install card clears itself after three seconds; a failed one waits with an × to dismiss, and opening its log holds the timer.
Everywhere else
Section titled “Everywhere else”- Tunnels have a search box over name, domain and the public URL — and it says how many shared sites the filter is hiding, because a live public URL is not a view preference.
- rexenv writes its own log in every build (
logs/rexenv.log, its own tab, rotated at 2MB). On a release build the app previously logged nowhere, so every bug report arrived without the one artefact that explains it. - Your default PHP version stops resetting. Settings → Make default held until the next launch and then snapped back to the pinned minor; a new pinned minor arriving with a release also claimed to be a second default.
- A settings value the pool dies on can be undone. A
memory_limittoo small to start a worker passed both parse-time gates, persisted, and then failed every later start with nothing on screen connecting the two. - ~360MB of superseded PHP trees stop accumulating. A user’s
bin/had reached 3.3GB with every replaced patch still on disk, growing ~180MB per updated minor. - Low-contrast text is gone. 135 text usages moved to an AA-clean token and two tokens were deleted outright; the light theme’s update badges were a raw Tailwind hue that could not follow a theme at all.
0.2.0 — 15 August 2026
Section titled “0.2.0 — 15 August 2026”- PHP 7.4.33, as a first-class version: same picker, same pool model, its own pool on
9774, 60 extensions includinggd,intl,mysqliandredis. Nobody publishes a portable PHP 7.4, so rexenv builds and hosts it — and therefore ships the licence texts beside the interpreter. No OPcache and no Xdebug on 7.4, stated rather than discovered. - End-of-life runtimes say so. PHP 7.4, 8.0 and 8.1 are badged where you pick a version, computed against today’s date rather than a list somebody remembered to update.
- FrankenPHP stops misreporting the PHP version. It serves every site with its own embedded PHP, so the picker is read-only there and annotated with what actually serves — and a 7.4 site on FrankenPHP is refused rather than silently served by 8.5.
- Resolver drift is reported. If Valet or Herd reclaims a TLD your sites stop resolving;
that now arrives as a banner naming the symptom and the way back, and
rex doctorreports it. - Repository panel: working-tree row — status, stash, restore, reset.
- Mail: unread filter, Mark all read, and read state that does not wait for a poll.
- Browsers: open a site in any browser’s private window, per row.
- Public sharing works for sites on a FrankenPHP or Apache backend.
- A foreign proxy already on
:443is reported during onboarding instead of failing later. - Security: the WordPress magic-login reads the last
X-Forwarded-Forhop.
0.1.1 — 12 August 2026
Section titled “0.1.1 — 12 August 2026”rexno longer hangs forever when the app stops answering. An app instance can hold the CLI socket, accept the connection and never reply —rex --versionandrex statusthen blocked with no output and no timeout.--versionis bounded at 2s and falls back to the CLI version alone; long-running commands still wait as long as they need, but say so after 10s instead of leaving a dead terminal.- A Laravel site’s database is named
lv_, notwp_. The name derived from the domain alone, so every site type got a WordPress prefix on a database WordPress never touches. Creation-time only — existing sites keep the name they stored.
0.1.0 — 12 August 2026
Section titled “0.1.0 — 12 August 2026”The first published build. No release notes were written for it, and none are invented here — what it shipped is what the rest of these docs describe, minus everything listed above.