Avatar for the reflex-dev user
reflex-dev
reflex
BlogDocsChangelog

Performance History

Latest Results

ENG-11157: add --min-instances/--max-instances to `reflex deploy` (#6884) * ENG-11157: add --min-instances/--max-instances to reflex deploy Adds first-class autoscaling controls to `reflex deploy`, backed by the instance bounds route merged server side in flexgen #4744. New `hosting.set_instance_bounds()` posts to /api/v1/apps/{app_id}/instance_bounds with only the bounds the user actually passed, so apps that don't override keep their platform defaults. It runs after the app is resolved and validated but before the export and the deployment submit: the deployment reads the bounds off the app when it is created, and failing before the export means a rejected bound doesn't cost a full build. Server details for the 400 (validation / unsupported platform) and 409 (scale in progress) cases are surfaced verbatim rather than pre-validated in the CLI. Also adds regression coverage for the existing --vmtype passthrough, pinning it from the CLI flag through to the `vm_type` submit field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ENG-11157: pass --vmtype through to GCP deploys The server has honored GCP sizing since flexgen#4744; this block predates that and silently dropped the flag — the same gap the customer originally reported. Only --region is still dropped (it comes from the connection). Verified end to end against staging: the flag reaches the deploy submit, the deployment records Single CPU Medium, and the Cloud Run revision rolls out with cpu:1/memory:1024Mi. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address review: keep bounds adjacent to submit, append new params Move set_instance_bounds() from before the export to immediately before create_deployment(). Applying it earlier meant a failed export left the new bounds on the app with no deployment to consume them, silently changing what the *next* deployment scales to — and, for a non-zero minimum, what the app costs to run. The residual window is now just the submit itself, and if that is rejected the CLI says the bounds stuck instead of letting the next deploy inherit them unannounced. Append min_instances/max_instances to the end of deploy()'s signature rather than grouping them next to vmtype, so positional callers keep binding hostname and everything after it to the same parameters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review: warn on every failed-submit path, fix export fixture The "bounds outlived the deploy" warning only fired when create_deployment returned a failure string. It raises on transport errors, and Ctrl-C during the submit is a third exit, so both left the bounds changed silently. Move the warning into a _warn_if_bounds_outlive_deploy context manager wrapping the submit, so every path out of it reports. mock_export_import_error_fn took a fixed 6 arguments while deploy() calls export_fn with 7 on reflex > 0.7.6, so it raised TypeError, not the ImportError its name promises. Both tests using it still passed, for the wrong reason. Take *args so the intended path is the one exercised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review: hedge when the bounds response is lost set_instance_bounds raises on a transport failure, which happens before the warning context around the submit is entered. A response lost after the server applied the write therefore left billable state changed with no message at all -- the one gap in "a failed deploy always says what stuck". Handle it at the call site rather than in the helper: hosting.py's convention is that transport errors propagate, and deploy() is what knows this particular call has persistent, billable side effects. The message hedges both ways, since a dropped connection says nothing about whether the write landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Number the changelog fragments for #6884 Also add the missing hosting-cli bugfix fragment for the --vmtype passthrough on Google Cloud deploys, which shipped in 5a70527 without one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
main
2 hours ago
ENG-10966: Add reflex-release package: changelog-driven release automation (#6868) * Extract the changelog-driven release pipeline into reflex-release Adds packages/reflex-release, a standalone, repository-agnostic version of the release automation currently hard-wired into this repo's scripts/ and .github/: towncrier news fragments, the changelog-as-source-of-truth publish detection, and the human-gated PyPI upload. Other repositories (reflex-enterprise, xy) can adopt the same flow with `uvx reflex-release init`. Everything the pipeline needs to know about a repository is declared in one [tool.reflex-release] table: root package and/or sub-package directory, branch policy, release timezone, lockstep groups that must release together at a single version (generalizing the reflex/reflex-base pair and its exact pin), internal packages that release without a changelog, and the source paths that require a news fragment. A single-package repository needs only root-package. The GitHub Actions workflows are scaffolded into the consuming repository rather than referenced across repositories, because PyPI trusted publishing validates the OIDC job_workflow_ref claim against the repository that owns the workflow file. `reflex-release sync` regenerates them from the configuration and `sync --check` fails CI on drift, so upgrading stays a version bump plus one command. The seven shell scripts the current pipeline shells out to are folded into the CLI, leaving only four (or five) generated YAML files in a consumer repo; repository-specific artifact checks move to an optional post_build.sh hook. The README documents installing the workflows, adding towncrier and news/ directories to a repo that has neither, growing into sub-packages, tag-derived versions, the GitHub settings that must be configured by hand, and adopting the flow in a repository that already publishes. This repo's own release pipeline is untouched; only the new package is registered with it so it can be selected in Dispatch release and Publish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Honor towncrier's directory and filename settings The fragment directory and changelog filename were hardcoded as news/ and CHANGELOG.md, so a repository that configures towncrier differently would have its pending fragments silently missed by auto-selection and its changelog written where the publish detection never looks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Address review: tag prefix, lockstep entries, gh output - tag-prefix (was root-tag-prefix) now applies to sub-packages as well: the root package is tagged <tag-prefix><version> and a sub-package <package>-<tag-prefix><version>, so a repo that tags bare 1.2.3 gets widget-core-1.2.3 rather than an inconsistent widget-core-v1.2.3. - Document and pin down the lockstep empty entry. A member dragged along by a sibling's release already gets towncrier's "No significant changes." section — even with no news directory at all — but nothing proved it and the README read as if it were manual work. Adds a regression test covering plan -> materialize -> detect for a partner with no fragments, and points the fail-closed error at the re-dispatch that fixes it. - Restore gh output in job logs. Actions (release create, workflow run, pr edit) now stream straight to the log behind an echoed command line instead of being captured and discarded; only the two calls whose stdout is consumed (pr create) or whose failure is the normal path (the release-exists probe) stay captured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Generate package checkboxes and auto-wire the drift check The Dispatch release form gets one checkbox per package again, generated from the configuration at sync time instead of hand-maintained. A lockstep group renders as a single checkbox covering every member, since selecting one always widens to the whole group anyway; the checkbox contributes all its members to the selection. Selection is carried by a folded PACKAGES scalar of `${{ inputs.<pkg> && '<packages>' || '' }}` expressions, so an unchecked box contributes an empty string and checking nothing still means auto-select. No new protocol between the workflow and the CLI: the existing selection parser already splits on whitespace and commas. workflow_dispatch accepts at most ten inputs, so dispatch-package-inputs defaults to "auto" — checkboxes while they fit, a comma-separated text field beyond that — and can be forced either way. Package names that collide once normalized to input identifiers fail at sync time rather than producing an invalid workflow, and the rendered YAML of every workflow is now parsed in tests (the folded scalar has to be indented past its key, which is exactly the kind of break that only shows up when Actions rejects the file). Because the checkbox list is generated, adding a package now changes dispatch_release.yml. The generated changelog workflow therefore runs `sync --check` on every pull request, so drift — from a new package or a stale pinned version — is a red PR rather than a surprise at release time. Also spells out in the README and the scaffold docstring why the workflows are copied rather than referenced across repositories: trusted publishing pins the publish workflow to the consuming repository, release_from_changelog's `./` call to publish.yml resolves against the repository holding the calling file, and triggers must be declared locally regardless — leaving only two job bodies as shareable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Address PR review: heading guard, publish gates, detection ranges Security: - The changelog heading guard exempted any branch whose name starts with the release prefix, and a branch name is attacker-selectable. The exemption now additionally requires the pull request to be authored by github-actions[bot], which is how the Dispatch release workflow opens it and which a contributor cannot forge. The label escape hatch already needed triage rights. Correctness: - prepare-publish auto patch-bumped any package without a changelog, not just internal ones, so a manual dispatch could publish a version the changelog never described. It now keys off internal-packages, as its docstring always claimed. - The dev-pin gate ran before pin-lockstep, so a publish-last member with a temporary dev floor on its sibling failed on metadata the build was about to rewrite. It now runs after the rewrite and judges what ships. - detect-internal diffed HEAD~1..HEAD while the workflow's paths filter matches the whole push, so an internal package changed in any but the last commit was silently skipped. It now diffs the push event's range and falls back when that base is unreachable. - Internal root packages never matched: their paths do not start with "./". Added Config.path_prefix, and the generated trigger uses the root's source dirs (failing loudly if they are unconfigured). - towncrier's title_format was honored when writing headings but the parser could only read version-led ones, so a custom format would strand every release. sync now round-trips the configured format through the parser and rejects what it cannot read back. - Lockstep validation compared list lengths, so a duplicated publish-last entry could make every member publish last; it compares sets now. - release-post rejected alphas but accepted beta/rc/dev headings. - latest-release-package = "" is documented as disabling the Latest badge but was rejected as an unknown package. - pin-exact missed PEP 503 equivalent spellings (widget.core). - ls-remote ran without credentials, so the prerelease branch-existence check always reported "missing" in private repositories. - The release commit used `commit -a`, which could sweep unrelated tracked edits into it; it now stages exactly the changelogs and the news directories towncrier consumed. - sync now removes auto_release_internal.yml once no package is internal, instead of leaving a generated workflow firing on every push. - An explicit empty root-source-dirs / package-source-subdirs was overridden by the layout guess. Also: scalar configuration keys are validated as strings at load time (a non-string release-timezone crashed inside zoneinfo), reflex_release joins the coverage sources, and the README's stale root-tag-prefix reference is corrected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Harden the release pipeline for cross-repository deployment Audit of the generated workflows against the usual GitHub Actions failure modes found the mechanics sound — no expression is interpolated into a shell script (inputs travel through env:), every checkout sets persist-credentials: false, no workflow uses pull_request_target, and the only job holding id-token runs no repository code. The weak points were in what an adopting repository can configure. - The environment gate proved reviewers exist but not that they cannot approve their own deployment. GitHub allows self-review unless it is turned off, which reduces the two-person rule to a confirmation dialog. The publish job now fails when the API reports self-review is allowed, and warns when it cannot tell. - An empty branch prefix made str.startswith() match everything: setting hotfix-branch-prefix = "" let *any* branch publish final versions, and widened the release workflow's push trigger to "**". Empty prefixes, and prefixes the main branch itself matches, are now rejected at configuration load. - init warns when the workflows end up unpinned, and its checklist now leads with the two settings that make the approval load-bearing: Prevent self-review, and naming the pypi environment in the PyPI trusted publisher (without it, any job in publish.yml can mint an upload token regardless of the gate). The README gains a Security model section covering the trust boundaries between the unprivileged build and the credential-holding upload, what the approval actually covers (and what the checksum manifest does not — it travels inside the artifact, so it is an integrity check, not a defense against a compromised build), the ways to weaken the pipeline — missing branch ruleset, trusted publisher without the environment, self-review, internal-packages, an unpinned tool, a self-consistent edit to publish.yml — and the supply-chain position of resolving the tool from PyPI at run time, with a locked alternative. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Make the self-review policy configurable, permissive by default The previous commit made a prevented-self-review environment mandatory. That is the right default for an untrusted reviewer list, but it is a policy choice, not a property of the pipeline: when the environment's reviewers are already a small trusted group, requiring a second person for a routine release buys little and costs availability. allow-self-review (default true) exposes the choice. True keeps GitHub's own behavior and the publish job simply records in the log that self-review is permitted; false restores the assertion, so the setting cannot silently regress on a repository that depends on it. Either way the upload still needs an explicit, attributed approval from someone on the environment's reviewer list, and the reviewer list itself remains the control worth auditing. The README documents it as a trade-off rather than a defect: what you keep under both postures, what you give up under the permissive one, and when to pick the strict one. The init checklist now leads with "that list is who can release" and only mentions Prevent self-review for repositories that opted into asserting it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Address review: detection scope, commit scope, config validation - detect-internal matched every path for a root internal package: the root's directory prefix is empty, so a push touching only a sibling auto-released it. Both detection and the generated paths filter now use the package's configured source prefixes, so the trigger and the detection agree instead of one over-matching the other. - The all-zero base a branch-creating push reports fell back to diffing only the final commit, which is the failure the surrounding comment says to avoid. It now treats every tracked file as changed, matching what the paths filter saw; an unreachable non-zero base keeps the last-commit fallback. - The release commit staged every package's news directory, which in a local materialize could sweep in a fragment someone was drafting. towncrier already stages the deletions it makes, so staging the changelogs alone is both narrower and sufficient — covered by a test that runs a real materialize and asserts the consumed fragment's deletion is in the commit. - Configuration: an empty main-branch is rejected (nothing could trigger or be targeted); a branch prefix nested under the main branch is rejected (git cannot create a ref below an existing one); a non-string latest-release-package is rejected instead of silently disabling the Latest marking, while the documented empty-string opt-out still works. - check_title_format converts a malformed format string into the same guidance instead of a KeyError/ValueError traceback, since the pull-request drift guard runs it on every PR. Documentation: the news fragment and README both claimed the allow-self-review = false assertion fails when the setting is missing. It does not — it fails when it can see self-review is permitted and warns when GitHub does not report the setting. Both now say so, and the tag ruleset advice follows the configured tag-prefix rather than hardcoding `v*`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Rename reflex-release news fragments to PR 6868 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Link the dispatch results and simplify root release titles The Dispatch release job summary named what it produced but never linked it: finding the pull request it opened, or the branch it pushed, meant navigating there by hand. Both are now markdown links in the summary — the PR as #<number>, the prerelease branch plus a filtered link to that branch's release_from_changelog runs — and both are emitted as notice annotations, which surface on the run page itself rather than only in the summary tab. Outside Actions there is no repository URL to build from, so the links degrade to the plain text they were before. GitHub releases for the root package are titled with their tag (v0.2.1) instead of <package>@<version>: the root package is the repository, so the tag already names the release unambiguously. Sub-packages keep the package name, which is what distinguishes their releases. Applied to reflex's own dispatch_release and publish scripts too, so the extracted package and the pipeline it was extracted from stay aligned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Read and write every file as UTF-8 The package relied on the platform's preferred encoding for all of its text I/O. On any system where that is not UTF-8 — Windows, where it is cp1252, and the ASCII locale the Windows failure reproduces under — reading a workflow template raised UnicodeDecodeError and writing a changelog or pull request body raised UnicodeEncodeError. The templates and the generated PR body both contain non-ASCII text, so sync, init and the release commands were broken there outright, not just in tests. Every read_text/write_text/open on a text file in the package and its tests now names encoding="utf-8", which is what all of these formats are: towncrier's changelogs and fragments, TOML by specification, and the files GitHub Actions reads back for $GITHUB_OUTPUT and $GITHUB_STEP_SUMMARY. Reproduce the failure on Linux with: LC_ALL=C PYTHONUTF8=0 PYTHONCOERCECLOCALE=0 uv run pytest tests/units/reflex_release Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Address review: strict self-review, commit scope, title_format - allow-self-review = false now fails on anything short of a proven prevent_self_review: true. An environment the API does not report the setting for is indistinguishable from one that permits self-review, and strict mode is opt-in, so a warning there is not a two-person rule. The error names the escape hatch for deployments that cannot report it. - The release commit stages only the changelogs of the packages in the release rather than every changelog in the repository, so a worktree carrying an unrelated changelog edit cannot have it swept in. Config lost existing_changelogs(), which nothing calls now. - towncrier's title_format = false writes sections with no version heading. The pipeline finds versions by reading those headings, so that configuration would have materialized changelogs no release could ever be detected from — silently, forever. It is now rejected where the setting is read, as is a non-string title_format, and the heading probe additionally catches AttributeError/TypeError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Bind published artifacts to what was approved Three gaps found reviewing the publish path against its own promise — that nothing reaches PyPI without a human knowing exactly what it is. verify-dist checked the version but never the distribution name. Every package in a repository publishes through the same trusted-publishing identity, and lockstep siblings deliberately share a version, so the version alone does not identify what was built: a build that produced a different distribution would have uploaded it under this package's approval, and nothing downstream would have noticed. It now checks the PEP 503-normalized name from the artifact's core metadata against the package's [project] name. The approver could not see what they were approving. The SHA256SUMS manifest existed but only inside the artifact, so the environment approval named a package and a version and nothing about the bytes. The manifest is now written into the build job's summary, which is the page a reviewer lands on from the approval prompt. Nothing recorded a published version's contents durably. SHA256SUMS lived only as a workflow artifact, which expires; after that there was no record tying a release to the bytes on PyPI. It is now attached to the GitHub release, so works against a downloaded wheel indefinitely. Also: the changelog guard that rejects hand-written version headings ran only on pull requests into the main branch, while a merged heading is a publish trigger on any branch release_from_changelog watches — hotfix branches publish final versions without touching main. It now runs on pull requests into every branch that can publish, asserted against the release workflow's own trigger list. The README's approval section documents all of it, including why PyPI attestations are deliberately not generated: it would put a third-party action inside the one job holding the OIDC credential. Applied to reflex's own scripts/release.py and workflows too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Address review: manifest, base ref, heading guard, tests - cmd_verify_dist gained a required PACKAGE, which the existing test_release tests did not set, so both verify-dist tests errored before reaching what they test. They now pass the package and assert the PEP 503 spelling difference (reflex_base metadata vs reflex-base config), plus a new case for the wrong-distribution rejection. - The SHA256SUMS attached to a release listed dist/-prefixed paths and the release notes, so `sha256sum -c` against downloaded release assets reported missing files rather than verifying them — the README claimed otherwise. The manifest is now written from inside dist/ with bare filenames covering exactly what goes to PyPI, and the publish job verifies it from there. Asserted against both call sites. - reflex's changelog workflow diffed against origin/main for the affected-package scan and the towncrier check, while now triggering on pull requests into r/pre-** and r/hotfix/**. A hotfix train diverges from main, so that folded in the train's already-released source changes whose fragments are gone, failing legitimate pull requests. All three steps now use the pull request's own base ref. - reflex's changelog workflow also still skipped the heading guard on any branch named release/*, the attacker-selectable bypass the template fixed two rounds ago. It now requires the github-actions[bot] author too. - README: verify-dist runs after the build, not before it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Collapse the reflex-release news fragments into one Twelve fragments described the review rounds that built one unreleased package, not twelve things a reader needs. The package has never been published, so its "fixes" have no released behavior to have changed: they are just how the first version came out. One feature entry names the pipeline, what init scaffolds, what the configuration covers and what gates an upload, and points at the package README for the rest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG * Revert reflex's own release pipeline to main The changes to reflex's workflows and scripts/release.py were parity edits — keeping the extracted package and the pipeline it came from in step. That is the wrong shape for this pull request: the next step is replacing the current implementation with reflex-release outright, so carrying improvements into code that is about to be deleted makes this diff harder to review for no lasting benefit. Each one lives in the package's templates, which is where it will land. What stays is only what publishing reflex-release needs: the package as a workspace member and dev dependency, its src on the pyright path and in the coverage sources, pyyaml for the workflow-rendering tests, and the package registered in the three places the existing pipeline enumerates packages by hand (the publish choice list, the dispatch checkbox, and detect.sh). Reverted: verify-dist's distribution-name check, the checksum manifest changes, the dispatch summary links, the root release title, the changelog workflow's trigger/base-ref/bot-author changes, and the test_release.py updates that supported them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnY8e5SHmgXG64jsv2ekLG --------- Co-authored-by: Claude <noreply@anthropic.com>
main
4 hours ago

Latest Branches

CodSpeed Performance Gauge
+1%
ENG-11157: wait out "being scaled" instead of failing the deploy#6886
3 hours ago
b247762
eng-11157-deploy-instance-bounds
CodSpeed Performance Gauge
-1%
ENG-10966: Add reflex-release package: changelog-driven release automation#6868
4 hours ago
ffefa18
claude/reflex-deploy-workflow-pkg-jsb589
CodSpeed Performance Gauge
0%
10 hours ago
eafdb10
carlos/update-search-breadcrumbs
© 2026 CodSpeed Technology
Home Terms Privacy Docs