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".
In this repository14 other files it gives its agents.
- ai-tell-cleanupSkill
- branch-guardSkill
- frontend-perf-impact-scanSkill
- jvm-memory-leak-reviewSkill
- nestjs-provider-reviewSkill
- nextjs-app-router-reviewSkill
- parallel-devSkill
- parallel-dispatchSkill
- parallel-prSkill
- pr-impact-scanSkill
- react-hooks-reviewSkill
- review-loopSkill
- reviewSkill
- spring-kafka-listener-reviewSkill
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
- Distinctive: line 72
If the file relies on version-specific behaviour, note it.
- Distinctive: line 93
Not a Kotlin style linter (ktlint / detekt cover that).
- Distinctive: line 94
Does not run the code. Verification of behaviour is the user's next step.
- Distinctive: line 95
Does not migrate blocking code to coroutines wholesale — that's a design decision, not a review output.
≈ “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.
Files
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-coreversion 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 asuspend fun→ usedelay(...)..get()/.await()on aFuture/CompletableFuture→ use the-awaitbridge (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 injectedCoroutineScopeorcoroutineScope { ... }.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 incoroutineScope { ... }or make it a top-levelasync.
Dispatcher misuse
Dispatchers.Defaultfor I/O work → occupies the CPU pool. UseDispatchers.IO.Dispatchers.IOfor CPU-bound work → starves I/O for other consumers. UseDispatchers.Default.newSingleThreadContext(...)in library code → creates and never closes a thread. Use an existing dispatcher or take aCoroutineScopefrom the caller.Dispatchers.Mainused 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 usetry/finallyfor cleanup.launch { ... }without aCoroutineExceptionHandleron 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 untilawait()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 aWhileSubscribed(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
@Componentholding aCoroutineScopefield with no cancellation on@PreDestroy→ jobs outlive the bean. - Ktor
Applicationscope leaked into request handlers → request-scoped work runs beyond the request. - Android
viewModelScopein 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 —
runInterruptibleinterop with blocking JVM APIs was rewritten; olderwithContext(IO) { blockingCall() }may not surfaceInterruptedExceptioncorrectly. - 1.8+ —
Flow.filterIsInstanceshort-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.
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
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.

