Skip to content
Back to skills

Fabric Release

ASecurity

Release a Fabric mod - semantic versioning, changelogs, reproducible jars, and publishing to Modrinth, CurseForge and GitHub Releases from GitHub Actions with tokens kept in GitHub Secrets. Use when preparing a release, bumping a version, writing release automation, or when asked to "publish", "upload to Modrinth" or "make a release".

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentsgojavatestinggitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add vvs1off/fabric-skills --skill fabric-release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fabric Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fabric Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vvs1off-fabric-release/badge)](https://www.skillsdirectory.com/skills/vvs1off-fabric-release)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fabric-release
description: Release a Fabric mod - semantic versioning, changelogs, reproducible jars, and publishing to Modrinth, CurseForge and GitHub Releases from GitHub Actions with tokens kept in GitHub Secrets. Use when preparing a release, bumping a version, writing release automation, or when asked to "publish", "upload to Modrinth" or "make a release".
---

# Fabric release

A release is a jar that someone else will run. It must be built by CI from a tagged commit,
with a clear version and changelog, and published without any credential ever touching the
repository.

## Versioning

Use Semantic Versioning for the **mod** and put the Minecraft version in build metadata:

```
<major>.<minor>.<patch>+<minecraft_version>     e.g. 1.4.2+26.3
```

- **major**: breaking change for users or dependent mods (config format reset, removed
  feature, changed public API, world-data incompatibility).
- **minor**: new feature, new Minecraft version port, backwards-compatible.
- **patch**: bug fix only.
- Pre-releases: `1.5.0-beta.1+26.3`. Mark them as beta/alpha on Modrinth as well.
- `fabric.mod.json` gets the version through `processResources` (`"version": "${version}"`);
  set it in one place, `gradle.properties`:

```properties
mod_version=1.4.2
minecraft_version=<...>
```

```groovy
version = "${project.mod_version}+${project.minecraft_version}"
```

## Changelog

Keep `CHANGELOG.md` in "Keep a Changelog" format (https://keepachangelog.com): an
`## [Unreleased]` section that every PR updates, renamed to `## [1.4.2] - YYYY-MM-DD` at
release time, with `Added / Changed / Fixed / Removed` groups written for players, not for
developers. CI can extract the section for the release body.

## Reproducible builds

Same commit -> byte-identical jar. Add to `build.gradle`:

```groovy
tasks.withType(AbstractArchiveTask).configureEach {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}
```

Also:

- Commit the Gradle wrapper (`gradlew`, `gradle/wrapper/*`) and verify it in CI
  (`gradle/actions/wrapper-validation` or the `setup-gradle` action's built-in validation).
- Pin every dependency to an exact version in `gradle.properties` - no `+`, `latest.release`
  or `-SNAPSHOT` in a release build (the template's `loom_version` uses `-SNAPSHOT`; that is
  acceptable for the build plugin, but pin it if you need bit-for-bit reproducibility).
- Build releases only in CI from a tag, never from a developer machine.
- The artifact to publish is `build/libs/<name>-<version>.jar` from the `jar` task on 26.1+, or
  from `remapJar` on older versions. Never publish `-sources` or `-dev` jars as the main file.

## Secrets - the non-negotiable part

- Tokens (Modrinth PAT, CurseForge API token) live **only** in GitHub -> Settings -> Secrets
  and variables -> Actions, referenced as `${{ secrets.MODRINTH_TOKEN }}`,
  `${{ secrets.CURSEFORGE_TOKEN }}`. GitHub's own token is `${{ secrets.GITHUB_TOKEN }}`.
- Never put a token in `gradle.properties`, `build.gradle`, a workflow file, `.env`, a commit
  message, or a log line. If one was ever committed, **revoke it immediately** - deleting the
  commit does not un-leak it.
- Give tokens the minimum scope (Modrinth: create/write versions for the project only) and
  give the workflow minimal `permissions:`.
- Workflows triggered by `pull_request` from forks do not get secrets; never switch to
  `pull_request_target` to "fix" that for a publish job.
- Pin third-party actions to a full commit SHA (with the version in a comment) so a
  compromised tag cannot steal your token.

## Publishing workflow (GitHub Actions)

Triggered by pushing a tag like `v1.4.2`. Look up the current major versions / commit SHAs of
each action before using this; the ones below are placeholders to fill in.

```yaml
# .github/workflows/release.yml
name: Release
on:
  push:
    tags: ["v*"]

permissions:
  contents: write        # create the GitHub Release

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<sha>            # vX
      - uses: actions/setup-java@<sha>          # vX
        with:
          distribution: temurin
          java-version: "25"                    # what your MC version requires
      - uses: gradle/actions/setup-gradle@<sha> # vX (also validates the wrapper)

      - name: Check tag matches mod_version
        run: |
          v=$(grep '^mod_version=' gradle.properties | cut -d= -f2)
          test "v$v" = "${GITHUB_REF_NAME}" || { echo "Tag ${GITHUB_REF_NAME} != mod_version $v"; exit 1; }

      - run: ./gradlew build

      - name: Publish to Modrinth, CurseForge and GitHub
        uses: Kir-Antipov/mc-publish@<sha>      # vX - check its README for current inputs
        with:
          modrinth-id: <MODRINTH_PROJECT_ID>
          modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
          curseforge-id: <CURSEFORGE_PROJECT_ID>
          curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
          github-token: ${{ secrets.GITHUB_TOKEN }}
          files: |
            build/libs/!(*-@(dev|sources|javadoc)).jar
          loaders: fabric
          version-type: release
          changelog-file: CHANGELOG.md
          dependencies: |
            fabric-api(required)
```

Alternatives:

- **Minotaur** (`com.modrinth.minotaur`, Modrinth's official Gradle plugin) - publish from a
  Gradle task; read the token from `System.getenv("MODRINTH_TOKEN")`, never from a property
  file. Docs: https://github.com/modrinth/minotaur
- **GitHub only**: `gh release create "$GITHUB_REF_NAME" build/libs/*.jar --notes-file notes.md`
  with `GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}`.

Project ids (Modrinth slug/id, CurseForge numeric id) are public and may be committed; tokens
may not.

## Release checklist

1. All changes merged; `main` is green in CI.
2. `mod_version` bumped; `CHANGELOG.md` section dated; README "supported versions" updated.
3. `fabric.mod.json` `depends` ranges are correct for the target Minecraft version(s).
4. `./gradlew build` locally, then `runClient` (and `runServer` for non-client-only mods) on the
   built jar in a clean instance with only required dependencies.
5. Commit: `chore(release): 1.4.2`; tag: `git tag -a v1.4.2 -m "v1.4.2"`.
6. Push the tag **only with the maintainer's approval**; CI builds and publishes.
7. Verify the Modrinth / CurseForge / GitHub pages: correct file, game versions, loader, and
   dependencies; download and launch the published jar once.

## Common mistakes

- Putting `modrinth.token=...` in `gradle.properties` (even "local" ones get committed) or
  echoing a secret in a workflow step.
- Publishing from a laptop instead of CI; publishing an untagged commit.
- Uploading the `-dev`, `-sources` or a non-remapped jar.
- Version in the jar (`fabric.mod.json`) not matching the tag or the Modrinth version number.
- Declaring every Minecraft version since 1.20 as compatible on Modrinth without testing.
- Forgetting to mark Fabric API as a required dependency on Modrinth/CurseForge.
- Changelog written as commit hashes instead of user-facing changes.
- Using `pull_request_target` or unpinned third-party actions in a job that has publish tokens.
- Reusing a version number for a different jar (Modrinth and players' launchers cache by
  version).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.