Appendix G: A Technical Guide for Repository and Organization Administrators¶
Introduction¶
As Salesforce organisations scale their use of GitHub, CI/CD pipelines multiply. Individual project teams duplicate workflow logic, authentication steps drift out of sync, and a security policy change that should affect every repository ends up requiring dozens of separate pull requests. The answer is centralisation: defining automation once, then composing and calling it across every repository in the organization.
GitHub Actions provides three distinct mechanisms for code reuse: reusable workflows, composite actions, and starter workflow templates. It also provides a special organizational repository (.github) that acts as the canonical source for sharing them. Each mechanism solves a different problem, and understanding when to reach for each one is the first step toward a clean, maintainable CI/CD architecture.
This appendix targets GitHub organization administrators and senior contributors managing Salesforce DX (SFDX) projects. Code examples assume familiarity with the Salesforce CLI (sf) and standard SFDX project layouts.
Action version note: every uses: reference on this page names the version that was current at the time of writing. Where a page references the organisation's own reusable workflows and composite actions by tag, the tag is an immutable release tag; third-party actions are shown at their floating major tag, a choice many organisations make for highly trusted actions such as actions/checkout (the trade-off is explained in Appendix G). Newer major versions may introduce breaking changes: review the release notes for an action before upgrading, and validate in a non-production pipeline before promoting to protected branches. Dependabot and Renovate (Section 7) open PRs when new versions are published, providing a structured upgrade path.
The GitHub Actions Component Landscape¶
Before diving into implementation, it is worth establishing a clear mental model of the components and how they relate to one another.
┌───────────────────────────────────────────────────────────┐
│ Calling Repository │
│ │
│ .github/workflows/pr-validation.yml ← Caller Workflow │
│ │ │
│ ├── uses: org/.github/.github/workflows/deploy.yml │ ← Reusable Workflow (a job)
│ │ │
│ └── steps: │
│ - uses: org/.github/.github/actions/sf-auth │ ← Composite Action (a step in a job)
└───────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────┐
│ org/.github (Shared Repository) │
│ │
│ .github/workflows/ ← Reusable Workflows │
│ .github/actions/ ← Composite Actions │
│ workflow-templates/ ← Starter Workflow Templates │
└───────────────────────────────────────────────────────────┘
The three building blocks:
- Reusable workflows are complete workflow files that can be called as an entire job. They define their own runners and job structure. Think of them as pipeline templates.
- Composite actions are collections of steps bundled into a single action and invoked as a single step within a job. Think of them as shared task functions.
- Starter workflow templates are pre-built workflow files that GitHub surfaces in the "Actions" tab of any repository in the organization, giving developers a governed starting point rather than a blank page.
The Organizational .github Repository¶
GitHub treats a repository named .github within your organization as a special shared resource. It is the central hub for all cross-repository automation assets.
Configuration Repository Structure (Subset of full list)¶
<my-org>/.github/
├── .github/
│ ├── workflows/ # Reusable workflows (called with `uses:`)
│ │ ├── sf-prettier-verify.yml
│ │ ├── sf-code-analyzer.yml
│ │ ├── sf-apex-test-selection.yml
│ │ ├── sf-validate-deployment.yml
│ │ └── … # (see Appendix A.2 for the full list)
│ └── actions/ # Composite actions (called as steps)
│ ├── setup-sf/
│ │ └── action.yml
│ ├── sf-auth-jwt/
│ │ └── action.yml
│ ├── sf-auth-sfdx-url/
│ │ └── action.yml
│ └── post-pr-comment/
│ └── action.yml
└── workflow-templates/ # Starter templates (shown in the Actions UI)
├── sf-pr-checks.yml
└── sf-pr-checks.properties.json
Note: The distinction between
workflow-templates/(at the root) and.github/workflows/(inside the.githubsubdirectory) often causes confusion. Templates live at the repository root and are surfaced in the UI; reusable workflows live inside.github/workflows/and are called programmatically from other workflows.
Access Control¶
The visibility of the .github repository determines who can use assets from it:
- Public
.githubrepository: Reusable workflows and composite actions are callable from any repository (including those outside your organization). - Internal
.githubrepository (GitHub Enterprise): Accessible from any internal or private repository within the enterprise. - Private
.githubrepository: Accessible only from repositories within the same organization. This is the recommended setting for most enterprise Salesforce teams.
Additionally, at the organization level, administrators can restrict Actions to only allow workflows and reusable workflows from within the organization. This prevents developers from accidentally pulling in unvetted third-party actions (GitHub Docs: Disabling or limiting GitHub Actions for your organization).
Reusable Workflows¶
A reusable workflow is a standard GitHub Actions YAML file that exposes a workflow_call trigger. Any other workflow in the organization can invoke it as a complete job, passing in typed inputs and secrets.
Anatomy of a Reusable Workflow¶
The excerpt below is abbreviated from the validation workflow, shown in full in Appendix A.2.12.
# <my-org>/.github/.github/workflows/sf-validate-deployment.yml
# (abbreviated)
name: Metadata Validation and Apex Tests
on:
workflow_call:
inputs:
test-level:
description: "Test level for validation run"
required: true
type: string
wait-minutes:
description: "Minutes to wait for validation to complete"
required: false
type: string
default: "60"
artifact-name:
description: "Name of the delta package artifact to download"
required: false
type: string
default: "delta-package"
sf-username:
description: "Username of the integration user in the validation org"
required: true
type: string
sf-instance-url:
description: "Instance URL of the validation org"
required: true
type: string
secrets:
SF_JWT_KEY:
description: "JWT private key for the validation org"
required: true
SF_CLIENT_ID:
description: "Client ID of the External Client App in the validation org"
required: true
jobs:
validate-deploy:
name: Metadata Validation
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v7
- name: Set up Salesforce environment
uses: ./.github/actions/setup-sf
with:
install-code-analyzer: "false"
install-sfdx-git-delta: "false"
- name: Authenticate to validation org
uses: ./.github/actions/sf-auth-jwt
with:
jwt-key: ${{ secrets.SF_JWT_KEY }}
client-id: ${{ secrets.SF_CLIENT_ID }}
username: ${{ inputs.sf-username }}
instance-url: ${{ inputs.sf-instance-url }}
alias: validation-org
- name: Download delta package
uses: actions/download-artifact@v8
with:
name: ${{ inputs.artifact-name }}
path: delta/
- name: Validate delta deployment
run: |
sf project deploy start \
--dry-run \
--target-org validation-org \
--manifest delta/package/package.xml \
--test-level ${{ inputs.test-level }} \
--wait ${{ inputs.wait-minutes }}
The key elements of a reusable workflow definition:
on.workflow_call: the trigger that makes this file callable from other workflows.inputs: typed parameters (string,boolean,number) with optional defaults.secrets: explicitly declared secrets the called workflow needs. These are separate from inputs and cannot be referenced inwith:expressions on the caller side.outputs: values surfaced back to the caller, mapped from job outputs.
Calling a Reusable Workflow¶
# any-salesforce-repo/.github/workflows/pr-validation.yml
name: PR Validation
on:
pull_request:
branches: [main]
jobs:
validate:
uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2.3.1
with:
test-level: RunRelevantTests
sf-username: ${{ vars.SF_USERNAME_PROD }}
sf-instance-url: ${{ vars.SF_INSTANCE_URL_PROD }}
secrets:
SF_JWT_KEY: ${{ secrets.SF_JWT_KEY_PROD }}
SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID_PROD }}
The uses: key references the reusable workflow using the format {owner}/{repo}/{path}@{ref}. The @{ref} component can be a branch name, tag, or commit SHA. In production, pin it to an immutable release tag (@v2.3.1): a tag that is never moved fixes the behaviour of the called workflow and prevents unexpected breakage from upstream changes. The versioning strategy below explains the trade-off against a floating major tag.
Passing Secrets: Explicit vs. inherit¶
GitHub provides two mechanisms for forwarding secrets to a called workflow.
Explicit passing (recommended for clarity):
Implicit inheritance (convenient, use with caution):
With secrets: inherit, every secret available to the caller is automatically forwarded to the called workflow. This is convenient in same-organization calls, but it means the called workflow has access to secrets it may not need; this is a relevant consideration in least-privilege security models.
Important: Secrets are only passed to directly called workflows. In a chain A → B → C, workflow C receives secrets from A only if B explicitly forwards them.
secrets: inheritat each hop is required for full propagation.
Nesting and Limits¶
As of late 2025, GitHub raised the nesting limit to 10 levels of reusable workflow calls, and a single workflow run can call up to 50 reusable workflows in total (GitHub Changelog: New releases for GitHub Actions, November 2025). In practice, deep nesting should be treated as an architectural smell; two or three levels is typically the maximum useful depth.
A calling job that uses uses: to invoke a reusable workflow cannot contain additional steps:. The entire job is delegated to the called workflow. If you need steps before or after the delegated logic within the same job, use a composite action instead.
Composite Actions¶
A composite action bundles multiple steps into a single unit that is consumed as a uses: step within a job, not as a job itself. Where a reusable workflow replaces a job, a composite action replaces an entire step.
Anatomy of a Composite Action¶
Each composite action lives in its own directory and is defined in an action.yml file. The excerpt below is the start of the sf-auth-jwt action, shown in full in Appendix A.3.2.
# <my-org>/.github/.github/actions/sf-auth-jwt/action.yml
# (abbreviated)
name: "Authenticate to Salesforce (JWT)"
description: "Authenticates to a Salesforce org using the JWT bearer flow."
inputs:
jwt-key:
description: "The JWT private key (PEM format)."
required: true
client-id:
description: "The External Client App consumer key (client ID)."
required: true
# ... username and instance-url, both required, and alias, with a default
runs:
using: composite
steps:
- name: Write JWT key to temporary file
shell: bash
env:
JWT_KEY: ${{ inputs.jwt-key }}
run: |
umask 077
printf '%s\n' "$JWT_KEY" > /tmp/sf-jwt-key.key
# ... run sf org login jwt, then remove the key file
# with if: always() so the key never survives a failed login
Key structural requirements:
runs.using: 'composite'- declares this as a composite action (as opposed to a JavaScript or Docker action).- Every
runstep within a composite action must declare ashell. This is a requirement unique to composite actions. - Inputs are referenced via
${{ inputs.<name> }}. In arunstep, pass an input to the script throughenv:, as the excerpt does, rather than writing${{ inputs.<name> }}in the script, so that a quote or a$in a value cannot change the command. Unlike reusable workflows, inputs to composite actions are strings only: they have no typed boolean or number variants. Callers must pass'true'or'false'as strings and handle coercion in the action's shell logic. Thesetup-sfaction in the reference implementation (Appendix A.3.1) is a concrete example: itsinstall-code-analyzerandinstall-sfdx-git-deltainputs are compared as strings (if: inputs.install-code-analyzer == 'true').
Calling a Composite Action¶
# any-salesforce-repo/.github/workflows/validate.yml
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Authenticate to sandbox
uses: <my-org>/.github/.github/actions/sf-auth-jwt@v2.3.1
with:
jwt-key: ${{ secrets.SF_JWT_KEY_STAGING }}
client-id: ${{ secrets.SF_CLIENT_ID_STAGING }}
username: ${{ vars.SF_USERNAME_STAGING }}
instance-url: ${{ vars.SF_INSTANCE_URL_STAGING }}
alias: "ci-sandbox"
- name: Validate
run: |
sf project deploy start \
--dry-run \
--source-dir force-app \
--target-org ci-sandbox \
--test-level RunLocalTests \
--wait 30
Notice that the composite action is a step, and additional steps freely appear before and after it in the same job. This is the key ergonomic difference from reusable workflows.
Secrets in Composite Actions¶
Composite actions do not have an on.workflow_call.secrets block. Secrets must be passed as inputs. This is an intentional design: the calling workflow holds the secret context and passes only what the action needs. The action receives it as an input string and must handle it accordingly (e.g., writing to a temp file, not echoing to logs).
- uses: <my-org>/.github/.github/actions/sf-auth-jwt@v2.3.1
with:
jwt-key: ${{ secrets.SF_JWT_KEY_STAGING }} # Secret passed as input
client-id: ${{ secrets.SF_CLIENT_ID_STAGING }} # Secret passed as input
username: ${{ vars.SF_USERNAME_STAGING }}
instance-url: ${{ vars.SF_INSTANCE_URL_STAGING }}
alias: 'my-sandbox'
Starter Workflow Templates¶
Starter workflow templates solve a different problem: discoverability and governance at the moment a developer creates a new repository. Instead of starting from scratch, developers see your organization's approved templates in the GitHub "Actions" tab.
This functionality may be irrelevant at strictly-governed organisations, such as federal agencies, where all workflows must be approved, even to create a workflow.
In a slightly relaxed situation, template workflows may be pre-approved initially and able to be installed by any repository editor in a feature branch, and then any edits to those files would require organization administrator approval.
Creating a Starter Template¶
Templates live in the workflow-templates/ directory at the root of the org .github repository (not inside .github/workflows/). Each template requires two files: the workflow YAML and a .properties.json metadata file with the same base name.
The workflow file:
# workflow-templates/salesforce-pr-validation.yml
name: Salesforce PR Validation
on:
pull_request:
branches: [main]
jobs:
validate:
uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2.3.1
with:
test-level: RunRelevantTests
sf-username: ${{ vars.SF_USERNAME_PROD }}
sf-instance-url: ${{ vars.SF_INSTANCE_URL_PROD }}
secrets:
SF_JWT_KEY: ${{ secrets.SF_JWT_KEY_PROD }}
SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID_PROD }}
The properties file:
Templates Calling Reusable Workflows¶
As illustrated above, a starter template can itself call a reusable workflow using uses:. This is a powerful composition pattern: the template gives every new repository a working pipeline on day one, while the actual logic lives in a single, centrally maintained reusable workflow. When the deployment logic needs to change (e.g., to add a new test level flag), you update the reusable workflow in one place, and every repository automatically picks up the change on their next run.
Non-Public Template Support (2025)¶
Prior to late 2025, starter workflow templates required the .github repository to be public. GitHub now supports templates from non-public repositories (GitHub Changelog: YAML anchors and non-public workflow templates). If the .github repository is internal, all internal and private repositories in the enterprise can see and use the templates. If it is private, only repositories within the same organization can use them. This is a significant improvement for enterprise teams that cannot expose their pipeline logic publicly.
Combining the Components: An Organization-Wide Strategy¶
The three components are not mutually exclusive; they are designed to be layered. A well-structured organization uses all three in concert:
Developer creates new Salesforce repo
│
▼
Selects "Salesforce PR Validation" starter template
from Actions tab (Starter Workflow Template)
│
▼
Template calls the org's canonical validate workflow
uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2.3.1
(Reusable Workflow)
│
▼
Reusable workflow uses composite actions for shared steps
uses: ./.github/actions/sf-auth-jwt
(Composite Action)
Repository Versioning Strategy¶
When the .github repository stores both reusable workflows and composite actions, a single git tag covers all of them. This simplifies versioning but means a change to one action bumps the version for everything in the repo.
Callers pin to an immutable release tag (@v2.3.1), never to a branch, and take updates through Renovate PRs (Section 8). A tag that is never moved gives a reviewed, reproducible pipeline: the workflow that ran yesterday is the workflow that runs today, and an upgrade is a visible diff in the consuming repository rather than a silent change upstream.
Callers targeting org-wide standards reference the release tag:
Many organisations also keep a floating major tag (v2) that moves to each non-breaking release, so that a caller using @v2 receives updates without a PR. That is a reasonable choice for highly trusted actions such as actions/checkout, where the review saved outweighs the reproducibility lost, and the samples in these standards show third-party actions at their floating major tag for that reason. For the organisation's own workflows and actions the immutable tag is the standard. Renovate can be configured to keep the two styles side by side in one workflow file: it moves an immutable tag to the next release and leaves a floating major tag alone until the next major version.
Organization Policy Enforcement¶
GitHub organization administrators can enforce that all repositories only use actions and reusable workflows from within the organization. This is configured at Organization Settings → Actions → General → Allow actions and reusable workflows. Selecting "Allow actions created by GitHub" plus your own organization prevents any external action from running without explicit approval, which is an important control for regulated Salesforce environments handling financial or healthcare data.
Salesforce SFDX: Putting It Into Practice¶
The following example represents a realistic multi-repository Salesforce organization using all three components.
Shared Repository Structure (<my-org>/.github)¶
The full tree, with every caller workflow, reusable workflow and composite action in place, is the recommended directory structure in Appendix D; the subset earlier on this page shows the shape. The inner .github/workflows/ directory holds both the caller workflows (A.1) and the reusable workflow implementations they invoke (A.2). The inner .github/actions/ directory holds the composite actions that the reusable workflows call as steps (A.3).
The delta package in this architecture is produced by the sf-generate-delta.yml reusable workflow (A.2.8), not a composite action. That workflow runs sf sgd source delta as a job, uploads the result as an artifact, and exposes an artifact-uploaded output. Downstream gates (sf-code-analyzer.yml, sf-apex-test-selection.yml, sf-jest-tests.yml, sf-validate-deployment.yml) each download the artifact by name rather than regenerating the delta independently. Keeping delta generation in a job rather than a composite action lets multiple downstream jobs consume the same delta in parallel without re-running the underlying git diff.
Stitching the Pieces Together¶
The next four code blocks walk through how a caller workflow invokes a reusable workflow, how the reusable workflow calls composite actions as steps, and how a project repository consumes the whole chain.
The caller workflow is the trigger-bound entry point. In a project repository, it responds to a GitHub event and composes reusable workflows from the organization .github repository. From the reference implementation (pr-prod.yml, A.1.3), a simplified excerpt shows the pattern:
# salesforce-project-repo/.github/workflows/pr-prod.yml
name: CI on PR (prod)
on:
pull_request:
branches: [main]
types: [opened, synchronize, ready_for_review]
env:
APEX_COVERAGE_THRESHOLD: 85 # Apex classes below this coverage are listed
jobs:
variables:
name: Variables
runs-on: ubuntu-latest
outputs:
apex-coverage-threshold: ${{ steps.set.outputs.apex-coverage-threshold }}
steps:
- id: set
run: echo "apex-coverage-threshold=$APEX_COVERAGE_THRESHOLD" >> "$GITHUB_OUTPUT"
generate-delta:
name: Delta Package
uses: <my-org>/.github/.github/workflows/sf-generate-delta.yml@v2.3.1
apex-tests:
name: Apex Test Selection
needs: generate-delta
if: needs.generate-delta.outputs.artifact-uploaded == 'true'
uses: <my-org>/.github/.github/workflows/sf-apex-test-selection.yml@v2.3.1
with:
organization-run-relevant-tests: ${{ vars.APEX_RUN_RELEVANT_TESTS_ORGANIZATION }}
run-relevant-tests: ${{ vars.APEX_RUN_RELEVANT_TESTS }}
validate-deploy:
name: Validation
needs: [variables, generate-delta, apex-tests]
if: needs.generate-delta.outputs.artifact-uploaded == 'true' && !failure() && !cancelled()
uses: <my-org>/.github/.github/workflows/sf-validate-deployment.yml@v2.3.1
with:
test-level: ${{ needs.apex-tests.outputs.test-level }}
tests: ${{ needs.apex-tests.outputs.tests }}
coverage-threshold: ${{ needs.variables.outputs.apex-coverage-threshold }}
sf-username: ${{ vars.SF_USERNAME_PROD }}
sf-instance-url: ${{ vars.SF_INSTANCE_URL_PROD }}
secrets:
SF_JWT_KEY: ${{ secrets.SF_JWT_KEY_PROD }}
SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID_PROD }}
Three layers are visible: the caller composes jobs, each job delegates to a reusable workflow via uses:, and the reusable workflows consume secrets passed explicitly from the caller. One job also hands its outputs to the next: the test selection chooses a test level and a list of tests, and the validation takes them as inputs. The needs: and if: conditions express dependency ordering and short-circuit on an empty delta so gates skip cleanly when no Salesforce metadata changed.
The reusable workflow implements a single quality gate. Its job calls composite actions for repeated setup steps. From sf-validate-deployment.yml (A.2.12), the job steps collapse to:
# <my-org>/.github/.github/workflows/sf-validate-deployment.yml
jobs:
validate-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Set up Salesforce environment
uses: ./.github/actions/setup-sf # A.3.1
with:
install-code-analyzer: "false"
install-sfdx-git-delta: "false"
- name: Authenticate to validation org
uses: ./.github/actions/sf-auth-jwt # A.3.2
with:
jwt-key: ${{ secrets.SF_JWT_KEY }}
client-id: ${{ secrets.SF_CLIENT_ID }}
username: ${{ inputs.sf-username }}
instance-url: ${{ inputs.sf-instance-url }}
alias: validation-org
- name: Download delta package
uses: actions/download-artifact@v8
with:
name: delta-package
path: delta/
# ... sf project deploy start --dry-run with the selected tests, post-pr-comment ...
Every step that wraps more than one command is a composite action. setup-sf replaces what would otherwise be five steps (Node, npm ci, CLI install, Code Analyzer plugin, sfdx-git-delta plugin) with one invocation that accepts toggles for the optional plugins. sf-auth-jwt replaces the three-step pattern of writing the private key to a temp file, authenticating, and cleaning up. The net effect: every reusable workflow that connects to an org has the same shape, and authentication behaviour changes in one place when it changes at all.
The composite action is the leaf of this call graph. It contains steps, not jobs, and runs in the caller job's runner context. The sf-auth-jwt action shown in full in Appendix A.3.2 is a representative example: three run steps handle file write, CLI login, and cleanup, with an if: always() on the cleanup step to ensure the secret file never survives a failed login.
Why This Split Matters¶
Dividing the system into caller workflows, reusable workflows, and composite actions is not organisational hygiene for its own sake: each layer enables a specific kind of change without disturbing the others.
- Adding a new quality gate is a reusable-workflow change. Create
sf-<gate>.ymlin the organization.githubrepository, add a job topr-prod.ymlandpr-pipeline.ymlthat calls it, and every consuming project inherits the gate on its next pipeline run. - Onboarding a new environment (e.g. a pre-production hotfix branch) is a caller-workflow change. Add
pr-hotfix.ymlthat reuses the same gate chain with the JWT credentials of the hotfix org: the secretsSF_JWT_KEY_HOTFIXandSF_CLIENT_ID_HOTFIXand the variablesSF_USERNAME_HOTFIXandSF_INSTANCE_URL_HOTFIX. The reusable workflows do not change. - Switching authentication mechanisms (e.g. from the JWT bearer flow to GitHub OIDC, which Section 7 calls an emerging pattern) is a composite-action change. Replace
sf-auth-jwtin the reusable workflows that use it, update the consuming caller secrets, and the gate logic is untouched.
Each layer has a distinct owner, a distinct change cadence, and a distinct blast radius. The combination is what makes the architecture sustainable at scale.
Key Differences at a Glance¶
| Dimension | Reusable Workflow | Composite Action | Starter Template |
|---|---|---|---|
| Defined in | .github/workflows/*.yml with on: workflow_call |
action.yml with runs.using: composite |
workflow-templates/*.yml |
| Called with | uses: in a job |
uses: in a step |
Selected from the Actions UI |
| Can contain | Multiple jobs, multiple runners | Steps only, single runner context | Any valid workflow syntax |
| Secrets | Declared in on.workflow_call.secrets or secrets: inherit |
Passed as inputs (string) | Resolved at call time in calling workflow |
| Outputs | Declared in on.workflow_call.outputs |
Declared in outputs: |
N/A |
| Max nesting | 10 levels (as of late 2025) | 10 levels | 1 level (the template itself) |
| Can call reusable workflows? | Yes | No | Yes |
| Versioning | @{ref} in uses: |
@{ref} in uses: |
Copied into the target repo at creation |
| Runner defined by | The called workflow | The calling job | The template itself |
References¶
- Reuse workflows — GitHub Docs
- Creating a composite action — GitHub Docs
- Creating workflow templates for your organization — GitHub Docs
- Sharing actions and workflows with your organization — GitHub Docs
- Disabling or limiting GitHub Actions for your organization — GitHub Docs
- GitHub Changelog: New releases for GitHub Actions, November 2025
- GitHub Changelog: YAML anchors and non-public workflow templates, September 2025
- sfdx-git-delta — GitHub