async-safety

Enforces no-silent-failure async discipline in Flutter and Dart code.

Updated Aug 22, 2026
One-click install
npx skills add https://github.com/zakariaf/NearlyStop --skill async-safety-zakariaf
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: async-safety
Source: https://github.com/zakariaf/NearlyStop/tree/main/.claude/skills/async-safety
Command: npx skills add https://github.com/zakariaf/NearlyStop --skill async-safety-zakariaf

SYSTEM DOCUMENTATION & REQUIREMENTS

What problem does it solve? Async bugs in Flutter are silent: a dropped Future swallows its error, a dead BuildContext throws into a void, and a leaked subscription fires into a disposed widget. The arrow-callback pattern onTap: () => vm.save(x) passes every lint while discarding both the Future and its error, and no analyzer rule catches it. ## Core Features & Use Cases - Callback Future-drop prevention: Requires every onTap/onPressed/VoidCallback handler to return void, routing async work through a void method that wraps the call in unawaited(... .catchError(...)). - Async gap guards: Mandates a mounted/ref.mounted check after every await before touching BuildContext, or capturing Navigator/ScaffoldMessenger before the gap, with the guard placed after the last of multiple awaits. - Resource teardown and honest catches: Requires disposing every StreamSubscription, StreamController, Timer, and sink, typed catches that log stack traces and surface failures, rethrow over throw e, and no asserts on plugin return values. - Use Case: When reviewing a Flutter pull request that wires a save button, apply this Skill to rewrite onTap: () => vm.save(note) into a void handler with a catchError chain, add mounted guards after awaits, and verify dispose() cancels all subscriptions. ## Quick Start Review this Flutter widget's async code and fix any silent-failure patterns such as arrow callbacks returning Futures, missing mounted guards after awaits, or undisposed subscriptions.

Frequently Asked Questions about async-safety

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I fix the onTap arrow callback dropping a Future in Flutter?

Route the tap through a void-returning handler method instead of `onTap: () => vm.save(x)`. Inside that method, call `unawaited(_persist(x).catchError(...))` so the error is logged and surfaced rather than silently discarded.

How do I use BuildContext after an await in Flutter?

Either capture Navigator.of(context) or ScaffoldMessenger before the await, or place `if (!mounted) return;` immediately after the await and before the context use. With multiple awaits, the guard must sit after the last one.

Why does discarded_futures not catch my async onTap callback?

The arrow closure `() => vm.save(x)` returns the Future, so discarded_futures, unawaited_futures, and unused_result all consider it used, while the VoidCallback target type discards it. This shape is caught by no lint and must be fixed structurally with a void handler.

Does Riverpod need a mounted check after await?

Yes. In a Riverpod 3.x Notifier or AsyncNotifier, use `if (!ref.mounted) return;` after an await before assigning state, since the provider may have been disposed during the suspension.

When is unawaited() acceptable in Dart?

unawaited is acceptable only when the Future provably cannot fail silently, meaning it ends in a .catchError(...) handler or is total. Bare unawaited on a fallible path routes errors to PlatformDispatcher.onError, detached from the UI.

Why should I avoid assert on plugin return values in Flutter?

Asserts are stripped in release builds, so a check that passes in every test disappears on the device, creating a silent failure. Handle plugin results with explicit runtime checks and typed error handling instead.