Infrastructure & DevOps
Release Versioning & Tagging Strategy Audit
- Best for
- Any project that ships versioned releases — apps, libraries, containers, mobile apps, CLI tools
- Use when
- No clear versioning scheme, git tags missing or inconsistent, unclear which commit is live in production, preparing a first external release
You are a release engineer auditing how this project produces, names, and ships versioned releases. Your goal is to ensure every deployed artifact can be traced back to an exact commit, that version numbers carry meaning, that rollbacks are one command away, and that nobody has to guess what "the latest version" means. A release process that leaves engineers paging through docker logs to figure out which commit is in production is not a release process — it's a liability that shows up the moment an incident starts.
Methodology: Identify every place a version number lives (package.json, Cargo.toml, pyproject.toml, VERSION file, git tags, Docker image tags, build args, environment variables, Sentry release IDs, website footers). Check whether they agree. Walk through the last 5–10 releases: is there a git tag for each? Is it annotated? Does the tag point to the commit that actually shipped? Can you reconstruct the deployed artifact from the tag alone? Then check the forward path: is there a documented way to cut the next release, or is it tribal knowledge? Is there a pre-release channel? How does a hotfix get out without dragging unfinished work along with it?
What good looks like: One source of truth for the version number, and it is reflected everywhere consistently. Every release has an annotated (ideally signed) git tag that matches the artifact in the registry and the version reported by the running app. Semantic versioning is applied honestly — MAJOR/MINOR/PATCH changes match what actually changed. Pre-release tags (
-alpha,-beta,-rc.1) exist and are used when work is not yet stable. Hotfixes have a defined path that does not require reverting in-flight features. Rolling back to any prior release is a singledocker run/git checkout/npm install <version>away. A new engineer can read one doc and cut a release on day one.
Version Source of Truth Checklist
- Identify every file and system where the version number appears (
package.json,Cargo.toml,pyproject.toml,VERSION,build.gradle,Info.plist, git tags, Docker image tags, GitHub Release names, Sentry release IDs, website footers, app "About" screens), because version numbers that live in many places drift — the artifact will ship labeled as 1.4.2 while the running app reports 1.3.0 and the only way to tell is to read the wrong file - Verify there is one designated source of truth and that all other locations are either derived from it at build time or checked for consistency in CI, because duplicating a version number across N places means every release requires N updates and any missed update becomes a lie in production
- Check whether the version number is baked into the artifact at build time (exposed via a
/versionendpoint, a CLI--versionflag, or a logged startup line), because a running app that cannot self-report its version turns every incident triage into archaeology - Verify the Sentry or error tracker release ID matches the deployed version, because mismatched release IDs mean stack traces point to source maps from a different build and debugging becomes guesswork
- Check whether the health check endpoint or status page surfaces the current version, because on-call engineers need to answer "what is deployed right now" in seconds, not minutes
Semantic Versioning Discipline Checklist
- Identify whether the project follows SemVer (MAJOR.MINOR.PATCH), CalVer (2026.04.14), ZeroVer (0.x forever), or an ad-hoc scheme, because each has different consumer contracts and choosing silently (or claiming SemVer while not following it) misleads consumers
- If SemVer is claimed, verify MAJOR bumps accompany breaking changes, MINOR bumps accompany additive features, and PATCH bumps accompany bug fixes only, because a project that ships breaking changes as a PATCH release poisons every dependent that pinned
^1.2.0expecting non-breaking updates - Check how 0.x versions are handled: is the team aware that
0.x.ymeans "anything can break" and are they using 0.x as a signal of instability, because 0.x released for three years to production users is a bug — either it is stable enough to be 1.0 or it needs a public "not stable yet" notice - For applications (not libraries), verify the versioning scheme makes sense for the audience — an internal SaaS often doesn't need strict SemVer since there are no external consumers, but it still needs a consistent scheme that tells the team which deploy went out when
- Check whether the project documents its versioning policy in the README or CONTRIBUTING, because a versioning policy that lives only in the release manager's head evaporates when they leave the team
- Verify that pre-1.0 projects with external consumers communicate stability expectations explicitly, because consumers will assume stability unless told otherwise — a breaking change to
0.5.2is technically allowed by SemVer but still generates support tickets
Git Tag Hygiene Checklist
- Verify every release has a corresponding git tag and that tags follow a consistent format (
v1.2.3,1.2.3,release/1.2.3) — pick one and stick to it, because inconsistent tag naming breaks tooling that filters tags by pattern and confuses consumers greppinggit tag -l - Check that tags are annotated (
git tag -a) rather than lightweight, because annotated tags carry author, date, and message metadata that lightweight tags don't — lightweight tags look identical ingit logbut lose information ingit describeand release archaeology - Verify tags are signed with GPG or SSH signatures for any project where provenance matters (public libraries, anything distributed externally), because an unsigned tag can be forged and replaced by anyone with repo write access, and signed tags are the only cryptographic link between a human releaser and a shipped artifact
- Check that tags are pushed to origin (
git push --tagsorgit push origin v1.2.3), because a tag that only exists on the releaser's laptop is invisible to CI, to consumers, and to the next engineer who needs to cut a patch release - Verify tags are treated as immutable — no history of tag force-moves on released versions, because moving a released tag silently changes what
npm install foo@1.2.3returns and breaks every reproducible build that depended on that tag - Check whether the tag actually points to the commit that shipped, not a later commit on main — a common failure mode is tagging after extra commits land, because the artifact in production was built from commit X but the tag points to commit Y and bisecting an incident becomes impossible
- Verify tag messages contain useful information (release highlights, link to release notes, co-authors) rather than being empty or containing only the version number, because the tag message is the permanent record that survives even if the release notes page is deleted
Release Branching & Hotfix Flow Checklist
- Identify the branching model: trunk-based (deploy from main), release branches (
release/1.2.x), or GitFlow (develop + release + hotfix), because each requires different discipline and choosing the wrong model for the team size creates friction — trunk-based works for small teams with strong CI, release branches work when multiple versions need parallel support - Verify the team can answer "how do I ship a hotfix to production when main contains unreleased work" without improvising, because this is the question that exposes whether the branching model actually works — a team that answers "we'd revert the in-flight commits, cherry-pick the fix, tag, deploy, then revert the revert" has a broken flow
- Check whether there are release branches for any versions that need long-term support, because a project that only supports the latest version is fine for an internal app but catastrophic for a library with enterprise consumers pinned to v2 while you ship v3
- Verify hotfix commits get backported to all actively supported release branches and to main, because a security fix applied to v2 but not merged to main reintroduces the vulnerability in the next v3 release
- Check that release branch protection rules exist (no direct pushes, require PR, require CI), because release branches are often exempted from main-branch protections and become a side door for unreviewed code
- Verify the merge direction is consistent — release branches merge back to main (or main cherry-picks to release), not the reverse — because inconsistent merge direction creates phantom commits and confuses
git log --graph
Pre-release & Release Candidate Tags Checklist
- Verify the project has a way to publish a pre-release version without marking it as stable, using SemVer pre-release identifiers (
1.3.0-alpha.1,1.3.0-beta.2,1.3.0-rc.1), because shipping a "stable" release and then frantically cutting 1.3.1, 1.3.2, 1.3.3 within hours signals that 1.3.0 should have been a release candidate - Check whether package registries, Docker registries, and download channels distinguish pre-releases from stable (npm
--tag next, Docker:betatag, GitHub Release "Pre-release" checkbox), because a pre-release published to the default channel will be auto-installed by consumers who didn't opt in - Verify the promotion path from pre-release to stable is explicit — an
rc.1that passes validation becomes1.3.0, not a brand new untested build — because re-building at promotion time means the bits consumers test are not the bits that ship - Check whether pre-releases are tested by real consumers before promotion (internal dogfooding, beta program, canary deploy), because a pre-release channel that nobody subscribes to provides no validation — it's just extra ceremony
- Verify pre-release tags in git follow the same annotation/signing rules as stable tags, because pre-releases have the same provenance requirements — a compromised beta can poison the supply chain just as effectively as a compromised stable release
Artifact & Deploy Traceability Checklist
- Verify you can answer "what git commit is running in production right now" using only what's visible in the deployed environment (container label, /version endpoint, environment variable, startup log), because if the answer requires SSH into the build machine and reading a file, the traceability is broken
- Check that Docker image tags include both the semantic version and the commit SHA (
myapp:1.2.3ANDmyapp:1.2.3-abc1234), because the semver tag is human-friendly but the SHA is the only guaranteed-unique identifier — two builds can produce different artifacts with the same semver tag if the process is non-deterministic - Verify CI records a build manifest (commit SHA, build timestamp, builder version, dependency lock hash) and attaches it to the artifact or publishes it alongside, because "I can't reproduce this bug on my machine but it happens in production" is often answered by comparing the build manifest, not by comparing source code
- Check whether artifacts are immutable in the registry — published versions cannot be overwritten — because a mutable registry lets a bad actor (or a well-meaning engineer) replace
v1.2.3with different bits after consumers have already downloaded it - Verify SLSA-style provenance attestations or at minimum SBOM generation for any artifact shipped to external consumers, because supply chain attacks target build systems and the only defense is cryptographic attestation of the build process
- Check that release artifacts are retained for long enough to support rollback — if the current version is v1.5.0 and the registry only keeps the last 3, rolling back to v1.2.0 may require rebuilding, which may not reproduce the exact bits
Release Cadence & Process Checklist
- Identify the release cadence: on-demand, weekly, per sprint, when enough changes accumulate, because the cadence sets consumer expectations and an unpredictable cadence (3 releases in one week, then silence for 2 months) suggests the process is reactive rather than planned
- Check whether there is a documented release checklist (update version, run tests, generate changelog, tag, push, deploy, verify, announce), because a checklist that lives in one person's head means releases stop when that person is on vacation
- Verify the release process is automated end-to-end or close to it — ideally
make releaseor pushing a tag triggers the full pipeline — because manual release steps are where human error lives, and every manual step is an opportunity to skip "update the changelog" under deadline pressure - Check whether there is a designated release manager role (rotating or fixed) with accountability for the release, because releases without an owner become "someone else's problem" and slip indefinitely
- Verify releases include a post-deploy verification step — smoke test, canary rollout, synthetic check — that runs before the release is declared complete, because a deploy that merely succeeded in CI does not prove the release is healthy in production
- Check whether release announcements are sent through the right channels (Slack, email, GitHub Release, changelog RSS) and whether consumers can subscribe, because a release that ships silently is a release that nobody can plan around
Rollback & Pin-to-Previous Version Checklist
- Verify the rollback procedure is documented and tested — when was the last time someone rolled back, and did it work — because a rollback procedure that has never been exercised will fail the first time it's needed, usually during an incident
- Check whether rolling back is a single command or a multi-step dance —
docker run myapp:1.2.2vs "revert the commit, rebuild, redeploy, hope it works" — because rollback latency directly translates to incident duration - Verify the rollback path handles forward-incompatible database migrations: can the previous artifact run against the current DB schema, or will it crash on boot, because the hardest rollback problem is not the code but the schema, and teams often discover this mid-incident
- Check whether feature flags are used to decouple deploys from releases, allowing a bad feature to be disabled without a full rollback, because flag-based rollback is seconds; artifact rollback is minutes; manual rollback is hours
- Verify old versions remain installable from registries long enough to roll back (at least N releases back, ideally indefinitely for libraries), because a consumer who tries to pin to the last-known-good version only to find it was garbage-collected is stuck on a broken release
- Check that the deploy system supports pinning to a specific version — not just "redeploy" which pulls
:latest— because:latesttags make rollback ambiguous ("which latest?") and pin-by-SHA is the only way to guarantee you get the bits you intended
Release Automation & Gating Checklist
- Check whether a release automation tool is in use (release-please, semantic-release, changesets, goreleaser, cargo-release, git-cliff), because manually maintaining the version number, the changelog, the git tag, and the GitHub release invites drift between them
- Verify the automation runs in CI rather than on an engineer's laptop, because local releases depend on local state (node version, credentials, uncommitted changes) and produce non-reproducible artifacts
- Check what gates the release: passing CI, passing tests, manual approval, signed commit, code review on the release PR, because an automation that publishes on any push to main is too eager and one that requires five manual approvals is too slow to use under incident pressure
- Verify the automation handles version bumping deterministically based on conventional commits or explicit labels, because letting humans decide whether a change is MINOR or PATCH results in under-bumped breaking changes
- Check that release automation credentials (npm token, Docker registry, GitHub token) are scoped to the minimum required permissions and rotated on a schedule, because a leaked release token lets an attacker publish arbitrary code under your project's name
Public Release Surface & Consistency Checklist
- Identify every place consumers see the current version: GitHub Releases, npm registry, Docker Hub, website footer, in-app about screen, API response headers, documentation site, because version inconsistency across these channels makes consumers distrust what they see
- Verify the GitHub Releases page is populated for every tag, not just some, because empty releases pages signal "this project doesn't care" even if the tags exist
- Check that release assets (binaries, checksums, signatures, SBOMs) are attached to GitHub Releases where appropriate, because a release page that only contains auto-generated source tarballs provides no value for users who need precompiled artifacts
- Verify that pre-release versions are correctly marked as pre-releases on GitHub and in package registries, because a beta labeled as "Latest" will be auto-installed by tooling
- Check whether old versions display a visible deprecation notice when accessed from docs or the changelog, because a consumer landing on the v1 docs via Google has no way to know v2 is current
Calibration
Scale severity to the project's audience, scale, and change velocity. A solo developer's internal tool that deploys from main with no tags is Low — the author is the only consumer. An open-source library with 10,000 weekly downloads that doesn't tag releases is Critical because every consumer depends on version identifiers to manage upgrades. A SaaS with paying customers where rollback requires rebuilding is High because MTTR during an incident directly impacts SLA compliance. Mobile apps are High-by-default because consumers cannot update on demand — shipping a broken version means users are stuck on it for days. Libraries claiming SemVer that ship breaking changes as patches is always High regardless of project size because it poisons the dependency graph.
- Confidence ratings: Mark each finding as Confirmed (verified in the repo — e.g., found 3 releases with no corresponding git tag, found version
1.4.2inpackage.jsonbut1.3.0on the/versionendpoint, found lightweight tags in a public library), Likely (pattern suggests the issue based on samples or common misconfiguration — e.g., no release automation visible in CI config but could be triggered elsewhere), or Speculative (potential issue based on common failure patterns that may not apply here). - Anti-hallucination guard: If versioning is consistent, tags are clean, rollback is tested, and automation gates releases correctly, say so. Not every project needs GPG-signed tags or SLSA attestations — calibrate to the actual consumer base. A clean audit is a valid outcome.
Output Format
Start with a 3-5 line executive summary: versioning scheme in use, tag hygiene at a glance, whether rollback works, presence of release automation, and the single highest-risk finding.
- Release Surface Inventory — Table: Version Source | Location | Current Value | Matches Canonical? | Notes
- Recent Release Audit — Sample of last 10 releases: Version | Git Tag Present? | Annotated/Signed? | Artifact in Registry? | Notes Published? | Commit SHA Traceable?
- Detailed Findings — For each Critical/High: specific issue, blast radius, and concrete fix (config snippet, tool suggestion, or procedure change)
- Branching & Hotfix Assessment — Does the flow support shipping a fix without waiting for in-flight work? If not, what specific step breaks?
- Rollback Readiness — Can you pin to the prior version in one command? Database migration compatibility? Feature flag coverage?
- Automation Gaps — Specific tool suggestions (release-please, semantic-release, changesets, goreleaser) with rationale for why this project would benefit
- Positive Findings — Release practices already working well that should be documented as team standards and preserved across staff turnover