Action pins

Generated workflows pin third-party GitHub Actions to full commit SHAs (not floating @v4 tags). That keeps CI reproducible and resistant to tag moves. Version labels appear as trailing comments so humans and Dependabot can still read which release a SHA corresponds to:

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

zipx ships current defaults in the plugin jar. Consumers who want to track upstream action releases ahead of a zipx upgrade use a small pin file plus optional Dependabot automation — the same path this repository dogfoods.

Resolve order

When generating a workflow, zipx picks pins in this order:

yesnoyesno
  1. zipxActions — only when set away from ActionPins.Defaults (one-off / escape hatch in build.sbt)

  2. Pin file — if zipxActionsPath points at an existing file (default .github/zipx/action-pins.yml)

  3. ActionPins.Defaults — embedded classpath resource from the zipx release you depend on

Empty zipxActionsPath := "" disables file loading so you always use jar defaults (or an explicit zipxActions).

The pin file

Path: .github/zipx/action-pins.yml (configurable via zipxActionsPath).

This is not a workflow. It lives outside .github/workflows/ and is named so it is not confused with ci.yml. Flat keys match ActionPins fields; values are owner/action@sha with an optional # vX.Y.Z comment:

# zipx GitHub Action SHA pins (not a workflow).
checkout: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
setupJava: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
setupSbt: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e # v1.5.5
cache: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uploadArtifact: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
downloadArtifact: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

After editing the pin file (or syncing from Dependabot):

sbt zipxWorkflowGenerate
git add .github/zipx/action-pins.yml .github/workflows/ci.yml

If zipxDependabotSync := true, also commit .github/workflows/zipx-action-pins-sync.yml when it changes.

{
      val text =
        """checkout: actions/checkout@abc123 # v9.0.0
setupSbt: sbt/setup-sbt@def456 # v1.9.9
""".stripMargin
      ActionPinFile.render(ActionPinFile.parse(text))
    }
# zipx GitHub Action SHA pins (not a workflow).
# Source of truth for generated `uses:` refs. Prefer Dependabot + `sbt zipxActionsPull`
# (or the zipx-action-pins-sync workflow) over editing by hand.
# Docs: https://www.earlyeffect.rocks/zipx/ (Action pins)
checkout: actions/checkout@abc123 # v9.0.0
setupJava: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
setupSbt: sbt/setup-sbt@def456 # v1.9.9
cache: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uploadArtifact: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
downloadArtifact: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
scalaSteward: scala-steward-org/scala-steward-action@ff09222640622d474d0d9f93c04aefedd125b187 # v2.92.0

Friction ladder

GoalWhat to do
Stay on zipx release defaultsNo pin file; upgrade sbt-zipx when we bump pins
Track actions with low frictionCommit .github/zipx/action-pins.yml; enable Dependabot; run sbt zipxActionsPull on bump PRs
Fully hands-offzipxDependabotSync := true (generates the sync workflow) + Dependabot
One-off exotic pinzipxActions := ActionPins.Defaults.copy(...) in build.sbt

Dependabot

Dependabot's github-actions ecosystem only sees uses: in workflow / composite-action YAML — not Scala and not the pin file directly. That is fine: it bumps SHAs (and # vX.Y.Z comments) in the generated workflow. You then pull those bumps back into the pin file so zipxWorkflowCheck stays green.

Minimal Dependabot config:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    groups:
      github-actions:
        patterns:
          - "*"

Manual sync on a Dependabot PR:

sbt zipxActionsPull
# updates the pin file from ci.yml, then regenerates workflows
git add .github/zipx/action-pins.yml .github/workflows/

zipxActionsPull refuses to run if zipxActionsPath is empty (nowhere to write).

Automatic sync workflow

zipxDependabotSync := true

Then zipxWorkflowGenerate / zipxWorkflowCheck also maintain .github/workflows/zipx-action-pins-sync.yml (separate from ci.yml). On Dependabot PRs that workflow:

  1. checks out the PR branch

  2. runs sbt zipxActionsPull

  3. commits and pushes pin-file + workflow updates when anything changed

zipx itself dogfoods this (zipxDependabotSync := true in the root build).

build.sbt escape hatch

Prefer the pin file for ongoing SHA tracking. Use zipxActions only for temporary or exotic overrides:

zipxActions := ActionPins.Defaults.copy(
  setupSbt = "sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e",
)

An explicit zipxActions that differs from ActionPins.Defaults wins over the pin file. Setting zipxActions := ActionPins.Defaults (or leaving the default) lets the pin file take effect when present.

How jar defaults stay honest

In the zipx repository, .github/zipx/action-pins.yml is the editable source of truth. At compile time, resourceGenerators copies it onto the zipx-core classpath as zipx/action-pins.yml. ActionPins.Defaults loads that resource, so a published zipx release ships the same pins this repo dogfoods.

Consumer repos without a pin file get those jar defaults until they add their own file or upgrade zipx.

Settings and tasks

Setting / taskRole
zipxActionsPathpin file path (default .github/zipx/action-pins.yml; "" disables)
zipxActionsexplicit ActionPins override (escape hatch)
zipxDependabotSyncalso generate zipx-action-pins-sync.yml
zipxActionsPullworkflow uses: → pin file → regenerate
zipxWorkflowGenerate / zipxWorkflowCheckwrite / verify ci.yml (and sync workflow when enabled)

Pinned actions today: actions/checkout, actions/setup-java, sbt/setup-sbt, actions/cache, actions/upload-artifact, actions/download-artifact.