---
name: async-await-correctness-review
description: "Reviews async/await code for coroutine and promise mechanics that go wrong silently — a coroutine or promise that is never awaited, independent awaits run one at a time in a loop, a synchronous blocking call sitting on the event loop, and CPU-heavy work stalling the loop. Names the specific fix for each. Use when someone asks you to review async, concurrent, or coroutine code. Do NOT use for shared-state data races and locking, or for non-async performance tuning."
metadata:
  decimalai-skill-id: "9ba7d0be-7bd9-45e9-931c-114063a03a13"
  decimalai-version-id: "5c99b25d-8dfc-4cfe-8a42-63c184e07ced"
  decimalai-version: "1"
  decimalai-body-sha256: "1215a030ecdbe5c029738920007e896bb117838a84879833247a98c2761187f6"
  decimalai-name: "async-await-correctness-review"
  decimalai-format: "1"
  decimalai-invocation: "model"
  decimalai-category: "code-review"
  decimalai-source: "https://app.decimal.ai/s/async-await-correctness-review@1/SKILL.md"
---
# Async / Await Correctness Review

Reviews async code across Python (`asyncio`), JavaScript/TypeScript (promises), Rust (`async`/`.await`), and Swift (structured concurrency) for the mechanics of coroutines and promises — not the shared-state races and locks that a general concurrency review covers. The four hazards below all fail quietly: the code compiles, often runs, and looks correct, but a coroutine never executes, a page waits ten times longer than it needs to, or one blocking line freezes every other task on the loop.

The base model knows each of these rules but does not apply them by default. Handed a loop that awaits independent calls one at a time, or an `async def` invoked without `await`, it usually reads the code as fine. This skill makes the four checks mandatory and requires naming the concrete fix, not just noting a smell.

## When to Activate

Activate when the user asks to:
- Review this async / coroutine / concurrent code
- Check whether this handler awaits things correctly
- Is this `asyncio` / promise / `Future` code right?
- Why is this async path slow / not running / hanging?
- Look over the concurrency in this function

Do NOT activate for shared-state data races, mutex/lock correctness, or non-async performance work — those belong to a concurrency-safety or performance review.

## The Four Hazards

For each finding, name the hazard, say what breaks at runtime, and give the exact fix (function or keyword), not a vague "consider making this concurrent."

### 1. Unawaited / floating coroutine or promise

A coroutine or promise is created but nothing waits on it. The work may never run, may run after the caller has moved on, or may swallow its own error.

- **Python** — calling an `async def` without `await` produces a coroutine object that is never scheduled (and emits a "coroutine was never awaited" warning). Fix: `await` it, or `asyncio.create_task(...)` when you truly want it in the background.
- **Background tasks** — `asyncio.create_task(...)` whose handle is dropped can be garbage-collected mid-flight. Fix: keep a reference (store the task, add it to a set, or await it later).
- **JavaScript** — an async call with no `await` and no `.then/.catch` is a floating promise; a rejection becomes an unhandled rejection. Fix: `await` it, or attach `.catch(...)`.
- **Rust** — a future does nothing until polled; calling an `async fn` without `.await` (and without spawning it) means the body never runs. Fix: `.await` it, or `tokio::spawn` it.

### 2. Independent awaits serialized in a loop

Each iteration `await`s a call that does not depend on the previous result, so N independent operations take the sum of their latencies instead of the max. This is a mechanics fix, distinct from an N+1 query.

- **Python** — replace `for x in xs: results.append(await f(x))` with `results = await asyncio.gather(*(f(x) for x in xs))`.
- **JavaScript** — replace the sequential `for … of` (or a bare `.map(async …)` whose promises are never awaited together) with `await Promise.all(xs.map(f))`.
- **Swift** — replace sequential `await` calls with `async let` for a fixed set, or a `TaskGroup` for a collection.
- Keep the sequential form only when each step genuinely depends on the previous one, or ordering/rate limits require it — say so explicitly when you leave it.

### 3. Synchronous blocking call on the event loop

A blocking synchronous call runs inside async code and stalls the single event-loop thread, freezing every other task until it returns.

- **Sleeping** — `time.sleep(n)` in an `async def` blocks the loop. Fix: `await asyncio.sleep(n)`.
- **Blocking I/O** — a synchronous HTTP/DB/file call (e.g. `requests.get`, a sync driver) in async code. Fix: use the async client (`httpx.AsyncClient`, `aiohttp`, an async DB driver), or offload with `await asyncio.to_thread(fn, ...)` / `run_in_executor`.
- **Sync-over-async** — calling `asyncio.run(...)` or `loop.run_until_complete(...)` from inside an already-running loop raises "this event loop is already running" or deadlocks. Fix: `await` the coroutine directly; never start a nested loop.

### 4. CPU-bound work blocking the loop

A tight, CPU-heavy computation (parsing, hashing, image work, big serialization) runs directly in an `async def`. `await` never yields during pure computation, so the loop is blocked for the whole run.

- **Python** — offload to a thread with `await asyncio.to_thread(fn, ...)` for GIL-releasing work, or to a `ProcessPoolExecutor` via `run_in_executor` for pure-Python CPU work.
- **JavaScript** — move the work to a Worker thread (`worker_threads`) or chunk it so it yields.
- The tell is heavy computation with no `await` inside it, sitting on the request path.

## Output

Report each finding as: **hazard → what breaks at runtime → the exact fix**. If the async code is already correct on all four, say so plainly rather than inventing a problem. Do not raise shared-state locking or general performance issues here.
