# Upgrade Ktor and Gradle Dependencies

Yes. Each week an agent bumps the Ktor plugin, Kotlin and libraries in a Ktor sample's Gradle build, tests after each group, and opens one pull request.

[Run this on Rush](https://cloud.getrush.ai/run/upgrade-gradle-dependencies/ktor/), in a Project that has your repository.

## What the agent does

The agent reads where the versions live, because in a Ktor project they are spread across the plugin block and a properties file rather than one catalog. In ktorio/ktor-samples, the `kweet` sample's `build.gradle.kts` applies `kotlin("jvm")` and the serialization plugin at `2.4.20` and `io.ktor.plugin` at `3.6.0`, and that plugin supplies the Ktor BOM, so the `io.ktor:ktor-server-*` lines carry no version at all. `kweet/gradle.properties` holds `kotlin_version=2.4.20`, `logback_version=1.5.21` and `exposed_version=1.0.0-rc-3`, read by the build script with `by project`; the rest are inline: H2 `2.4.240`, c3p0 `0.11.2`, Joda-Time `2.14.0`, FreeMarker `2.3.34`, Ehcache `3.11.1` and MockK `1.14.6`. The `jwt-auth-tests` sample has the same plugin versions, Logback inline, and `kotlin("test")` with `useJUnitPlatform()`. Both run on the Gradle 9.2.1 wrapper and declare `jvmToolchain(17)`. Only `ktor-client-wasm` has a `libs.versions.toml`; these two do not.

Then it reads the checks. `kweet/src/test/kotlin/KweetApplicationTest.kt` has four `testApplication` tests against a MockK `DAOFacade`: an empty home page, a home page with kweets, a failed login and a successful one. `jwt-auth-tests/src/test/kotlin/io/ktor/samples/jwtauth/ApplicationTest.kt` has one test that generates an RSA key pair, injects a fake JWK provider and checks a signed login round trip. At the repository root, `build-all.sh` runs `./gradlew clean check --console=plain` in every sample directory and `.github/workflows/build-all.yml` runs it on pull requests with JDK 24; `renovate.json` extends the recommended config, so bot bumps already arrive and the point of this run is to land them tested.

The groups follow what moves together. The `io.ktor.plugin` version is the Ktor version, so a Ktor bump is one plugin line per sample, and the BOM moves every `ktor-server-*` and the `ktor-server-test-host` test dependency with it. The two Kotlin plugins move together with `kotlin_version` in `gradle.properties`, and a Kotlin bump is checked against what the Ktor plugin release supports. Exposed in `kweet` sits on a release-candidate line and the code already imports `org.jetbrains.exposed.v1.*`, so a move to a final release is tried on its own, since the `exposed-jodatime` module and the DAO code in `dao/DAOFacadeDatabase.kt` are where a rename would show. H2, c3p0, Logback, FreeMarker, Ehcache and MockK move as a library group. The agent installs a JDK that satisfies `jvmToolchain(17)` before the first build.

After each group the agent runs `./gradlew test` in the sample, and `./gradlew check` as `build-all.sh` does, with `--console=plain` so the output is readable in the record. A group that passes is a commit; a group that fails, for example a Ktor release that changes a `testApplication` API the kweet test uses, is reverted and its compiler or test error kept. The pull request lists each bump with old and new versions per sample, the skips with their reason, and the test summaries. It works only in `kweet` and `jwt-auth-tests`, as the prompt says, and leaves the other samples alone.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Upgrade the Gradle dependencies of the kweet and jwt-auth-tests samples in this repository, in groups: the io.ktor.plugin version first, then the Kotlin plugins together with kotlin_version in gradle.properties, then Exposed on its own, then the remaining libraries together. Versions live in build.gradle.kts and gradle.properties; there is no version catalog in these two samples. Install a JDK that satisfies jvmToolchain(17). After each group run ./gradlew test and ./gradlew check --console=plain in each sample. Keep a group only if both pass in both samples; otherwise revert it and record the error. Do not touch other samples. Open one pull request with one commit per group, one line per bump with old and new versions, and every upgrade left out with the reason.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request that changes `kweet/build.gradle.kts`, `kweet/gradle.properties` and `jwt-auth-tests/build.gradle.kts`, in one commit per group that passed. Source files change only when a library group required it, for example an Exposed rename, and then the description names the file. The Gradle wrapper is unchanged unless the prompt asked for it.

The description is one line per bump per sample with old and new versions, a list of upgrades left out with the compiler error or failing test that excluded them, and the `./gradlew test` and `./gradlew check` summaries for both samples. The trajectory keeps the full Gradle output for every group, including the reverted ones.

Before merging, a reviewer checks that the `io.ktor.plugin` version is the same in both samples, that `kotlin_version` and the plugin versions agree, and that a left-out Ktor release is not a security fix that should go in with a code change.

## Run it on every Monday morning

Create an automation in the Project with this prompt and a `schedule` trigger (`cron` `0 9 * * 1` in your `timezone`). Each week the run opens one pull request with that week's bumps, or none when nothing moved. Because each group is its own commit, dropping one bump before merge is a revert of one commit.

To have the link reach your server, start the weekly run from your own scheduler instead, with `POST /o/{org}/sessions` and an API key that has a webhook URL: Rush then POSTs `run.completed` with the `session_url` when the turn ends, and a handler on your side can post the pull request link where the Ktor maintainers read. A run that ends with no safe group opens no pull request and says why in its result.

## Why run it on Rush?

A Gradle upgrade is slow on a laptop: a JDK, a wrapper download, a dependency resolution and a compile per attempt, and a Kotlin or Ktor plugin that disagrees with the other shows up only after all of that. On Rush the agent does each attempt in its own sandbox, keeps what passes, reverts what fails, and records every build log, so the pull request that reaches you is already green and the failures are written down with their errors.

Running two samples times four groups is eight full builds, which is why bot bumps sit unmerged. The record shows each build, and the same request runs on every Ktor service you maintain at the same time.

## Frequently asked questions

### Why is there no version catalog change?

Because `kweet` and `jwt-auth-tests` do not use one. Their versions are in the `plugins` block, inline dependency strings and `kweet/gradle.properties`. A project with a `gradle/libs.versions.toml` gets the same groups applied to the catalog instead; the agent reads the build to find out which.

### How does it bump Ktor itself?

By changing the `io.ktor.plugin` version in each `build.gradle.kts`. The plugin supplies the BOM, so the unversioned `io.ktor:ktor-server-*` and `ktor-server-test-host` dependencies follow it. There is no separate Ktor version property to keep in step.

### Which JDK does it build with?

One that satisfies `jvmToolchain(17)`, which both samples declare. The repository's CI installs JDK 24 and relies on Gradle's toolchain handling; the agent installs a 17 so the build does not depend on a resolver that is not configured.

### Renovate already opens bumps here. What does this add?

Renovate opens one pull request per dependency and lets CI say red or green. This run tries the bumps in groups, in a sandbox, reverts the ones that break the tests, and opens a single pull request that is already green, with the failures explained instead of left as red checks.
