Skip to content

dispatch-helm-chart.yml

Trigger a chart update in a separate chart repository, via workflow_dispatch.

The app repository writes nothing: it sends the version to the chart repository, which runs its own entry-point workflow (normally update-helm-chart.yml in called mode) and opens the pull request on its side.

If the chart lives in the same repository as the app, this is not the workflow you want — call update-helm-chart.yml directly.

Inputs

InputTypeDescriptionRequiredDefault
CHART_REPOstringTarget chart repository (owner/repo)Yes-
CHART_NAMEstringName of the chart to update (in CHART_DIR)Yes-
WORKFLOW_NAMEstringWorkflow file name to trigger in the chart repositoryNoupdate-app-version.yml
CHART_DIRstringDirectory containing the Helm charts (in CHART_REPO)Nocharts
APP_VERSIONstringApplication version to set in Chart.yaml (appVersion). Leave empty to keep the current appVersion (chart-only release)No-
UPGRADE_TYPEstringWhich SemVer part to increment: auto (default), major, minor, patch or prerelease - forwarded verbatim to the chart repository, where update-helm-chart.yml derives the level from the appVersion delta (falling back to patch with a warning when there is no delta to read)Noauto
PRERELEASE_IDENTIFIERstringIdentifier used when the bump enters the prerelease flow - UPGRADE_TYPE: prerelease, or auto with a prerelease APP_VERSION (e.g. rc)Norc
AUTOMERGE_PRERELEASEboolAsk the chart repository to merge its update PR when the bump is a prereleaseNofalse
AUTOMERGE_RELEASEboolAsk the chart repository to merge its update PR when the bump is not a prereleaseNofalse
AUTOMERGE_METHODstringHow the chart repository should merge: auto (queue until required checks pass, needs Allow auto-merge there) or admin (merge now, bypassing branch protection)Noauto
BASE_BRANCHstringBranch of CHART_REPO the workflow is dispatched on, and the base its pull request is opened againstNomain
RUNS_ONstringRunner labels as JSON arrayNo["ubuntu-24.04"]

Secrets

SecretDescriptionRequiredDefault
APP_CLIENT_IDGitHub App Client ID (not the numeric App ID). With APP_PRIVATE_KEY, authenticates as a GitHub App — takes precedence over GH_PAT. See AuthenticationNo*-
APP_PRIVATE_KEYGitHub App private key (PEM). Required alongside APP_CLIENT_IDNo*-
GH_PATGitHub Personal Access Token. Legacy alternative, still supported (see Token setup)No*-

* None is formally required, but one of the two modes must be supplied: APP_CLIENT_ID + APP_PRIVATE_KEY, or GH_PAT. GITHUB_TOKEN cannot dispatch a workflow in another repository; with no credential the job fails explicitly rather than doing nothing.

Permissions

ScopeAccessDescription
--None

This is the entire reason the workflow is separate. Everything it does authenticates against CHART_REPO with the App token (or GH_PAT); nothing touches the calling repository. The calling job should therefore declare permissions: {}:

yaml
  dispatch-chart:
    uses: this-is-tobi/github-workflows/.github/workflows/dispatch-helm-chart.yml@v0
    permissions: {}
    with:
      CHART_REPO: this-is-tobi/helm-charts
      CHART_NAME: my-service
    secrets:
      APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
      APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}

Why dispatching lives in its own workflow. GitHub validates the permissions requested by every job of a called reusable workflow at parse time, whatever their if:. A caller always grants the union, so any job sharing a workflow with this one would push its own scopes onto every dispatch call — for work that never runs. One job per privilege level is what keeps permissions: {} reachable here. ci/tests/permission-union.test.sh enforces the rule across all the reusable workflows.

Token setup

The credential is used for gh workflow run --repo <CHART_REPO>, and is minted scoped to that repository alone.

Set APP_CLIENT_ID and APP_PRIVATE_KEY as repository secrets in the app repository (the one calling this workflow).

RequirementValue
App installed onthe chart repository (CHART_REPO), not the app one
App permissionsActions: Read & Write, Metadata: Read
Token scoped toCHART_REPO only — never the current repository

CHART_REPO must be given as owner/repository; a bare repository name is rejected before any token is minted, since it would otherwise resolve the owner to the repository name.

APP_CLIENT_ID and APP_PRIVATE_KEY must be supplied together. Setting only one fails the job rather than falling back to GH_PAT or GITHUB_TOKEN, which would silently authenticate as something other than the App.

Fine-grained PAT

Create a fine-grained personal access token scoped to the target chart repository (CHART_REPO) with:

PermissionAccessReason
ActionsRead & WriteTrigger workflow dispatch in the chart repo

Classic PAT

Create a classic token with the repo scope (grants access to all repos the user can access).

Where to store it

Add the token as a repository secret named GH_PAT in the source (app) repository — the one that calls this workflow:
Settings > Secrets and variables > Actions > New repository secret

