agentleFS
BK202503BK202503/open-claude-all/skills/kotlin-coroutine-review/SKILL.md

kotlin-coroutine-review

Skill7 starsChanged 4 months agoMITView on GitHub

Review Kotlin coroutine usage (typically in Spring Boot / Ktor code) for structured-concurrency violations, blocking calls inside suspend functions, dispatcher misuse, GlobalScope leaks, missing CoroutineExceptionHandler, and Job/Deferred lifecycle bugs. Triggers include "review this coroutine code", "why does my suspend function block", "kotlin async review", "am I using GlobalScope right".

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the file relies on version-specific behaviour, note it.

    line 72
  2. Distinctive:

    Not a Kotlin style linter (ktlint / detekt cover that).

    line 93
  3. Distinctive:

    Does not run the code. Verification of behaviour is the user's next step.

    line 94
  4. Distinctive:

    Does not migrate blocking code to coroutines wholesale — that's a design decision, not a review output.

    line 95

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

kotlin-coroutine-review

Coroutine bugs are almost never at compile time and rarely visible in unit tests — they show up as thread-pool starvation, silent exception swallowing, or leaked jobs. This skill runs a structured audit of a specific coroutine-heavy file or module.

Prerequisites

  • Target file(s) visible.
  • Kotlin version resolvable from build.gradle / build.gradle.kts.
  • kotlinx-coroutines-core version resolvable (behavior differs between 1.6, 1.7, 1.8).

Phase 1 — Anti-pattern sweep

Walk the file and flag each hit against this catalog:

Blocking inside suspend

  • Thread.sleep(...) in a suspend fun → use delay(...).
  • .get() / .await() on a Future / CompletableFuture → use the -await bridge (kotlinx-coroutines-jdk8).
  • Any JDBC call in a suspend function on the default dispatcher → wrap with withContext(Dispatchers.IO). JDBC is blocking.
  • runBlocking { ... } inside a suspend function → almost always a mistake; you're already in a coroutine.

GlobalScope / structured-concurrency violations

  • GlobalScope.launch { ... } → leaked coroutine; won't be cancelled by parent. Use an injected CoroutineScope or coroutineScope { ... }.
  • CoroutineScope(Dispatchers.IO).launch { ... } inline → same issue; the scope has no parent.
  • Any launch { ... } inside a suspend function whose result the caller depends on → parent may return before child completes. Wrap the block in coroutineScope { ... } or make it a top-level async.

Dispatcher misuse

  • Dispatchers.Default for I/O work → occupies the CPU pool. Use Dispatchers.IO.
  • Dispatchers.IO for CPU-bound work → starves I/O for other consumers. Use Dispatchers.Default.
  • newSingleThreadContext(...) in library code → creates and never closes a thread. Use an existing dispatcher or take a CoroutineScope from the caller.
  • Dispatchers.Main used in a non-UI backend service → the main dispatcher isn't installed on the JVM by default; runtime error at first use.

Exception handling

  • try { ... } catch (e: CancellationException) { ... } that swallows the exception → breaks cancellation propagation. Rethrow, or use try/finally for cleanup.
  • launch { ... } without a CoroutineExceptionHandler on the scope → uncaught exceptions terminate the parent scope (structured concurrency) or crash the process (root scope).
  • async { ... } whose .await() is never called → exception is swallowed until await() is invoked.
  • supervisorScope { ... } used where you actually want failure to propagate → sibling coroutines keep running after a failure.

Flow / Channel pitfalls

  • flow { emit(...) } doing I/O without .flowOn(Dispatchers.IO) → emissions block the collector's dispatcher.
  • stateIn(scope, WhileSubscribed(), initial) without a WhileSubscribed(5000) timeout → subscription churn on config changes recreates upstream unnecessarily.
  • Channel(UNLIMITED) in a producer without a bounded consumer → memory leak under back-pressure.
  • .collectLatest { ... } in a UI where every emission triggers cancellation of in-flight work → intentional if for user typing, dangerous if for RPC calls.

Scope lifecycle

  • Spring @Component holding a CoroutineScope field with no cancellation on @PreDestroy → jobs outlive the bean.
  • Ktor Application scope leaked into request handlers → request-scoped work runs beyond the request.
  • Android viewModelScope in a repository → repository logic tied to a specific viewmodel's lifecycle by accident.

Phase 2 — Version-specific gotchas

Note behaviour differences by kotlinx-coroutines version:

  • 1.6.x — Dispatchers.IO.limitedParallelism(n) limits the shared pool globally. Later releases have per-instance behaviour.
  • 1.7.x — runInterruptible interop with blocking JVM APIs was rewritten; older withContext(IO) { blockingCall() } may not surface InterruptedException correctly.
  • 1.8+ — Flow.filterIsInstance short-circuiting semantics changed for empty inputs.

If the file relies on version-specific behaviour, note it.

Phase 3 — Emit findings

Same format as spring-kafka-listener-review:

[<severity>] <category>: <one-line summary>
  Where: <file>:<line>
  Why it matters: <impact in 1 sentence>
  Fix: <concrete code change>
  Reference: <kotlinx-coroutines doc / KEEP link if applicable>

Severity ladder:

  • must-fix — leaks, silent exception swallowing, cancellation-safety violations
  • should-fix — dispatcher misuse, scope lifecycle unclarity
  • consider — style, more idiomatic patterns

Non-goals

  • Not a Kotlin style linter (ktlint / detekt cover that).
  • Does not run the code. Verification of behaviour is the user's next step.
  • Does not migrate blocking code to coroutines wholesale — that's a design decision, not a review output.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.