Yeda AI Tips · #118

Español

Ban GlobalScope.launch in Code Reviews

Your AI assistant just leaked a coroutine, and you will never see the error. GlobalScope.launch compiles, runs, and looks harmless in a diff — which is exactly why AI-generated Kotlin reaches for it so often. Make it an automatic review reject.

Why GlobalScope is a leak by design

GlobalScope is a CoroutineScope with no Job behind it. Anything you launch there lives for the whole application lifetime and is tied to nothing that can cancel it. The official Kotlin docs flag three concrete failure modes:

  1. Uncontrolled work — the coroutine keeps running after the thing that started it (a screen, a request, a job) is gone.
  2. Resource leaks — a coroutine that never resumes or gets cancelled holds its memory, sockets, and file handles forever.
  3. Silent crashes — there is no parent to receive failures, so without an explicit CoroutineExceptionHandler the exception surfaces platform-specifically or not at all. That is the "you will never see the error" part.

The API is so easy to misuse that Kotlin marks it @DelicateCoroutinesApi: you must explicitly opt in with @OptIn(DelicateCoroutinesApi::class) to use it without a warning. Treat that annotation in a diff as a red flag, not a formality.

What structured concurrency buys you

Structured concurrency means coroutines form a tree: every coroutine has a parent, a parent waits for its children, and cancelling the parent recursively cancels every child. Two guarantees fall out of that:

GlobalScope.launch opts out of the entire tree. That is the whole bug.

The review checklist

You see in the diffAsk for instead
GlobalScope.launch { ... }A scope owned by the caller (viewModelScope, an injected CoroutineScope, coroutineScope { })
Sequential work wrapped in launchA plain suspend function — no new coroutine needed
Concurrent work inside a suspend functioncoroutineScope { launch { } ; launch { } }
catch (e: CancellationException) without rethrowRethrow it — swallowing it breaks cancellation for the whole branch
GlobalScope hardcoded in a classAn injected CoroutineScope constructor parameter (Android's official best practice — it also makes the class testable)

That last row matters for AI-generated code specifically: hardcoding GlobalScope usually means hardcoded Dispatchers too, and code that no test can control.

The cancellation trap

Cancellation in coroutines is cooperative and travels as a CancellationException. Two ways generated code silently breaks it:

Either bug plus GlobalScope compounds: unowned work that also refuses to die.

Power-user: enforce the ban with a linter

Don't rely on human reviewers to spot it. detekt ships a GlobalCoroutineUsage rule (coroutines rule set) that flags GlobalScope.launch and GlobalScope.async — it is off by default, so turn it on:

# detekt.yml
coroutines:
  GlobalCoroutineUsage:
    active: true

Then put the same rule in your AI assistant's project instructions ("never use GlobalScope; launch in a scope owned by the caller"), so the code arrives correct instead of getting bounced in review. The one legitimate exception the Kotlin docs allow: a true application-lifetime background job, launched once, with an explicit CoroutineExceptionHandler and the @OptIn annotation making the choice visible.

Resources

Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.

Talk to us · Read the blog