0x55aa
← Back to Blog

Semantic Versioning: Stop Shipping Version 1.0.0 Forever šŸ”¢

•7 min read

Semantic Versioning: Stop Shipping Version 1.0.0 Forever šŸ”¢

Confession time: I maintained an open source PHP security library for almost two years and literally every release was either 1.0.0, 1.0.1, or 1.0.1-hotfix2. 😬

Users couldn't tell if they were running something ancient or something bleeding-edge. Dependabot opened PRs with zero context. And one very frustrated contributor left a comment: "Does this project even have versioning?"

That was my wake-up call.

As a full-time developer who contributes to open source, release management is the most ignored skill in the entire ecosystem. Everyone obsesses over writing code, opening PRs, and getting stars — but the moment you ask "how do you version your project?", most people go suspiciously quiet.

Let me fix that! šŸš€

What Even IS Semantic Versioning? šŸ“–

You've definitely seen it: 2.4.1, 0.12.3, 5.0.0-beta.1.

The format is simple: MAJOR.MINOR.PATCH

MAJOR = Breaking changes (stuff WILL break for users)
MINOR = New features (backwards compatible)
PATCH = Bug fixes (nothing breaks)

Real example:

# You added a new helper function → MINOR bump
v1.2.0 → v1.3.0

# You fixed a regex bug → PATCH bump
v1.3.0 → v1.3.1

# You renamed the main class → MAJOR bump
v1.3.1 → v2.0.0

That's literally it. But the CONSEQUENCES of getting it wrong are enormous!

The Day I Broke 47 People's Builds šŸ’„

Early in my open source journey, I was maintaining a Laravel request validation helper. Someone opened a PR to rename the main facade from Validator to RequestGuard — cleaner, more descriptive.

I merged it. Bumped to v1.1.0.

Within 24 hours: 47 GitHub issues. Angry users. Failed CI pipelines. One guy even opened a PR to revert it titled "PLEASE REVERT - production is on fire." šŸ”„

I had shipped a BREAKING CHANGE as a minor bump.

Users expected 1.1.0 to be safe to upgrade to. It wasn't. Their composer.json had "^1.0" which happily pulled in 1.1.0 and exploded on deploy.

That incident taught me more about versioning than any blog post ever could.

Balancing work and open source taught me: Your users TRUST version numbers. A wrong bump destroys that trust instantly — and rebuilding it takes months.

The Semver Rules That Actually Matter šŸŽÆ

Rule #1: Start at 0.x.x If You're Still Figuring It Out

# Not ready for stability promises yet?
v0.1.0  # Initial release
v0.2.0  # More features, might break
v0.9.0  # Feature complete, pre-stable

# Ready to commit to stability?
v1.0.0  # The "I promise not to break you" release

The 0.x.x range is your playground. Breaking changes in minor bumps? Fine! Users know 0.x is unstable. But once you ship 1.0.0, the contract is real.

# DO NOT do this to your users
v0.9.9 → v1.0.0  (fine)
v1.0.0 → v1.0.1  (fix a typo in README)
v1.0.1 → v1.1.0  (rename main class)  ← THIS IS A BREAKING CHANGE

Rule #2: Everything In CHANGELOG.md šŸ“‹

# Changelog

## [2.1.0] - 2026-03-08
### Added
- `validateRequest()` now accepts custom rule sets
- New `--strict` mode for tighter validation

### Fixed
- Fixed crash when input contains null bytes

## [2.0.0] - 2026-02-14
### Breaking Changes
- Renamed `Validator` facade to `RequestGuard`
- Dropped support for PHP 7.x

### Migration Guide
```php
// Before (v1.x)
use Acme\Validator;
Validator::check($request);

// After (v2.x)
use Acme\RequestGuard;
RequestGuard::check($request);

**In the security community**, we follow strict disclosure timelines and changelogs for CVE patches. The same discipline applies to library versioning — users need to know exactly what changed and why. šŸ”’

### Rule #3: Use Git Tags — Actually Use Them

```bash
# Too many open source devs just... push to main
# and consider it "released"

# The RIGHT way:
git tag -a v2.1.0 -m "feat: custom rule sets for validateRequest"
git push origin v2.1.0

