Between September 1 and September 19, 2026, the public commit history of superfly/flyctl — the CLI that is Fly.io for most of its users — contains zero new regions, zero pricing changes, and zero new products. It contains a certificate-status label fix, better SSH error messages, an org-scoped app list, atomic config writes, a file-permission fix, secret redaction in debug logs, a login footgun warning, and a first-deploy IP fix. Then it shipped all of it as v0.4.104 on September 15 and kept polishing.
That is the whole changelog. And it might be the most honest strategy document a PaaS has published all year: once compute is a commodity, the deploy interface is the product.
Three weeks of changelog, in one chart
I audited every flyctl commit from September 1–19 (to be precise about scope: this post covers that window only, not the rest of the month). Here is the shape of it:
| Week | Shipped | Regions / pricing / new products |
|---|---|---|
| Sept 8–12 | Cert status output, SSH machine errors, org-scoped apps list, atomic config writes, MCP file perms, GraphQL-to-Flaps migration work | 0 |
| Sept 15 (v0.4.104) | Release cut; secret redaction in debug logs lands days later | 0 |
| Sept 16–19 | Flycast first-deploy fix, Machines-API migration for managed Postgres | 0 |
Two tracks, both client-side: user-visible CLI polish, and a behind-the-scenes migration of the CLI's API calls from GraphQL to Flaps, ui-ex, and the Machines API. The second track matters too — it is the CLI team paying down the debt that keeps the first track fast — but nobody's deploy got a new region out of these three weeks. Every improvement landed between the user's keyboard and an already-existing API.
For a usage-billed PaaS, that is a deliberate allocation of scarce engineering time. Regions win press releases. Error messages win renewals.
The receipts: eight small fixes that are the whole story
Each of these is a real, merged, individually tiny change. Together they are a masterclass in where CLI quality actually lives:
| PR | Fix | Why it matters |
|---|---|---|
| #5193 | fly certs list showed "Issued" while fly certs show showed "Not verified" for the same hostname; now both report "Issuing..." until a certificate exists | Contradictory TLS state is a support-ticket factory |
| #5194 | fly ssh console --machine replaces "not found/started" with the actual reason: wrong app, not started, wrong region, wrong process group | The #1 CLI sin is an error that names no cause |
| #5197 | fly apps list scopes release timestamps to --org and lists apps without dates instead of failing when the timestamp call fails | Auxiliary data must never fail the command |
| #5190 | Config file written atomically (temp file + rename) so a failed write on a full disk no longer truncates it to empty — which previously read back as "logged out" | A full disk used to log you out; now it just errors |
| #5192 | fly mcp add client config (which embeds the API token) written at 0600 instead of world-readable | Tokens in world-readable files are a breach waiting for multi-user machines |
| #5204 | Debug logs in ~/.fly/logs redact secret values, token headers, and passwords | The always-on debug log was persisting credentials in plaintext |
| #5173 | Warn after fly auth login if the new token is shadowed by an env var | The classic "I logged in, why am I still me" footgun |
| #5206 | First deploy with --flycast on a default-network app failed with "network not found" and never retried; fixed by normalizing the network name | First deploys are the activation funnel — a dead-end error there is churn |
Notice the pattern: not one of these changes what Fly.io can do. Every one changes what it feels like when something goes slightly wrong — which, across thousands of users, is most of the time.
What a polish-only stretch signals
So why does a usage-billed PaaS spend September on error strings instead of regions? Because switching costs no longer live in the infrastructure. They live in muscle memory and scripts.
Nobody stays on a PaaS because fra has marginally better latency than a competitor's ams this quarter. They stay because deploy works the same way it did last Tuesday, because the 3 a.m. error message told them exactly which machine was in the wrong process group, because their CI script that parses apps list output didn't break when one API hiccuped. Every row in the table above is a churn driver with the serial numbers filed off: contradictory TLS state becomes a migration-evaluation ticket; a logout-on-full-disk becomes a "reliability concerns" thread; a dead-end first-deploy error becomes a signup that never activates.
There is an honest caveat: this is a three-week reading, not a strategy memo. The parallel GraphQL-to-Flaps migration running in the same window is pure platform plumbing, and Fly ships regions and products in other months. But the ratio is the signal. A team with finite engineers looked at September and decided the highest-leverage work was eight papercuts and zero announcements. When compute is commoditized, the deploy interface is where retention is manufactured — one error message at a time.
The rename that took four years
The longest-running DX item in flyctl history is its own name — and it is still not finished.
The CLI shipped as flyctl, following the kubectl convention. Users immediately wanted fly: a 2022 forum thread complains the installer auto-creates a fly symlink (and re-adds it on every update, to the annoyance of Concourse CI users whose fly means something else). In April 2024, PR #3399 renamed the root command so help text says fly. And today — September 2026 — both install scripts still ship the flyctl binary with fly as a symlink: install.sh runs ln -sf for it, and install.ps1 carries explicit symlink handling with a comment noting Windows needs admin privileges to create one.
Four years, three stages, still dual-named. The lesson for anyone starting a CLI today is to skip the archaeology: pick the short, memorable binary name on day one. flyctl was a tribute to kubectl; fly is a brand. The rename cost years of installer edge cases — including a 2023 self-update bug where the updater renamed the symlink instead of the executable on Windows — that a three-letter name from the start would never have incurred.
The corner where polish stalled
Honesty cuts both ways: there is one corner of flyctl where DX polish visibly stopped four years ago. The builtin-builder registry (internal/build/imgsrc/builtins/defaultbuiltins.go) has not been touched since August 2022. The hugo-static builtin still builds with klakegg/hugo:0.74.0, a Hugo release from 2020. There is no new static builder, and there is no fly init command at all — it was removed back in 2021.
I note this partly to correct the record — anyone telling you September brought shiny new static-site builders is describing a changelog that does not exist — and partly because the gap is instructive. Static-site deployment is the simplest possible deploy interface: a folder of files and a one-liner. Fly's scaffolding for it is frozen in 2022 while its error messages get weekly attention. For a new open CLI, that asymmetry is the opening: match Fly's error-message quality and ship the static-site one-liner the incumbent left to gather dust.
The checklist for a Render-compatible open CLI
Distilled from everything above — each item mapped to the Fly evidence that proves it matters:
- Short, memorable binary name from day one. Don't ship
bexctland spend four years becomingbex. (§4) - Self-update that survives symlinks and Windows.
fly version updateexists, but the symlink/exe dance broke it on Windows in 2023 — test the updater against your own installer on every platform. (install.sh, PR #2308) - Errors that name the actual mismatch. "Machine X is in region fra, not selected region ams" ends the debugging session; "not found/started" starts one. (#5194)
- Auxiliary data must never fail the command. If the "Latest Deploy" column can't load, print the apps without it and warn. (#5197)
- Atomic config writes. Temp file, fsync, rename — a failed write must leave the previous config intact, never an empty file that reads as "logged out." (#5190)
- Secret hygiene in logs and files. Redact tokens from debug logs; write token-bearing files at
0600. Audit both, because they fail independently. (#5204, #5192) - Warn on login footguns. If the shiny new token is shadowed by an env var, say so immediately — don't let the user discover it mid-incident. (#5173)
- Windows parity in the installer. Ship the
.ps1, handle the symlink-vs-admin-privilege problem explicitly, and testversion updateon Windows — it breaks differently there. (install.ps1) - A static-site one-liner the incumbent never shipped. Fly's builtins froze in 2022; whoever serves a folder of HTML with one command wins the segment it abandoned. (§5)
Nine items. Zero of them are "add a region." That is the point.
Written by agents, for operators that include agents
One more September detail worth naming: most of the polish commits above are co-authored by Claude. AI coding agents wrote the better error messages, the atomic config write, the secret redaction. That is fitting twice over — the CLI is the surface agents operate through (fly mcp add exists precisely to hand the platform to an agent), and agents are now good enough at the unglamorous work of DX polish that a PaaS can ship a month of it. The deploy interface is no longer just human-facing. It is the API agents drive, and its quality compounds: every specific error message is one fewer guessing loop for the operator, carbon or silicon.
Fly spent September sanding its CLI while shipping nothing announcable. Watch what your own platform's changelog rewards — announcements or papercuts — and you'll know what it thinks the product is.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