If the chart repository also uses automerge, it needs its own credential for gh pr merge — see update-helm-chart.yml. The same PAT can serve both if it has permissions on both repositories.

Notes

  • The dispatch is asynchronous: this job succeeds as soon as the workflow_dispatch is accepted, without waiting for the update to complete in CHART_REPO.
  • BASE_BRANCH is always passed explicitly (gh workflow run --ref). Without it, gh resolves CHART_REPO's default branch itself through a GraphQL defaultBranchRef query, which exceeds the token's actions: write scope and fails (unable to determine default branch for <repo>: GraphQL: Resource not accessible by integration (repository.defaultBranchRef)). If CHART_REPO's default branch isn't main, set BASE_BRANCH — this applies with either credential, App or GH_PAT.
  • AUTOMERGE_METHOD compatibility: a chart repository whose entry-point workflow does not declare that input makes the API reject the whole dispatch (422 Unexpected inputs provided) rather than ignore the extra value. The dispatch is therefore retried once without it and emits a ::warning:: saying the chart repository's own default applies. Add the AUTOMERGE_METHOD input to that workflow to control the merge method from the app side — the template includes it.
  • Validation happens before the token is minted: CHART_REPO is checked for shape (owner/repository, single line) and BASE_BRANCH for embedded whitespace up front, so a malformed value fails cheaply instead of surfacing as an opaque API error.

Dispatch contract

CHART_REPO's entry-point workflow must be workflow_dispatch-triggerable and declare these inputs — GitHub rejects a dispatch carrying an undeclared input instead of ignoring it:

InputSent by this workflow
RUN_MODEalways called
APP_VERSIONinputs.APP_VERSION
CHART_NAMEinputs.CHART_NAME
CHART_DIRinputs.CHART_DIR (trailing slashes stripped)
UPGRADE_TYPEinputs.UPGRADE_TYPE
PRERELEASE_IDENTIFIERinputs.PRERELEASE_IDENTIFIER
AUTOMERGE_PRERELEASEinputs.AUTOMERGE_PRERELEASE
AUTOMERGE_RELEASEinputs.AUTOMERGE_RELEASE
AUTOMERGE_METHODinputs.AUTOMERGE_METHOD (see compatibility above)

A ready-to-use entry-point workflow is in the CI/CD examples.

Examples

These examples use GitHub App credentials, the recommended mode. To use a personal access token instead, replace the two APP_* lines with GH_PAT: ${{ secrets.GH_PAT }} — nothing else changes. The App must be installed on the repository named by CHART_REPO, since the token is minted scoped to it. See Authentication for end-to-end setup of either.

Dispatch after an app release

yaml
jobs:
  release:
    uses: this-is-tobi/github-workflows/.github/workflows/release-app.yml@v0
    permissions:
      contents: write
      issues: write
      pull-requests: write

  trigger-chart-update:
    uses: this-is-tobi/github-workflows/.github/workflows/dispatch-helm-chart.yml@v0
    needs:
    - release
    if: ${{ needs.release.outputs.release-created == 'true' }}
    # Nothing is written on this repository: everything goes through the App
    # token to CHART_REPO.
    permissions: {}
    with:
      WORKFLOW_NAME: update-app-version.yml
      CHART_REPO: this-is-tobi/helm-charts
      CHART_NAME: my-service
      APP_VERSION: ${{ needs.release.outputs.version }}
      UPGRADE_TYPE: minor
      AUTOMERGE_RELEASE: true
    secrets:
      APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
      APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}

Prerelease bump with automerge

UPGRADE_TYPE: prerelease bumps the chart prerelease version (1.2.31.2.4-rc1.2.4-rc.11.2.4-rc.2: from a stable version the patch is bumped first, then the counter increments). APP_VERSION is written as-is into appVersion; only the chart version field follows the prerelease bump logic. AUTOMERGE_PRERELEASE: true asks the chart repository to merge the resulting PR.

yaml
jobs:
  bump-chart-prerelease:
    uses: this-is-tobi/github-workflows/.github/workflows/dispatch-helm-chart.yml@v0
    permissions: {}
    with:
      WORKFLOW_NAME: update-app-version.yml
      CHART_REPO: this-is-tobi/helm-charts
      CHART_NAME: my-service
      APP_VERSION: 1.4.0-rc.1
      UPGRADE_TYPE: prerelease
      PRERELEASE_IDENTIFIER: rc
      AUTOMERGE_PRERELEASE: true
    secrets:
      APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
      APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}

Chart repository whose default branch isn't main

yaml
jobs:
  trigger-chart-update:
    uses: this-is-tobi/github-workflows/.github/workflows/dispatch-helm-chart.yml@v0
    permissions: {}
    with:
      CHART_REPO: this-is-tobi/helm-charts
      CHART_NAME: my-service
      APP_VERSION: 1.4.0
      # Dispatched on this branch of CHART_REPO, and the base of the PR opened there.
      BASE_BRANCH: develop
    secrets:
      APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
      APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}