Summary
semantic‑release can create a Git tag without committing a bumped package.json. This happens when the Git plugin runs before the npm plugin writes the new version, or when the repository is read‑only for the action. The fix is to ensure the @semantic-release/npm plugin runs first (or enable npmPublish: false correctly) and to give the workflow write permission to the repo.
Root Cause
@semantic-release/gittries to commitpackage.jsonbefore the npm plugin has updated the version field.- The GitHub Actions job lacked the
contents: writepermission initially, so the commit was silently dropped. npmPublish: falseprevents publishing but does not stop the npm plugin from updating the version; it must be placed before the Git plugin in the plugins array.
Why This Happens in Real Systems
- Plugin order matters: Semantic‑release executes plugins sequentially. If the Git plugin appears before the npm plugin, it commits the old
package.json. - Permission defaults: Newer GitHub Actions runners grant read‑only permissions by default; without explicit
contents: write, any commit attempt fails. - Private packages: When
private: trueis set, some CI configurations skip the npm step, leaving the version unchanged.
Real-World Impact
- Deployments (e.g., on Vercel) receive the same version number repeatedly, causing cache misses and difficulty tracking releases.
- Tags exist without a corresponding source‑code snapshot, breaking traceability.
- Automated release notes or downstream pipelines that rely on
package.jsonversion fail.
Example or Code (if necessary and relevant)
// .releaserc.json
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}
]
]
}
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
permissions:
contents: write # required for git commit
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
How Senior Engineers Fix It
- Reorder plugins so
@semantic-release/npmruns before@semantic-release/git. - Verify the workflow has
contents: writepermission (or use a Personal Access Token with repo scope). - Add a debug step (
npx semantic-release --debug) to confirm the version is calculated before the Git commit. - Optionally, use the
dryRunflag in a separate test run to ensure thepackage.jsonis being modified.
Why Juniors Miss It
- They often assume plugin order is irrelevant and copy‑paste configuration without checking execution flow.
- They overlook the default read‑only permission in GitHub Actions, expecting the checkout step to permit commits.
- They may not realize that
npmPublish: falsedisables publishing but does not disable version bumping, leading to confusion when the version stays unchanged.