Changelog
Release history for the sfp CLI — what changed in each version.
The sfp CLI carries its own version line, separate from sfp server's. Releases are published to npm as @flxbl-io/sfp and in the sfp docker images; see Install sfp. Under codev the CLI runs on the server, at the server's release.
Only releases that change the CLI are listed. A version absent from this page shipped no CLI-facing change. A version marked self-hosted is published to source.flxbl.io, where self-hosted deployments and the sfp docker images take it from.
October 2026
v52.27.0 — October 5, 2026
- A test suite in
config/sfp_testsuites.yamlcan list the source or diff packages it deploys with underdeploy_with.sfp buildexpands the suites bound to a package into a list of test classes and stores it in the artifact. A binding to a package missing fromsfdx-project.json, to a package of another type, or from a suite that selects tests by tags fails the build sfp release,sfp installandsfp validate orgdeploy a source or diff package with Apex whose artifact carries bound test suites withRunSpecifiedTestsand that list of test classes. An explicittestLevel,skipTestingandisOptimizedDeployment: falsetake precedence over the bindingsfp validate orgfails a bound package when any Apex class or trigger in its scope is covered below 75%, or below the package's coverage threshold when that is higher, by the bound suites alone. The failure names the classes below the threshold and the suites that ran- The
sfp validate orgreport lists only the packages that failed, captioned "N of M failed", and reports a coverage failure only for a package whose coverage verdict failed sfp buildandsfp quickbuildskip a package whose build commit is an ancestor of the commit that last published it on the same branch, so the package is not published again under a new version. Other packages in the run still build; skipped packages are listed under "Packages skipped as superseded" and inbuildResult.jsonwithstatus: skippedandskipReason: supersededsfp buildandsfp quickbuildacceptversionNumber: 0.0.0.LATESTon a dependency to a package of the same project whose version is0.0.0.NEXT. When the dependency is not part of the build, it resolves to the version last published on the branch as recorded by sfp server, and that exact version is written into the dependent's artifact. The build fails when the published versions cannot be read from sfp serversfp releasetreats a deployment that stops with an error before every package is attempted as failed: the packages not yet deployed are reported as failed instead of skipped, later release definitions are not deployed, and the command exits non-zero. Writes of deployment information that conflict with a concurrent release are retriedsfp repo patch --releasedefinitions <domain>/<release>refuses a release candidate that is aborted or has an artifact without a version. A reference such ascore/RC-A.jsonis fetched from sfp server unless a file exists at that path
September 2026
v52.26.0 — September 30, 2026
- A diff package with Apex that sets
isOptimizedDeployment: falsein its descriptor deploys withRunLocalTestsinsfp release,sfp installandsfp validate org, instead ofRunRelevantTests.skipTestingstill takes precedence sfp validate orgprints the deployment'sTestLevelfor each package when tests run as a separate step after the deploymentsfp insights loginscounts unique users with a single aggregate query, so the report no longer fails for orgs with more users in the 30-day window than one query batch returnssfp server pool scratch initauthenticates each scratch org leased from a snapshot pool before preparing it. An org that cannot be authenticated is reported as an error in the pool failure summary and is not deleted
v52.25.0 — September 29, 2026
sfp release --quickdeploy <deployId> --payload-fingerprint <fingerprint>commits the validation thatsfp release --checkonlyreturned instead of deploying the package again. Pre-deployment steps are skipped, post-deployment steps run as usual, and the release is recorded as a plain release is. The release is refused when the fetched artifact's fingerprint differs from--payload-fingerprintsfp release --quickdeploy-fallbackdeploys the package in full when Salesforce rejects the validated deploy id. Without it, a rejected id fails the release and leaves the org unchangedsfp sfdmu runscans the target reports sfdmu writes, prints a records summary, and fails when any row was rejected.--no-failonrowerrorsreports rejected rows as a warning without failing. The--jsonresult carries the count underrowFailuressfp sfdmu run,sfp pushandsfp installprint "Command completed, but N record(s) were rejected by the target org" in place of "Command succeeded." when sfdmu rejected rowssfp publishskips a version already in the registry only when the stored artifact was built from the same commit and package version id. A different source fails the publish withVERSION_COLLISION, an artifact whose source cannot be read fails withIDENTITY_UNVERIFIED, and neither records release metadata, the version baseline or git tags. An artifact the internal registry rejects this way is not published to the external registrysfp server environment lock --hatchet-run-idnames the Hatchet run that owns the lock; the server releases the ticket once that run ends, including while it is still queuedsfp analyzeon a pull request reuses the previous architecture and version-assist verdicts when the net diff is unchanged or the change since the last analysed commit is not significant. SetincrementalReviewEnabled: falseinanalyze.yamlor the server project configuration to always run a fresh analysis
v52.24.0 — September 27, 2026 · self-hosted
sfp release --checkonlyvalidates a single-package domain against the target org with one check-only deployment and returns the validated deploy id. Nothing is committed to the org and no release history is recorded; it cannot be combined with--dryrunsfp release --jsonreports the deployment error for a failed package instead ofnull- The CLI adds the operating system certificate store to the certificate authorities it trusts, so
sfp auth loginandsfp server updatereach an sfp server whose certificate is issued by a private CA. On Node.js earlier than 22.19, setNODE_EXTRA_CA_CERTSinstead sfp auth login --global-authcontinues when the server configuration cannot be read, using--sfp-server-urlas the sign-in callback basesfp server callback postaccepts"comment": { "key": "<key>" }in the context to keep one comment per pull request, updated in place, on GitHub and Azure DevOpssfp validate orgreports a run with nothing to deploy as "No changes to validate" instead of "Validation passed". The check still concludes as successsfp insights connected-apps --jsonreturns the org's external client apps underexternalClientApps, and underhiddenManagedAppsthe number of managed connected apps left out without--include-managed. When the org cannot list external client apps, the reason is given underexternalClientAppsSkippedsfp insights contentlists built-in list views that Salesforce reports without an object asStandard views (no object)
v52.23.1 — September 24, 2026
- Improvements to check-only validation in
sfp validate org sfp validate orgnow respectsdependencyOnDomainssfp validate org --deploywithtestsalways runs Diff packages withRunRelevantTestssfp sync org --releaseconfigaccepts multiple domains and syncs all of them
v52.23.0 — September 23, 2026 · self-hosted
sfp validate orgbuilds its validation check from the same report as every other surface, laid out for the platform configured on sfp serversfp validate orgno longer writes a second check for a run sfp server already reports- Validation results carry per-class coverage for each package
sfp server callback postkeeps one validation comment per Azure DevOps pull request, updated in place
v52.22.0 — September 23, 2026
sfp releasecandidate generateno longer fails for a source-only release configuration with no promotion target- String replacement globs match the same way on install,
sfp pushandsfp pull sfp pushreports only the replacements that changed a file, and--jsonlists the rules that matched nothing underunmatchedRules- Non-regex replacement values are applied as written, so a
$in a value is kept
v52.21.2 — September 22, 2026
- New
sfp releasecandidate sync-checklists the domains on a branch whose last release to an environment did not succeed sfp releaserecords a release attempt for each domain on sfp server- Improvements to AI credential handling in
sfp analyze
v52.21.1 — September 22, 2026
- Source packages with
isOptimizedDeployment: falsedeploy withRunLocalTestsagain - Fixes to command aliases and topic descriptions for
sfp pull,sfp push,sfp sfdmu run,sfp server review-envsandsfp server variable
v52.21.0 — September 21, 2026
sfp release --branch <branch>releases the latest published release candidate of each domain on a branch tracked by the project, in place of--releasedefinitionor--releasecandidate; the three flags are mutually exclusive.--repositoryand--sfp-server-urlare both required with it, and--domain— repeatable — restricts the release to the domains named. A candidate is eligible only when its domain build succeeded and the candidate was not aborted.sfp release --branchprints the resolved domain, release candidate and commit as a table, then the branch head commit and the domains whose candidate commit differs from it, and lists the domains skipped for want of an eligible candidate. The same resolution is carried underbranchResolutionin the--jsonoutput.sfp release --branchfails before it releases anything when the branch is not among the branches tracked in the project's server configuration, when a--domainis not one of the branch's domains, or when no selected domain has an eligible candidate.
v52.20.1 — September 20, 2026
sfp sandbox createsets a sandbox's add-on features fromfeaturesin the--definition-filedefinition — an array of strings such asSandboxStorage. When the definition also names asourceSandboxName, the features recorded on that source sandbox take precedence over the ones in the definition.sfp sandbox createfails when the lookup ofsourceSandboxNameerrors, instead of warning and creating the sandbox without a source. A source sandbox that simply returns no record is still reported as not found, and creation continues without a source.
v52.20.0 — September 20, 2026
- A source or diff package that carries a Flow in
pre-destructive/flows/orpost-destructive/flows/has that Flow deactivated and every one of its versions deleted through the Tooling API before the metadata deployment, and the Flow is dropped from that deployment. The deletion is not part of the deployment transaction, so it is not undone when the deployment fails. See Destructive changes. - A Flow that is absent from the org is recorded as already absent and installation continues. A lookup, deactivation or deletion failure is reported as a warning, that Flow's delete is skipped, and the remaining components are installed. Progress is folded into a
Destructive Flowslog group that closes with a table of each Flow, whether it was deactivated, how many versions were deleted, and the result. enableFlowDestructiveHandlingin a package'ssfdx-project.jsondescriptor turns that handling off for the package, restoring plain metadata deletion. It defaults to true and is independent ofenableFlowActivation. The handling is also skipped, and the plain Flow member retained, for a check-only deployment and for a dry-run installation.sfp repo diff --create-packagestages two synthetic source packages when the changed packages disagree onenableFlowDestructiveHandling— the metadata from the packages that keep it first, then the opted-out metadata in<name>-flow-optout— and each is deployed separately. Packages that agree still produce one synthetic package.- An installation whose components are all removed — a package whose only members were destructive Flows — completes without a metadata deployment rather than submitting an empty one.
sfp org flow activate,sfp org flow deactivateandsfp org flow cleanupescape--developernameand--namespaceprefixin the SOQL that looks the Flow definition up. An explicitly empty--namespaceprefixnow matches only flows with no namespace; it was previously ignored, matching any namespace. Each command's output is folded into a log group.sfp org flow cleanuplogs each version as it is deleted, with its position in the run, and names the attempt and the Salesforce error when a deletion is retried.
v52.19.2 — September 19, 2026
sfp server updatepauses Hatchet worker assignments in bounded cycles instead of holding one pause for the whole drain.--pause-timeoutsets the maximum seconds of a single pause, defaulting to 600; when it expires without a confirmed drain the assignments are restored for a 300-second cooldown and a new cycle starts, until the drain is confirmed or--drain-timeout— still 3600 seconds by default — is spent.--pause-timeoutmust not exceed--drain-timeout, and both flags reject a value below 1.sfp server updateupdates the tenant files and pre-pulls the new images before it pauses assignments, rather than pre-pulling in parallel with the drain, and recreates Caddy inside the pause window.sfp server updateaborts before it stops any service when no drain is confirmed within that budget. Assignments are restored first, and the error names the number of pause cycles, the running and queued workflow counts, and the names of up to 100 running tasks.sfp buildcontinues when a package it has just built is an ordering-only dependency of a package still to be built — a source, diff or data package of the same project, which dependency resolution removes from the dependent's resolved dependency list. Stamping the completed version onto a dependency that is no longer listed previously threw and failed the build.sfp buildfails on an invalidpreDeploy/replacements.ymlin a package: a missing or emptyname,globorpattern, or an environment value that is not a string, boolean or number. It previously logged a warning and built the package without its replacements.- Replacement YAML — a package's
preDeploy/replacements.yml, and the file passed tosfp push --replacementsoverride— keeps an unquoted date such as2026-09-17or timestamp such as2026-09-17T12:34:56+05:30as its original text instead of reading it as a date. A boolean or numeric value is applied as its text, and a null, array or object value is a configuration error. - A replacement whose value is an empty string is applied, removing the matched text, and an exact org alias takes precedence over
defaulteven when the alias value is empty. An empty value was previously treated as no value, leaving the pattern in place. This coverssfp pushand the source-package install path thatsfp installandsfp releaserun. sfp pushtreats a package'spreDeploy/replacements.yml, or--replacementsoverride, as authoritative for that package: thereplacementsentries insfdx-project.jsonno longer apply to it as well. A package with no package-level configuration keeps the project-level replacements, and in a push carrying both kinds those project rules are matched against project-relative paths, so a hidden directory in the checkout path no longer suppresses a match.sfp push --replacementsoverridemerges the override entries into the package configuration by replacement name, the override's environment values taking precedence, instead of applying both lists.sfp pushfails withUnable to prepare push replacements: <reason>when it cannot prepare the replacements. It previously logged a warning and pushed the source without them.sfp pushapplies package replacements to every file a component carries rather than only its top-level content path, and no longer counts a file in a sibling directory whose name starts with the package directory's name as being inside the package.sfp pushremoves the temporary conversion context it creates for replacements once the deployment is submitted, including when the conversion or the submission fails.- The header line every command prints carries
-Build:<number>once when a build number is set, and omits it otherwise. It previously repeated the-Build:label, and printed an empty one when no build number was set.
v52.19.1 — September 17, 2026
sfp analyzeprints the analysis result as JSON only when--jsonis set. It previously printed the raw result to stdout alongside the human-readable output whenever a linter named in--fail-onhad a blocking finding or could not run.- The
[AI_RESPONSE]entry carrying a prompt's full response — written by the architecture and version-assist linters ofsfp analyze— is logged at debug level rather than info, so it no longer appears in the default console output. It is dispatched straight to the run's logger, so a file or log sink attached to the run still receives it.
v52.19.0 — September 16, 2026
sfp server updatelogs the Hatchet worker-assignment commands it runs over SSH against a remote host, and their output, at debug level rather than info. A command that exits non-zero is still reported at error level.sfp server updatenames the worker-assignment pause before it runs, and on resume reports how many workers had their assignments restored and how many registrations the restart replaced. That report is printed whether the update finishes or fails.
v52.18.2 — September 15, 2026
sfp server callback post --context-idtakes the UUID of a callback context held on the server, in place of the inline JSON on--context. The two flags are mutually exclusive and exactly one is required; the command reads the context from the server with the server URL and application token before it posts.sfp server callback postfails before posting on a--context-idthat is not a UUID, a--contextthat is not valid JSON, a context that is not a JSON object, and a context whoseusername,accessLevel,targetOrg,taskId,taskTypeormessageis not a string.
v52.18.0 — September 15, 2026
- A package install that composes partial CustomObject metadata — layout assignments from
mutators/layout-assignments.yml, and the search-layouts contribution that augments them — carries the target org's object settings into that partial: the supported feature flags,description,gender,startsWith, each targeted record type'sdescription, and the name field'strackHistoryandtrackFeedHistory. A CustomObject deployment resetsallowInChatterGroups,enableActivitiesandenableReportsand clearsdescription,genderandstartsWithwhen they are omitted, so the composed partial previously reset them on install. - That composition stops before deployment when the org's CustomObject metadata is incomplete — a missing label or
nameField, an AutoNumber name field with nodisplayFormat, a value that is not a valid boolean, or anenableBulkApi/enableSharing/enableStreamingApigroup that is inconsistent — instead of composing a partial that would reset a property. - Each setting is written only when the Metadata API version supports it. The version is the one the install deploys with, falling back to the package's
apiVersionand then to the component set's. sfp validate orgruns its AI error analysis before it publishes the validation result, so the analysis reaches the published result and its markdown as well as the CI check. It previously ran only while the CI check was being created.sfp validate orgcancels the AI error analysis at its timeout — three minutes, unlesserrorAnalysis.timeoutin the AI configuration sets another, in milliseconds — and aborts the agent session rather than leaving the request in flight. A transient failure is retried up to three attempts, after 5 and 10 seconds, within that same deadline.- The validation markdown records
AI error analysis unavailable: <reason>when the analysis produces no insight, where the reason isno credentials,timed out,unparseable response,attempts exhaustedoranalysis failed. sfp validate orgreads its AI configuration from the validation worktree —config/ai-assist.yaml, then the.ymlspelling, thenconfig/ai-architecture.yamland.yml— rather than from the directory the command was started in. The analysis is skipped when the run failed because no changes were detected in the packages to be built, and runs whenever--opencode-serveris set.sfp releasecandidate unbundleremoves the file when a conflict has no copy of it on the side--conflict-strategyselects, instead of resolving markers in a file that side deleted.sfp releasecandidate unbundlefails when it cannot resolve the new SHA of a cherry-picked commit. It previously logged a warning and continued, leaving the deployment step lineage incomplete.sfp releasecandidate unbundle-analyzecounts only re-applied commits in the kept-commit total it prints and returns. A commit that turned out empty after conflict resolution, and was skipped, is no longer counted.sfp auth loginandsfp server auth loginserve branded sign-in result pages on the CLI's loopback receiver. The failure page escapes the detail returned by the authorization server and namessfp auth loginas the command to run again.sfp publishincludes the repository and an ISO 8601 timestamp in the webhook events it triggers on sfp server. The repository is read fromSFP_REPOSITORY,GITHUB_REPOSITORYorGITLAB_REPOSITORY, in that order.
v52.17.0 — September 11, 2026
sfp publish --pushgittagpushes the tags for all published packages in a singlegit push, instead of one push per package. A transient failure — a dropped connection, an unresolved host, an HTTP 5xx — is retried three times, after 2, 5 and 15 seconds. An authentication, permission, protected-tag or ruleset rejection is not retried.sfp publishcompletes when the tag push does not, instead of failing the run. It lists the tags that still need pushing and thegit push origin refs/tags/…command that pushes them, with remote URLs and credentials stripped from the reported error, and leaves the--gittaglimitand--gittagagecleanup for that retry.sfp releasecandidate unbundle --source-branchdefaults to the branch recorded on the release candidate, falling back tomainwhen the candidate records none. It previously always defaulted tomain. An explicit value still takes precedence.sfp releasecandidate unbundleremoves its temporary worktree when the run fails as well as when it succeeds, and raises a failure through the command's own error handling — so--jsonreports it — instead of exiting immediately.sfp releasecandidate unbundlereports how many manual deployment steps were re-anchored onto the rewritten branch and how many were flagged for review. The command fails when the server cannot record that anchor migration, after the branch has been pushed.
v52.16.1 — September 10, 2026
sfp server updatepauses Hatchet worker assignments before it drains, then waits only for workflows that occupy a worker slot. Queued tasks are no longer waited on; they stay pending and run once the workers return. The drain is checked again after the image pre-pull and the Caddy recreation, and the paused workers are restored when the update finishes or fails.sfp server updateaborts before it stops any service when the drain fails or the running-workflow count cannot be read. It previously logged a warning and continued with the update.--forcestill skips both the pause and the drain, and--drain-timeoutstill bounds the wait, in seconds, defaulting to 3600.sfp scratch deletecompletes without error when the Dev Hub holds no active scratch org record for the username. The lookup previously raisedNo ActiveScratchOrg found for username <username>, which the command reported as a missing org or a permissions failure.
v52.16.0 — September 8, 2026
sfp org login --server --default-devhubaccepts--repository(alias--repo) to authenticate with the DevHub selected for that project, and fails when the server resolves the DevHub from anywhere else.sfp server org get-default-devhubtakes the same flag. Both name where the DevHub came from — the project's default DevHub, a legacy project registration, or the tenant default.sfp scratch loginwithout--devhub-usernamehas the server resolve the DevHub that owns the scratch org, instead of trying the tenant default DevHub and then each registered DevHub in turn.sfp sandbox loginwithout--production-usernamehas the server resolve the production org bound to the sandbox, instead of trying each registered production org in turn. A sandbox registered without a production connection logs in, and the output omits theProduction Orgline.sfp server pool monitornames the pool it is reconciling when it reads sandbox state from Salesforce, so the state covers that pool's own instances rather than every sandbox under the Dev Hub.sfp scratch deletefails when the Dev Hub rejects the deletion, instead of reporting success. A username with no active scratch org record in the Dev Hub is left alone.--passphraseon the SSH-based server commands —sfp server init,start,stop,status,logs,scaleandupdate— is passed to the SSH connection, so an encrypted--identity-filecan be used. The key is read when the flags are parsed and is no longer written into the lifecycle configuration that debug logging serialises.- Those same commands report an SFTP error raised while checking for a directory on the remote host, instead of treating the directory as absent.
v52.15.1 — September 7, 2026
sfp server updateandsfp server initcopy the Supabase schema to a remote host through a temporary archive that is extracted and then removed. Removing it now tolerates an archive that is already gone, so a repeated or concurrent run against the same tenant directory no longer fails after the extraction has succeeded.
v52.15.0 — September 4, 2026
sfp auth loginandsfp server auth loginsign in to flxbl cloud when no server URL is given, through the global auth service over OAuth 2.1 with PKCE and a loopback receiver bound to127.0.0.1.--sfp-server-urlno longer defaults tohttp://localhost:3029.sfp auth login --sfp-server-urlreads the server's/sfp/api/configbefore signing in: a server that reports itself as self-hosted routes the sign-in to the auth service it publishes, and an unreachable URL fails before a browser opens.--global-authand--no-global-authforce either service and no longer carry a default.sfp auth loginprints the sign-in URL before it opens a browser, so a session where no browser opens can still complete the sign-in. On the self-hosted path the browser is redirected to the server's hosted/auth/callbackpage, which shows ansfp1_login code to paste into the terminal when it cannot reach the CLI's loopback.sfp installandsfp releasegroup the output of each pre- and post-deployer and of thepreDeploymentandpostDeploymentscripts, and keep the component checksum skip pre-fetch lines inside the eligibility group.
v52.14.3 — September 2, 2026
sfp analyze --output-format githubrenders code-analyzer and architecture findings as a Markdown list — severity, rule, location and gate on the first line, the message on the next — instead of an HTML table.--output-format markdownkeeps its tables.sfp analyzeandsfp project version suggestpass the architecture and version-assist instructions to the AI as its system prompt, rather than prepending them to the pull request content being analysed. The architecture instructions return the empty JSON result the parser reads when there is no architecturally significant change, in place of a plain sentence.
v52.14.2 — September 2, 2026
sfp project preflightreports a repository with nosfdx-project.json, or one that cannot be parsed, as anot-configuredstatus with the cause listed under issues. Previously the read error aborted the run.
v52.14.1 — September 2, 2026
sfp cascade merge-assistno longer stops responding when the AI agent asks to access a directory outside the working tree. The request is answered instead of left pending.
v52.14.0 — September 1, 2026
sfp validategrounds its AI analysis of a failure. Commands it suggests are checked against the CLI's own command set, and browserforce settings are reported as org-UI settings rather than as CLI flags.- A package build reports a missing Package2 registration in the Dev Hub as that, instead of as a generic creation failure.
- Repository and npm-registry calls to sfp server report the server's own error message instead of a generic substitute.
August 2026
v52.12.0 — August 30, 2026
sfp analyzeruns the version-assist AI linter only when it is asked for.--version-assistruns it,--no-version-assistkeeps it off even when the project enables it, and with neither flag it runs only when the server project settings explicitly enable it.- A package install that fails on a transport error — a dropped connection, DNS, or TLS failure — names the underlying cause instead of reporting
fetch failed. sfp server scalesizes both Hatchet worker pools.--workerssets the general pool and sizes the long-running pool proportionally;--longrunning-workerssets the long-running pool explicitly;--drain-timeoutbounds the wait for in-flight work, and--forcescales down without draining.
v52.10.2 — August 27, 2026
sfp analyzereports a linter that was skipped as skipped, instead of reporting it as a pass.
v52.10.0 — August 26, 2026
sfp validate org --waittimesets the metadata deployment wait for each package, in minutes. Defaults to 120.sfp org drift analyze --max-ai-findingsskips the AI review when the mechanical candidate count exceeds the threshold. Defaults to 50.sfp apextests resumebounds result fetching, so a large-org run stores its results instead of abandoning them on a timeout.sfp server initcreates the admin team, so a newly initialised admin can manage the organisation.
v52.9.3 — August 25, 2026
- Dependency resolution ignores source package dependencies declared inside the same project.
SFDMU
Run the bundled sfdmu (Salesforce Data Move Utility) against an org. From sfp v51 sfdmu is bundled with sfp and is no longer installed as an `sf` plugin, so use this as the drop-in replacement for `sf sfdmu run` in pre/post-deployment and custom scripts. Point --path at a directory containing an export.json (or --file at the file itself) to move records between csvfile and the target org. After the run, the per-row reports sfdmu writes to target/ are scanned the same way `sfp install` does: a records summary is printed and the command fails if any row was rejected, even though sfdmu itself exits successfully. Pass --no-failonrowerrors to report rejected rows as a warning without failing.
Common Errors
Next Page