Documentation menu
Dashboard & analytics
Managed runners
Team & notifications
Billing & plans
Workflow files
Latchkey steps
The steps Latchkey publishes for uses: latchkey/<name>@<version>: what each one does, its inputs, and how its version is pinned.
In short
- A Latchkey step is written
uses: latchkey/<name>@<version>, with its inputs underwith:. Latchkey never runs GitHub actions. - Every version is exact and never changes once published, so a change is a new version, such as
latchkey/afternpm-publish@1.0.1 latchkey/. The plan records the step's digest, so a re-run runs the same commands.npm-publish@1.0.0 - The cache steps today are
latchkey/,cache-restore@1.0.0 latchkey/,cache-restore@1.1.0 latchkey/cache-save@1.0.0andlatchkey/cache-save@1.1.0, and a cache miss or a cache error never fails the job. - The artifact, Docker and AWS steps today are
latchkey/,artifact-upload@1.0.0 latchkey/,artifact-download@1.0.0 latchkey/anddocker-build-push@1.0.0 latchkey/aws-login@1.0.0, and a failed artifact upload or download fails the step. - The setup and npm steps today are
latchkey/setup-node@1.0.0,latchkey/setup-node@1.0.1,latchkey/,setup-python@1.0.0 latchkey/setup-go@1.0.0,latchkey/setup-java@1.0.0and the twonpm-publishversions above.
How a Latchkey step runs#
When Latchkey compiles your workflow file, it copies the step's commands into your plan. The step then runs as one step of your job, on your job's runner and with your job's env:. The plan records the step's version and digest. A published version never changes, so a commit always runs the same commands, on a re-run too.
latchkey/setup-node sets NPM_CONFIG_PREFIX, latchkey/setup-python adds its virtual environment's bin to the end of PATH and latchkey/setup-java sets JAVA_HOME, each for every step of the job, including the steps before it. A variable your workflow file sets in an env:, whether the workflow's, the job's or a step's, wins over one a Latchkey step sets, and when two Latchkey steps set the same variable, the later step's value applies. A skipped step would still set its variable, so these three steps take no if:. To use one in only some runs, put it in a job of its own and write the if: on that job; to use more than one version, pick the version from a matrix.
Writing one#
A step takes name:, uses:, with:, env: and if:, and runs either run: or uses:, never both. Under with:, write each input as a plain value, from a matrix value, as a whole ${{ needs.<job>.outputs.<name> }}, or as a map of branches that chooses a plain value by the run's branch, the way a job's environment: can (see Workflow files). A secret never goes under with:: a step reads a secret from its env:, as a run: step does.
Pin an exact version. @1, @latest and a missing version are refused, and the refusal names the published versions.
latchkey/cache-restore#
Restores one Latchkey cache entry into a directory. A miss restores nothing, and the job goes on. Inputs: key (required; 1 to 200 letters, digits, ., _ or -), version (required; 1 to 64 of the same characters; a different version is a different entry) and into (default .; a path inside the workspace, or ~ for the home directory).
Version 1.1.0 also takes hash-files, described under latchkey/cache-save.
latchkey/cache-save#
Saves paths into one Latchkey cache entry. An entry that already exists is kept, and the job goes on. Inputs: key and version (required, as for latchkey/cache-restore), from (default .; the directory the paths are relative to, inside the workspace or the home directory) and path (required; one or more paths inside from, separated by spaces).
Both steps use the same cache as latchkey cache restore and latchkey cache save: it is scoped to the repository and branch, other branches read the default branch's cache, and entries are kept 14 days. A pull request from a fork is served no cache, so its steps restore nothing and save nothing, and the job goes on.
Version 1.1.0 of each also takes hash-files: patterns of files tracked in the repository, separated by spaces. The step adds a hash of their contents as committed to the end of key, so a lockfile change makes a new entry, as GitHub's hashFiles() does in a key. A pattern that matches no tracked file still gives the same key on every run.
latchkey/setup-node#
Switches the runner's Node and npm to a version the runner image carries, for every later step. Input: node-version (required; such as 22, 22.23 or 22.23.3; a major, or a major and minor, takes the newest such version on the image). A version the image does not carry fails the step, and the step names the versions it does carry. Version 1.0.1 also works in a job of a pull request from a fork, where version 1.0.0 fails.
latchkey/setup-python#
Makes a virtual environment at ~/.latchkey/venv from a Python version the runner image carries, and switches python, python3, pip and pip3 to it for every later step, so a plain pip install installs into it. The environment's commands, such as one a package installs, are on PATH after the image's own, so where the image already has a command by that name, the image's runs. Input: python-version (required; a minor version such as 3.12 or 3.13; the image carries one release of each). A version the image does not carry fails the step, and the step names the versions it does carry. The image's own Python is not changed, and inside the environment pip install --user is refused.
latchkey/setup-go#
Switches the runner's Go to a version the runner image carries, for every later step. Input: go-version (required; such as 1.25 or 1.25.8; a minor takes the newest such patch on the image). A version the image does not carry fails the step, and the step names the versions it does carry. A job of a pull request from a fork cannot use sudo, so the step fails there.
latchkey/setup-java#
Switches the runner's Java to a Temurin JDK the runner image carries, for every later step, and sets JAVA_HOME to it for the whole job. Input: java-version (required; a major such as 17 or 21). A version the image does not carry fails the step, and the step names the versions it does carry. A job of a pull request from a fork cannot use sudo, so the step fails there.
latchkey/npm-publish#
Publishes the package in directory (default .) with the token in NODE_AUTH_TOKEN, which the step reads from its env:, set from a secret. Inputs: tag (default latest), access (public or restricted), dry-run (default false) and registry (default https:/). With no token the step fails, unless dry-run is true. A job of a pull request from a fork is handed no secret, so there the step can run only with dry-run: true, which publishes nothing.
latchkey/docker-build-push#
Builds the image in context (default .) from file (default its Dockerfile) and tags it with every reference in tags (required, separated by spaces). With push: true it pushes them, and with the default false it loads the image into the runner's Docker for later steps. With username set, it logs in to registry (default Docker Hub) with the password from DOCKER_PASSWORD in the step's env:. A job of a pull request from a fork cannot use Docker, so the step fails there.
latchkey/aws-login#
Signs the job in to AWS as an IAM role, with the job's Latchkey identity token, for every later step. Inputs: role-to-assume (required; the role's ARN), aws-region (required), role-session-name (default latchkey) and role-duration-seconds (default 3600; 900 to 43200, and no longer than the role's maximum session). The role must trust Latchkey's issuer for your repository, with the audience sts.amazonaws.com. In a trial run, and when the job's pipeline is on trial, the job's identity token carries a sub that starts with advisory:, which a trust written for live runs does not match. Later steps' AWS CLI and SDKs read the role from the job's AWS config and fetch new credentials before the old ones expire, so a long job keeps working. No key enters any step's environment, and the step sets no environment variable: the Region is in the AWS config, not in AWS_REGION. A tool that reads AWS credentials only from environment variables gets none from this step. A job of a pull request from a fork has no identity token, so the step fails there.
latchkey/artifact-upload#
Uploads paths as one artifact of this run, for the jobs that need this job. Inputs: name (required; 1 to 128 letters, digits, ., _ or -, and not . or ..), from (default .; the directory the paths are relative to, inside the workspace or the home directory), path (required; one or more paths inside from, separated by spaces) and include-hidden-files (default false). The artifact stores each path as it sits under from, so to upload what a directory holds, set from to it and path to .. Files and directories whose names start with a dot are left out unless include-hidden-files is true, and an upload that names one of them, or finds no regular file, fails.
latchkey/artifact-download#
Downloads one artifact that a job named in this job's needs: uploaded, and extracts it into a directory. Inputs: name (required) and into (default .; a path inside the workspace or the home directory, made if it is not there). The artifact is checked against what was uploaded before anything is extracted, and one that holds a path outside into is refused, and nothing is extracted.
Both steps use the same artifacts as latchkey artifact upload and latchkey artifact download, with the same names and limits, and an artifact is kept 7 days (see Workflow files). Unlike the cache steps, a failed upload or download fails the step. A job of a pull request from a fork cannot use artifacts, so both steps fail there.
What Latchkey refuses#
Latchkey refuses a file rather than run it with a step it cannot read, and names the line: a uses: outside latchkey/, a step name it does not publish, a version that is not exact or not published, an input that is missing, unknown or outside its rule, run: and uses: on one step, and with: on a run: step. Each refusal says what to write instead.