# Latchkey steps

> The steps Latchkey publishes for `uses: latchkey/<name>@<version>`: what each one does, its inputs, and how its version is pinned.

Source: https://latchkey.dev/documentation/steps

## Summary

- A Latchkey step is written `uses: latchkey/<name>@<version>`, with its inputs under `with:`. Latchkey never runs GitHub actions.
- Every version is exact and never changes once published, so a change is a new version, such as `latchkey/npm-publish@1.0.1` after `latchkey/npm-publish@1.0.0`. The plan records the step's digest, so a re-run runs the same commands.
- The cache steps today are `latchkey/cache-restore@1.0.0`, `latchkey/cache-restore@1.1.0`, `latchkey/cache-save@1.0.0` and `latchkey/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/docker-build-push@1.0.0` and `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.0` and the two `npm-publish` versions 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](https://latchkey.dev/documentation/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://registry.npmjs.org/`). 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](https://latchkey.dev/documentation/cloud-identity), 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](https://latchkey.dev/documentation/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.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
