Skip to content

Using Marketplace Actions Safely


actions/checkout was your first taste of uses — a prebuilt action doing in one line what would otherwise take several run steps to replicate. GitHub Marketplace is full of these, covering everything from language setup to deployment to Slack notifications. This chapter covers picking them safely and puts one to real use: actions/setup-python, so your workflow can finally run the Python code sitting in your repository.

What’s Actually Running When You uses Something

An action referenced with uses is, under the hood, someone’s code — usually a small program that runs on the same runner as your job, with access to the same filesystem, environment variables, and (if you’ve configured any) secrets. actions/checkout is maintained directly by GitHub, but plenty of Marketplace actions are written and maintained by individuals or third-party companies. That’s not a reason to avoid them — the ecosystem depends on it — but it is a reason to be deliberate about which ones you pull into a workflow and how you pin their version.

Version Pinning: Tags vs. SHAs

Every uses: line ends with a version reference, and you have two real choices:

- uses: actions/setup-python@v5
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2

A tag like @v5 is convenient and readable, but it’s mutable — the maintainer can move that tag to point at different code later. That’s usually fine for well-maintained, high-trust actions like anything under the actions/ GitHub org, since they follow semantic versioning conventions and don’t rewrite tags carelessly. A full commit SHA, by contrast, is immutable — it points at one exact, unchangeable snapshot of the action’s code, forever.

ScopeTag (@v5)Commit SHA (@0b93645...)
ReadabilityHighLow
Auto-receives patches/fixesYesNo — pin never moves
Protected against a maintainer’s account being compromised and the tag being silently re-pointedNoYes
Typical useTrusted, well-known actions (actions/*)Anything handling secrets, deploy credentials, or from a less-established publisher

For everything in this section, tag pinning (@v4, @v5) is the fine enough — you’re using GitHub’s own official actions, and readability matters more than paranoia while you’re learning. Just know that SHA pinning exists and why teams reach for it once secrets and production deployments are on the line.

Tip

Before adding any Marketplace action to a real workflow, it’s worth a quick glance at its GitHub repository: is it maintained by a recognizable organization, does it have recent commits, does it have a healthy number of stars and users. None of that is a guarantee, but an action with one contributor, no recent activity, and a handful of stars deserves more scrutiny than actions/setup-python.

Adding setup-python

Your app.py has been sitting untouched since it was created — nothing in your workflow actually runs Python yet. Fix that. Update .github/workflows/hello.yml:

name: Hello World

on:
  push:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Run app.py
        run: python app.py

Two new things here:

  • with: passes inputs into an action — this is how you configure a uses step, the same way arguments configure a function call. setup-python accepts a python-version input telling it which interpreter to install and put on the PATH.
  • Quoting "3.12" matters. Written unquoted as 3.12, YAML may interpret it as the number 3.12 rather than the string "3.12" — which usually still works here, but quoting removes the ambiguity entirely, and it’s the safer habit for any version string.

Push it, then check the Run app.py step’s logs — you should see Hello, world!.

Adding A Real Test

A script that just prints something isn’t much of a CI target. Add a test file so there’s something meaningful for a workflow to check. Create test_app.py in your repository root:

from app import greet


def test_greet():
    assert greet("world") == "Hello, world!"

This uses pytest, a widely-used Python testing tool. In short: pytest looks for files and functions named test_*, runs each one, and reports pass or fail based on whether any assert statement inside fails. from app import greet just imports (makes available) the greet function to this file.

Update the workflow to install pytest and run it:

name: Hello World

on:
  push:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install dependencies
        run: pip install pytest
      - name: Run tests
        run: pytest

Commit and push both files:

git add app.py test_app.py .github/workflows/hello.yml
git commit -m "Run tests via setup-python and pytest"
git push

Check the Run tests step — pytest’s own output will show one test collected and passing. Now try breaking it on purpose: change test_app.py to expect the wrong output ("Goodbye, world!" instead of "Hello, world!"), push, and watch the same step fail with a clear assertion error in the log. Then put it back and push again. That failing-then-passing cycle is the entire point of CI — you just watched it happen for the first time in this course.

Last updated on