In a GitHub Actions step, what does uses: reference, and what forms can that reference take?
answer
- The alternative to run:
- A ref is never optional
- One form points inside your own repository
- There is a container form too
- Inputs live under a sibling key
basics
~10 suses: runs a packaged action instead of a shell command. It points at a public repository plus a ref (actions/checkout@v4), a path inside your own repository (./.github/actions/setup), or a Docker image (docker://alpine:3.19).
solid answer
~40 sA step either runs a command with `run:` or invokes a packaged action with `uses:`. The reference has three shapes: **`owner/repo@ref`** for an action in another repository, where `ref` is a tag, branch or commit SHA and is mandatory (`actions/checkout@v4`); **`owner/repo/path@ref`** when the action lives in a subdirectory of that repository; and **`./path`** for an action in the current repository, which requires that the repository has already been checked out. `docker://image:tag` runs a published container image directly as the action. Parameters go under `with:` and are declared by the action's `action.yml`. Whatever the form, `uses:` means "run someone's packaged code in my job" — which is why the ref you choose is a supply-chain decision, not cosmetic.
code
yaml · 9 linessteps:
- uses: actions/checkout@v4 # owner/repo@ref
- uses: acme/tools/setup-cli@v2 # subdirectory of a repo
- uses: ./.github/actions/configure # local, needs checkout first
with:
environment: staging
- uses: docker://alpine:3.19 # published container image
with:
args: echo hellogo deeper
Be able to write both step kinds and recall the owner/repo@ref form plus the fact that the ref is mandatory.
Explain all the reference forms including local and docker://, how with: maps to declared inputs, and how step outputs are read back via an id.
Frame uses: as executing third-party code inside your job and connect the ref choice to supply-chain exposure and reproducibility.
Own the organisational convention: where shared actions live, whether they are vendored locally or referenced across repositories, and how references are pinned and updated at scale.
## Two kinds of step GitHub Actions steps come in exactly two flavours. `run:` executes a shell command on the runner. `uses:` invokes an **action** — a reusable unit of code packaged with an `action.yml` metadata file that declares its inputs, outputs and how it executes. ```yaml steps: - uses: actions/checkout@v4 - run: ./gradlew build ``` A single step cannot have both keys. ## The reference forms **Public or private repository:** `owner/repo@ref`. The ref is required — there is no implicit default branch — and may be a release tag (`@v4`), a branch (`@main`), or a full commit SHA. GitHub fetches that repository at that ref and executes the action defined by the `action.yml` at its root. **Subdirectory:** `owner/repo/path/to/action@ref`. Many organisations keep several actions in one repository; the path selects which one, and the ref still applies to the whole repository. **Local:** `./.github/actions/setup-toolchain`. The path is relative to the repository root of the *workspace*, so the step only works after `actions/checkout` has run. This is the cheapest way to factor repeated steps out of one repository's workflows without publishing anything. **Docker image:** `docker://alpine:3.19` runs a published image directly, with `with.args` becoming the container arguments. Docker-based steps only work on Linux runners. ## Passing data Inputs go under `with:`, keyed by the names the action's `action.yml` declares; unknown keys are ignored rather than rejected, which is a classic source of "my setting had no effect". Environment variables go under `env:`. Outputs come back via the step's `id`: ```yaml - id: meta uses: acme/build-meta@v2 with: prefix: release - run: echo "tag is ${{ steps.meta.outputs.tag }}" ``` ## Why the ref matters The reference is the entire security boundary. `uses:` downloads and executes code from another repository *inside your job*, with access to the job's environment, the workspace, the network, and whatever secrets you have passed in. A mutable ref like `@main`, or a major tag like `@v4` that its owner re-points on each release, means the code you run today is not necessarily the code you reviewed. Pinning to a full commit SHA freezes it. That is why security-conscious repositories pin third-party actions by SHA and keep the human-readable version in a trailing comment. ## Common mistakes - Omitting the ref (`uses: actions/checkout`) — the workflow fails to parse the step. - Using a local `./` action before `actions/checkout`, so the path does not exist yet. - Assuming `with:` keys are validated: a typo'd input is silently dropped unless the action itself checks. - Believing an action is "just configuration". It is arbitrary code — JavaScript, a container, or a bundle of shell steps.
- What happens if you reference a local action with ./ before running actions/checkout?The step fails because the path does not exist: the workspace is empty until checkout populates it. Local actions are read from the checked-out working tree, not fetched from GitHub, so any job using one must check out the repository first — including jobs that otherwise need no source code.
- If you pass a with: key the action does not declare, what happens?Nothing visible. Undeclared inputs are simply not surfaced to the action, so a misspelled key silently takes no effect and the step runs with the default. The failure mode is a configuration that appears applied but is not, which is why reading the action's action.yml beats guessing input names.
saying these in an interview costs you the question
- Omitting the ref after owner/repo
- Treating an action as inert configuration
- Using a local ./ action without checking out first
- Assuming misspelled with: keys raise an error
- Thinking run: and uses: can share one step