Skillshub minecraft-ci-release

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/Jahrome907/minecraft-codex-skills/minecraft-ci-release" ~/.claude/skills/comeonoliver-skillshub-minecraft-ci-release-5f8d8f && rm -rf "$T"
manifest: skills/Jahrome907/minecraft-codex-skills/minecraft-ci-release/SKILL.md
source content

Minecraft CI / Release Skill

Workflow Overview

PR opened → build + test checks
main branch push → build artifacts
Tag push (v*) → build + publish to Modrinth + CurseForge + GitHub Releases

Routing Boundaries

  • Use when
    : the task is CI/CD pipelines, release automation, artifact publishing, versioning, or release governance.
  • Do not use when
    : the task is implementing gameplay/plugin/mod features (
    minecraft-modding
    ,
    minecraft-plugin-dev
    ,
    minecraft-datapack
    ).
  • Do not use when
    : the task is server runtime operations and infrastructure tuning (
    minecraft-server-admin
    ).

Versioning Convention

Minecraft mod versions follow:

{mod_version}+{mc_version}

1.0.0+1.21.1   ← mod 1.0.0 for MC 1.21.1
1.2.3+1.21.1
2.0.0+1.21.4

Git tag format:

v1.0.0
(mod version only, not MC version in the tag).


Core CI Workflow (NeoForge + Fabric)

.github/workflows/build.yml

name: Build

