GitLab CI¶
Use semvertag in GitLab CI via a small inline job that installs uv
and runs uvx semvertag tag. No PyPI install in your repo, no
maintained pipeline YAML beyond the snippet below.
Catalog component pending. A one-line
include: - component: …via the GitLab CI Catalog is the eventual delivery path — the descriptor lives attemplates/semvertag.yml— but the component has not yet been published to gitlab.com's Catalog. Paste the job below into.gitlab-ci.ymluntil then.
Quick Start¶
The minimum useful pipeline: auto-tag on every push to the default branch.
Required setup. Set
SEMVERTAG_TOKENas a project-level masked CI/CD variable holding a Project Access Token (or Personal Access Token) withapi+write_repositoryscope.CI_JOB_TOKENworks on projects where the job-token write scope is opted in (see Token scope below).
stages: [tag]
semvertag:
stage: tag
image: python:3.13-slim
resource_group: semvertag
variables:
SEMVERTAG_STRATEGY: branch-prefix
before_script:
- pip install --quiet --no-cache-dir 'uv>=0.4,<1'
script:
- uvx 'semvertag>=0.1,<1' tag
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
The job runs against the latest commit on the default branch and, if a
bump is warranted by the configured strategy, pushes a new tag to the
project's origin. If no bump is warranted, the job exits 0 without
pushing.
Concurrency default.
resource_group: semvertagmakes GitLab serialize concurrentsemvertagjobs across pipelines on the same project — back-to-back pushes will queue rather than race thecreate_tagAPI. Drop or rename the group if you intentionally want concurrent tag pushes.
Strategy¶
Set SEMVERTAG_STRATEGY to one of:
| Value | Description |
|---|---|
branch-prefix (default) |
Bump from the source-branch prefix of the latest merge commit. |
conventional-commits |
Bump from Conventional Commits headers since the last tag. |
When the Catalog component lands, this will become a typed inputs:
block on the include:. The values and default match
templates/semvertag.yml's
spec.inputs.strategy so the migration is a snippet swap.
Required permissions¶
The job pushes a tag, so the token it uses MUST carry write access to
the repository. semvertag reads the token from these env
vars in order: SEMVERTAG_GITLAB__TOKEN, SEMVERTAG_TOKEN,
CI_JOB_TOKEN, GITLAB_TOKEN. The first set value wins.
Token scope: CI_JOB_TOKEN vs Project Access Tokens¶
Two cases govern which token the job should use:
- GitLab projects where the maintainer has opted in to job-token
write scope (Settings → CI/CD → Token Permissions → Allow access
from the project's token to write to the repository).
CI_JOB_TOKENis auto-exported into every CI job and gets picked up by the alias chain — no further configuration needed. - Projects that have NOT opted in, or projects on older GitLab
versions where
CI_JOB_TOKENwas scoped read-only by default. The consumer creates a Project Access Token (preferred; scoped to the one project) or a Personal Access Token (works but bleeds the user's scope across all their projects). Token scopes required:api+write_repository. Store the token as a masked CI/CD variable namedSEMVERTAG_TOKEN; the alias chain picks it up ahead ofCI_JOB_TOKEN.
Masking caveat. Because the alias chain reads
SEMVERTAG_GITLAB__TOKEN→SEMVERTAG_TOKEN→CI_JOB_TOKEN→GITLAB_TOKENin order and the first set value wins, a staleSEMVERTAG_TOKENleft over from a prior PAT-based setup will silently override a freshly-rotatedCI_JOB_TOKEN. If you migrate from PAT → job-token, unsetSEMVERTAG_TOKEN(or rotate its value to empty) in the project's CI/CD variables.
Self-hosted GitLab: set SEMVERTAG_GITLAB__ENDPOINT (note the
double underscore — pydantic-settings uses __ as the nested-key
delimiter, so SEMVERTAG_GITLAB_ENDPOINT with a single underscore is
silently ignored) as a project CI/CD variable pointing to the
instance's API root, e.g. https://gitlab.example.com. The default
is https://gitlab.com and is not auto-derived from CI_SERVER_FQDN.
Endpoint shape. Use scheme + host only. Do NOT append
/api/v4(the client adds it); a value likehttps://gitlab.example.com/api/v4produces…/api/v4/api/v4/…URLs and 404s. A missing scheme (gitlab.example.com) fails at request time with httpxConnectError. A trailing slash is tolerated (the client strips it).
For most consumers on gitlab.com-hosted projects with job-token
write scope, the minimal job snippet above is the entire setup.
Branch-prefix vs conventional-commits¶
Pick branch-prefix if your team merges merge requests with branch
names that follow a fix/..., feat/..., chore/... convention.
semvertag reads the most recent merge commit's source-branch prefix
and bumps accordingly — fix/ bumps patch, feat/ bumps minor,
chore/ bumps nothing. This is the default. See
Branch-prefix strategy for the full
prefix-to-bump table and edge-case behavior.
Pick conventional-commits if your team writes
Conventional Commits messages
directly on the default branch (e.g. feat: add X, fix: handle Y,
feat!: drop Z). semvertag scans commits since the last tag and
chooses the highest bump implied by their type prefixes (feat! or
BREAKING CHANGE: → major, feat: → minor, fix: → patch,
everything else → none). See
Conventional Commits strategy
for the full type-to-bump mapping and commit-scanning rules.
Set the strategy per project by swapping the SEMVERTAG_STRATEGY
value in the job:
Troubleshooting¶
-
Token missing scope or insufficient permission: 403— the token does not haveapi+write_repositoryscope, or the project's protected-tag rules disallow the bot from creating tags. Verify theSEMVERTAG_TOKENscopes in GitLab UI (Settings → Access Tokens). -
Project id missing. Set CI_PROJECT_ID or pass --project-id.— the CI runner did not exportCI_PROJECT_ID(the variable is exported by every standard GitLab CI job; a custom executor that strips CI variables would suppress it). SetSEMVERTAG_PROJECT_IDas a project-level CI/CD variable as the override. -
Self-hosted GitLab, but the job connects to
gitlab.com— the default endpoint ishttps://gitlab.comand is not auto-derived fromCI_SERVER_FQDN. SetSEMVERTAG_GITLAB__ENDPOINTas a project-level CI/CD variable pointing to the instance's API root.