Kotlin Update & Release Workflow
A skill for integrating Kotlin updates from Renovate and releasing new versions via GitHub Actions.
Constitution (Critical Rules)
- NEVER commit directly to local
main. All work must be done on the renovate branch, merged via GitHub PRs. - NEVER push to
mainwithout going through the PR process for code changes. Version bumps and snapshot updates are the only exceptions. - ALWAYS verify CI passes on the PR before merging.
- ALWAYS follow the exact commit message conventions from past releases (see Workflow step 5 and 8).
- ALWAYS ensure
version.txthas no trailing newline and no UTF-8 BOM when writing it. - DO NOT add co-author trailers to commits unless explicitly requested.
- DO NOT leave the
fix/kotlin-updatebranch around after pushing — clean it up.
Prerequisites
ghCLI authenticated with repo access- Git configured with correct identity
- Push access to the repository (may need admin bypass for branch protection)
Workflow
1. Identify the Kotlin Update PR
# List open PRs from Renovate
gh pr list --state open --author renovate
# Check specifically for Kotlin PRs
gh pr list --head renovate/kotlin-monorepo --state open
There is typically one Kotlin update PR:
renovate/kotlin-monorepo— created by Renovate's default behavior. This is an open (non-draft) PR with automerge enabled.
If there happens to be a duplicate draft PR (e.g., from a custom branchPrefix), close it after merging.
2. Check CI Status
gh pr checks <PR-NUMBER>
If CI passes, skip to step 5. If CI fails, continue to step 3.
3. Fix Build Failures on the Renovate Branch
Checkout the branch locally, fix the issues, and push back:
# Create a local branch from the remote
git checkout -b fix/kotlin-update origin/renovate/kotlin-monorepo
# Fix the build issues (see Common Breaking Changes below)
# ... make changes ...
# Verify locally — use the Gradle MCP `gradle` tool with commandLine: ["build"] to verify the build.
# Commit and push back to the PR branch
git add <changed-files>
git commit -m "<descriptive fix message>"
git push origin fix/kotlin-update:renovate/kotlin-monorepo
Do NOT switch back to main or delete the branch yet — you may need additional fix pushes.
4. Wait for CI to Pass
# Poll until CI completes (typically 3-5 minutes)
Start-Sleep -Seconds 120
gh pr checks <PR-NUMBER>
Repeat until CI shows pass. If CI fails again, go back to step 3.
5. Merge the PR via GitHub
# Merge with admin bypass if branch protection blocks
gh pr merge <PR-NUMBER> --merge --admin
# Close the duplicate draft PR if one exists
gh pr close <DRAFT-PR-NUMBER>
6. Pull Changes to Local Main
git checkout main
git pull origin main
git branch -D fix/kotlin-update
7. Prep for Release
Update version.txt to the new release version:
[System.IO.File]::WriteAllText(
(Join-Path $PWD "version.txt"),
"X.Y.Z",
(New-Object System.Text.UTF8Encoding($false))
)
git add version.txt
git commit -m "Prep for X.Y.Z release"
git push origin main
8. Trigger the Release Workflow
gh workflow run release.yaml
The release workflow will:
- Run CI (build + test on all platforms)
- Verify version is NOT a SNAPSHOT (fails if it is)
- Publish to Maven Central via
publishAndReleaseToMavenCentral - Create a GitHub Release with auto-generated notes
- Deploy docs to GitHub Pages
9. Wait for Release to Complete
# Get the run ID from the output of `gh workflow run`
# Or find it:
gh run list --workflow=release.yaml --limit 3
# Monitor the run
gh run view <RUN-ID> --json status,conclusion
Wait until conclusion is success. The release workflow typically takes 15-25 minutes (builds on all 3 platforms: ubuntu, windows, macos).
10. Verify the Release
gh release list --limit 3
# Should show the new version as "Latest"
11. Bump to Next Snapshot
[System.IO.File]::WriteAllText(
(Join-Path $PWD "version.txt"),
"X.Y.(Z+1)-SNAPSHOT",
(New-Object System.Text.UTF8Encoding($false))
)
git add version.txt
git commit -m "Bump version to X.Y.(Z+1)-SNAPSHOT"
git push origin main
Common Breaking Changes
abiValidation API (Kotlin 2.4.0+)
Error: 'val enabled: Property<Boolean>' is deprecated. Property was removed, to enable ABI validation call function abiValidation().
Error: 'interface AbiValidationMultiplatformExtension' is deprecated. The class was removed.
Error: 'fun klib(action: Action<AbiValidationKlibKindExtension>)' is deprecated. Block 'klib' was removed.
Fix in buildSrc/src/main/kotlin/kotlin-shared.gradle.kts:
- Replace
kotlin.the<AbiValidationExtension>().apply { enabled = true }withkotlin.abiValidation() - Replace
kotlin.the<AbiValidationMultiplatformExtension>().apply { enabled = true; klib { ... } }withkotlin.abiValidation { keepLocallyUnsupportedTargets = true } - Remove imports for
AbiValidationExtensionandAbiValidationMultiplatformExtension - Keep
@OptIn(ExperimentalAbiValidation::class) - The
klib {}block is removed in 2.4.0:klib.enabled— removed, ABI dumps are always generated for klib targetsklib.keepUnsupportedTargets— moved toabiValidation { keepLocallyUnsupportedTargets = true }(top level)
- Since klib ABI dumps are always generated, you need to skip
checkKotlinAbiwhen running withinspekt.onlyJvm=true(native targets not registered):afterEvaluate { if (onlyJvm) { tasks.findByName("checkKotlinAbi")?.enabled = false } } - Update JVM ABI dumps with
:module:updateKotlinAbitask - Update the klib ABI dump if needed (run
:inspekt:updateKotlinAbiwithoutonlyJvm)
getBooleanArgument API Change (Kotlin 2.4.0+)
Error: Too many arguments for 'fun FirAnnotation.getBooleanArgument(name: Name): Boolean?'
Fix in compiler-plugin/src/main/kotlin/fir/InspektChecker.kt:
- In Kotlin 2.4.0,
FirAnnotation.getBooleanArgument()no longer accepts asessionparameter - Remove the
sessionargument from calls - Use the elvis operator for defaults:
annotation.getBooleanArgument(Name.identifier("foo")) ?: false
suspendFunctionN for Suspend Function References (Kotlin 2.4.0+)
Error: AddContinuationLowering assertion: FUN name:invoke ... has no continuation; can't call FUN name:test ... [suspend]
Fix in compiler-plugin/src/main/kotlin/ir/SpektGenerator.kt:
- In
createFunctionObject(), when creatingIrFunctionReferenceImplfor suspend functions, usesuspendFunctionN(N)instead ofkFunctionN(N)for the type:(if (function.isSuspend) builtIns.suspendFunctionN(function.parameters.size) else builtIns.kFunctionN(function.parameters.size)).defaultType - In Kotlin 2.4.0,
AddContinuationLoweringstrictly enforces that only suspend functions can call other suspend functions. AKFunctionNreference type has a non-suspendinvoke, whileSuspendFunctionNhas a suspendinvoke. UsingKFunctionNfor a suspend function reference causes the IR backend to fail because it tries to call a suspend function from a non-suspend context.
-Xcontext-parameters and -Xannotation-default-target Compiler Arguments (Kotlin 2.4.0+)
Error: The argument '-Xcontext-parameters' is redundant for the current language version 2.4.
Warning: The argument '-Xannotation-default-target=param-property' is redundant for the current language version 2.4.
Fix in buildSrc/src/main/kotlin/kotlin-shared.gradle.kts:
- Remove
-Xcontext-parametersfromfreeCompilerArgs(now redundant, context parameters are enabled by default) - Remove
-Xannotation-default-target=param-propertyfromfreeCompilerArgs(default behavior changed in 2.4)
getPluginArtifactForNative Removed (Kotlin 2.4.0+)
Error: ABI check failed — getPluginArtifactForNative missing from the generated ABI.
Fix: KotlinCompilerPluginSupportPlugin.getPluginArtifactForNative() was removed in Kotlin 2.4.0. Update the ABI dump:
- Run
:gradle-plugin:updateKotlinAbito regenerate the dump - Or manually remove the
getPluginArtifactForNativeline fromgradle-plugin/api/gradle-plugin.api
kcp-development and symbol-export Dependency Updates
When upgrading Kotlin, also check if kcp-development and symbol-export need updates:
- The
kcp-developmentlibrary inlines compiler plugin code and must be compatible with the target Kotlin version - Look up available versions:
mcp_gradle_lookup_maven_versions(coordinates: "dev.rnett.kcp-development:dev.rnett.kcp-development.gradle.plugin") - Check
symbol-exportsimilarly - Update both in
gradle/libs.versions.toml - A
ClassCastExceptioninBaseSpecCompilerPluginRegistrar.registerExtensions(FirExtensionRegistrarAdapter cannot be cast to ProjectExtensionDescriptor) indicates thekcp-developmentversion is incompatible with the current Kotlin version
Updating Compiler Plugin Test Golden Files
After fixing compilation and runtime errors, the test golden files (*.fir.txt, *.ir.txt) need to be actualized:
- Run
:compiler-plugin:clearDumpsto remove old expected dump files - Run tests with
OVERWRITE_EXPECTED=truesystem property:.\gradlew :compiler-plugin:test -PtestJvmArgs="-DOVERWRITE_EXPECTED=true" --rerun - Verify all tests pass without the overwrite flag:
.\gradlew :compiler-plugin:test --rerun - The
clearDumpstask is provided bykcp-development'sCompilerPluginTestingPlugin
JS/Yarn Lock Cross-Platform Issues
Error: CI fails with yarn lock inconsistencies after Kotlin update.
Fix in .github/workflows/ci.yaml:
- Add a
kotlinUpgradeYarnLockstep before thechecktask:- name: Upgrade yarn lock run: ./gradlew kotlinUpgradeYarnLock - This ensures the yarn lock is regenerated consistently for the current platform and Kotlin version
- The yarn lock may be consistent on Windows but differ on Linux — always verify CI passes
Project Configuration
- Version file:
version.txt(single source of truth, read by CI and release workflows) - Kotlin version:
gradle/libs.versions.toml→[versions] kotlin = "X.Y.Z" - Release workflow:
.github/workflows/release.yaml(manualworkflow_dispatchtrigger) - CI workflow:
.github/workflows/ci.yaml(runs on push and workflow_call) - Renovate config: No custom
renovate.json— uses Renovate defaults with automerge enabled - Only-JVM mode:
inspekt.onlyJvm=truesystem property disables native/wasm targets and klib ABI checks - Dependencies:
kcp-developmentandsymbol-exportingradle/libs.versions.tomlmust be compatible with the Kotlin version
Push Restrictions
This repository has a server-side pre-receive hook that rejects commits with timestamps before 4:00 PM on weekdays. If pushing fails with a "forbidden timestamp" error:
- Wait until after 4:00 PM local time to push, OR
- Amend commit dates:
git rebase HEAD~N --exec "git commit --amend --no-edit --date=now"(for N unpushed commits)
Source: rnett/inspekt — distributed by TomeVault.