on:
  push:
    branches: ["main", "develop"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    name: Build (${{ matrix.platform }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform: [neoforge, fabric]
      fail-fast: false

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java 21
        uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build (${{ matrix.platform }})
        run: ./gradlew :${{ matrix.platform }}:build --no-daemon

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: mod-${{ matrix.platform }}-${{ github.sha }}
          path: ${{ matrix.platform }}/build/libs/*.jar
          if-no-files-found: error

Release Workflow (with Publishing)

.github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write      # for creating GitHub releases

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java 21
        uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Extract version from tag
        id: version
        run: echo "MOD_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Build all platforms
        run: ./gradlew build --no-daemon

      - name: Publish to Modrinth & CurseForge
        run: ./gradlew publishMods --no-daemon
        env:
          MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
          CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            fabric/build/libs/*.jar
            neoforge/build/libs/*.jar
          generate_release_notes: true
          draft: false
          prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}

Paper Plugin CI

.github/workflows/build.yml
(plugin)

name: Build

on:
  push:
    branches: ["main"]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"
      - uses: gradle/actions/setup-gradle@v3
      - run: chmod +x gradlew
      - run: ./gradlew shadowJar --no-daemon
      - uses: actions/upload-artifact@v4
        with:
          name: plugin-${{ github.sha }}
          path: build/libs/*.jar

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"
      - uses: gradle/actions/setup-gradle@v3
      - run: ./gradlew test --no-daemon

Modrinth Publishing (minotaur)

build.gradle.kts
(root or platform-specific)

plugins {
    id("com.modrinth.minotaur") version "2.8.7"
}

// === Fabric subproject ===
modrinth {
    token.set(System.getenv("MODRINTH_TOKEN") ?: "")
    projectId.set("YOUR-PROJECT-ID")    // from modrinth.com project slug or ID

    versionNumber.set("${project.version}")
    versionType.set("release")          // release | beta | alpha

    uploadFile.set(tasks.remapJar)      // the JAR to upload

    gameVersions.addAll("1.21.1")
    loaders.addAll("fabric")

    changelog.set(
        rootProject.file("CHANGELOG.md").readText()
            .substringAfter("## [${project.version}]")
            .substringBefore("\n## [")
            .trim()
    )

    dependencies {
        required.project("fabric-api")
        // optional.project("some-optional-mod")
    }
}

Combined Fabric + NeoForge publish task (root-level)

// root build.gradle.kts
tasks.register("publishMods") {
    dependsOn(":fabric:modrinth", ":neoforge:modrinth")
    dependsOn(":fabric:curseforge", ":neoforge:curseforge")
    group = "publishing"
    description = "Publish all platforms to Modrinth and CurseForge"
}

CurseForge Publishing

build.gradle.kts

plugins {
    id("net.darkhax.curseforgegradle") version "1.1.25"
}

tasks.register<net.darkhax.curseforgegradle.TaskPublishCurseForge>("curseforge") {
    apiToken = System.getenv("CURSEFORGE_TOKEN") ?: ""

    val cf = upload(PROJECT_ID, tasks.named("remapJar"))  // or shadowJar
    cf.changelogType = "markdown"
    cf.changelog = rootProject.file("CHANGELOG.md").readText()
        .substringAfter("## [${project.version}]")
        .substringBefore("\n## [")
        .trim()

    cf.releaseType = "release"
    cf.addGameVersion("1.21.1")
    cf.addModLoader("Fabric")     // "NeoForge" for NeoForge subproject
    cf.addRequirement("fabric-api")
    // cf.addJavaVersion("Java 21")

    // Replace PROJECT_ID with your numeric CurseForge project ID
}

Replace

PROJECT_ID
with your actual numeric CurseForge project ID (found in project settings).


gradle.properties
Secrets Pattern

Never hardcode tokens. Read them from environment:

# gradle.properties (committed)
mod_id=mymod
mod_version=1.0.0
minecraft_version=1.21.1
modrinth_project_id=AABBCCDD
curseforge_project_id=123456

# DO NOT commit tokens
# Set these as GitHub repo secrets:
# MODRINTH_TOKEN, CURSEFORGE_TOKEN

Semantic Versioning for Mods

ChangeVersion bump
New features, no breaking changesMinor:
1.1.0
Bug fixes onlyPatch:
1.0.1
API/config breaking changesMajor:
2.0.0
Minecraft version updateKeep mod version, change
+1.21.4
suffix
Pre-release
1.0.0-beta.1
,
1.0.0-rc.1

CHANGELOG.md Convention

# Changelog

## [1.1.0] — 2025-06-01
### Added
- New `/kit` command
- PDC-based kill tracker

### Fixed
- Death message not appearing on Paper 1.21.11

## [1.0.0] — 2025-05-01
### Added
- Initial release

Automate CHANGELOG parsing in Gradle (as shown above in modrinth block) by extracting the section between version headers.


Dependabot Configuration

.github/dependabot.yml

version: 2
updates:
  - package-ecosystem: "gradle"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      gradle-plugins:
        patterns:
          - "com.gradleup.shadow"
          - "dev.architectury.loom"
          - "com.modrinth.minotaur"
          - "net.darkhax.curseforgegradle"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Build Caching Best Practices

# In all workflow jobs:
- name: Setup Gradle
  uses: gradle/actions/setup-gradle@v3
  with:
    # Read-only cache on PRs, read-write on main
    cache-read-only: ${{ github.event_name == 'pull_request' }}
    # Cache Minecraft assets (speeds up loom tasks by minutes)
    gradle-home-cache-includes: |
      caches
      notifications
      .gradle/loom-cache

Branch Protection + Required Checks

Recommended GitHub branch protection for

main
:

  • Require status checks:
    build (fabric)
    ,
    build (neoforge)
    ,
    test
  • Require linear history (squash/rebase merges)
  • Require signed commits (optional but recommended for release workflows)

Tag and Release Script

#!/usr/bin/env bash
# scripts/release.sh <version>
# Usage: ./scripts/release.sh 1.1.0
set -euo pipefail

VERSION="${1:?Usage: release.sh <version>}"

# Update gradle.properties
sed -i "s/^mod_version=.*/mod_version=${VERSION}/" gradle.properties

# Stage and commit
git add gradle.properties
git commit -m "chore: release v${VERSION}"

# Tag
git tag "v${VERSION}"

echo "Created commit and tag v${VERSION}"
echo "Push with: git push && git push --tags"

Workflow Snippet Validator

Use the bundled validator script to keep

SKILL.md
workflow snippets copy-paste safe:

# Run from the installed skill directory:
./scripts/validate-workflow-snippets.sh --root .

# Strict mode treats warnings as failures:
./scripts/validate-workflow-snippets.sh --root . --strict

The validator is bundled and self-contained. Run it from a copied

.agents/
,
.codex/
, or
.claude/
minecraft-ci-release
skill directory without relying on repo-root
node_modules
.

What it checks:

  • YAML snippet structure for workflow-like blocks (
    name
    ,
    on
    ,
    jobs
    )
  • Unresolved placeholder tokens and suspicious glob patterns
  • ${{ secrets.* }}
    usage stays consistent with secrets documented in this file

References