# Even better — use GitHub Releases
gh release create v2.1.0 \
  --title "v2.1.0 — Custom Rule Sets" \
  --notes "See CHANGELOG.md for full details"

GitHub Releases let you:

  • Attach binary downloads
  • Auto-generate release notes from PRs
  • Notify watchers automatically
  • Give Dependabot proper context for updates

The Tools That Make This Easy šŸ› ļø

standard-version / release-it

npm install --save-dev release-it

# Now releasing is literally one command:
npx release-it

# It will:
# 1. Bump version in package.json
# 2. Generate CHANGELOG.md entries from commits
# 3. Create a git tag
# 4. Create a GitHub Release

For PHP/Composer projects, I use a simple script that reads my commits since the last tag and drafts the CHANGELOG entry. Five minutes to release properly vs. an hour of manual editing.

Conventional Commits (The Secret Sauce 🤫)

# Write commits like this:
git commit -m "feat: add custom rule set support"
git commit -m "fix: crash with null byte inputs"
git commit -m "feat!: rename Validator to RequestGuard"
#              ^ the ! means BREAKING CHANGE

# And automated tools can determine your next version:
# feat → MINOR bump
# fix → PATCH bump
# feat! or BREAKING CHANGE → MAJOR bump

I converted my team's commit convention to this format 8 months ago. Now our CI pipeline literally calculates the next version and opens a release PR automatically. Zero manual version debates.

When To Do a Major Release (Without Terror) 😰

This is the one that paralyzes everyone. Here's my framework:

Ask yourself:
ā–” Does existing code break without user changes?  → MAJOR
ā–” Did I remove a public API method?               → MAJOR
ā–” Did I change a method signature?               → MAJOR
ā–” Did I drop a language/framework version?        → MAJOR
ā–” Everything else?                               → MINOR or PATCH

Major releases aren't evil — they're HONEST. A library that never does a major release is either stagnant or secretly breaking users on every "minor" update.

My personal rule: Ship major versions confidently, but always provide:

  1. A migration guide
  2. At least 3 months of overlap where both APIs work
  3. Deprecation warnings in the old code
/** @deprecated Use RequestGuard::check() instead. Will be removed in v3.0. */
public function validate(Request $request): bool
{
    // still works, but logs a deprecation warning
    trigger_error('Validator is deprecated. Use RequestGuard.', E_USER_DEPRECATED);
    return RequestGuard::check($request);
}

The GitHub Release Checklist I Use šŸ“

Before every release, I run through this:

Pre-release:
ā–” All tests pass on CI
ā–” CHANGELOG.md updated with changes
ā–” Breaking changes clearly labeled
ā–” Migration guide written (for MAJOR)
ā–” README updated if needed

Release:
ā–” Version bumped in all relevant files
ā–” Git tag created and pushed
ā–” GitHub Release published with notes
ā–” Notify users on Discussions (for breaking changes)

Post-release:
ā–” Check Packagist/npm shows new version
ā–” Verify Dependabot picks it up
ā–” Respond to any quick feedback in issues

Balancing work and open source taught me: The checklist adds 10 minutes. Debugging a bad release costs 10 hours. Do the math! ā±ļø

TL;DR — The Version Number Contract šŸ’”

Your version number is a promise to your users:

What you changed What to bump What users expect
Bug fix, typo PATCH Safe to upgrade immediately
New feature, new option MINOR Safe to upgrade, get new things
Breaking change, removal MAJOR Must read migration guide first

Three things to do this week:

  1. Audit your current version — does it accurately reflect your project's stability?
  2. Write a CHANGELOG.md if you don't have one (use git log to reconstruct the history)
  3. Try conventional commits on your next three commits — you'll never go back

The open source projects people TRUST and RELY ON aren't just the ones with great features — they're the ones that communicate change with honesty and clarity.

Version your code like you'd want others to version theirs. šŸ¤


Got a versioning horror story? I know you do. Drop it in the LinkedIn comments — I'll personally reply to every one! šŸ˜„

Want to see proper versioning in practice? Check out my GitHub — my security libraries use everything in this post.

Now go bump that version and ship it! šŸš€