The problem
A GitHub Action referenced by a branch gets no pin. The README says so under GitHub Actions and pre-commit hooks: "An action referenced by a branch gets no pin, since it resolves to no version. Neither does a rev: that names a branch, or one that is already a bare commit SHA without a # frozen: comment."
That leaves the most exposed reference unprotected. uses: actions/checkout@main runs whatever the branch points at on the day the workflow runs, and whoever controls the repository can move it between two runs. Pinning to a commit SHA exists for exactly that risk, and the reference that carries the most of it is the one Update-time never pins.
A branch reference is not ignored today. _ACTION_RE in src/update_time/updaters/update_github_action.py matches it, and its repository is checked for staleness and archival. Only the pin is missing.
The commit is one request away
_get_commit(owner, repository, ref) in src/update_time/sources/github.py already fetches a commit by ref, and GitHub's commits endpoint accepts a branch name as that ref.
Probed 2026-09-03:
| Reference |
Commit |
actions/checkout@main |
f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a |
pre-commit/pre-commit-hooks@main |
44d7f9b7e32225d2bd590282037e4650064f3950 |
What the increment adds
A branch reference is pinned to the commit the branch points at, and the branch name travels beside it, the way a version does today:
uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # main
The branch name has to survive in the file. Without it the reference is a bare commit SHA, and the next run cannot tell which branch to follow, or that a branch was ever meant.
Decisions to take
What a later run does with it. A version tag moving is drift, and Update-time warns rather than adopting. A branch moving is what a branch does, so a warning at every run would be noise, and adopting silently would defeat the pin. Three candidate rules:
- Hold the commit and say nothing, so the pin behaves like a frozen reference until the user changes it.
- Hold the commit and report the newer one at
INFO, so the user sees what adopting would take.
- Adopt the newer commit, and require the reference to opt in with a marker first.
The third fits the existing language, where allow[hash-drift] already means adopting what a reference now points at. Whether a moving branch is hash drift, or a scope of its own, is the question to settle before any code moves.
The cooldown. A commit has a date, so the cooldown can hold a fresh commit back the way it holds a fresh release back. Whether it should, for a reference whose author chose to follow a branch, is a policy question rather than a technical one.
Pre-commit hooks. A rev: naming a branch has the same gap. Whether pre-commit's # frozen: convention can carry a branch name, and what pre-commit autoupdate then does with the line, has to be probed before this reaches .pre-commit-config.yaml.
Out of scope
A local action, which carries no @ and names no repository.
A rev: that is already a bare commit SHA with no comment. Nothing in the file says which branch or tag it came from, so there is nothing to follow.
The problem
A GitHub Action referenced by a branch gets no pin. The README says so under GitHub Actions and pre-commit hooks: "An action referenced by a branch gets no pin, since it resolves to no version. Neither does a
rev:that names a branch, or one that is already a bare commit SHA without a# frozen:comment."That leaves the most exposed reference unprotected.
uses: actions/checkout@mainruns whatever the branch points at on the day the workflow runs, and whoever controls the repository can move it between two runs. Pinning to a commit SHA exists for exactly that risk, and the reference that carries the most of it is the one Update-time never pins.A branch reference is not ignored today.
_ACTION_REinsrc/update_time/updaters/update_github_action.pymatches it, and its repository is checked for staleness and archival. Only the pin is missing.The commit is one request away
_get_commit(owner, repository, ref)insrc/update_time/sources/github.pyalready fetches a commit by ref, and GitHub's commits endpoint accepts a branch name as that ref.Probed 2026-09-03:
actions/checkout@mainf548e57e544e1ff5a4c46bf1e1b8685f8e4a348apre-commit/pre-commit-hooks@main44d7f9b7e32225d2bd590282037e4650064f3950What the increment adds
A branch reference is pinned to the commit the branch points at, and the branch name travels beside it, the way a version does today:
The branch name has to survive in the file. Without it the reference is a bare commit SHA, and the next run cannot tell which branch to follow, or that a branch was ever meant.
Decisions to take
What a later run does with it. A version tag moving is drift, and Update-time warns rather than adopting. A branch moving is what a branch does, so a warning at every run would be noise, and adopting silently would defeat the pin. Three candidate rules:
INFO, so the user sees what adopting would take.The third fits the existing language, where
allow[hash-drift]already means adopting what a reference now points at. Whether a moving branch is hash drift, or a scope of its own, is the question to settle before any code moves.The cooldown. A commit has a date, so the cooldown can hold a fresh commit back the way it holds a fresh release back. Whether it should, for a reference whose author chose to follow a branch, is a policy question rather than a technical one.
Pre-commit hooks. A
rev:naming a branch has the same gap. Whether pre-commit's# frozen:convention can carry a branch name, and whatpre-commit autoupdatethen does with the line, has to be probed before this reaches.pre-commit-config.yaml.Out of scope
A local action, which carries no
@and names no repository.A
rev:that is already a bare commit SHA with no comment. Nothing in the file says which branch or tag it came from, so there is nothing to follow.