# Async hooks supported

- **Pattern:** `ab-002078` (`plugin-extension-architecture.hook-system.async-hooks`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002078
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002078)

## Why it matters

Most real-world plugins need to do async work — call external APIs, query databases, read files. If the hook invocation loop does not await handler return values, any plugin that returns a Promise appears to work but silently drops its errors. The host continues as though the handler succeeded while the underlying operation failed. This creates data integrity issues in write operations, invisible failures in webhook processors, and phantom results in request pipelines. ISO 25010 reliability.fault-tolerance requires that errors propagate to where they can be handled, not vanish into unhandled rejections.

## Severity rationale

High because async handlers whose Promises are not awaited silently swallow errors, making plugin failures invisible and corrupting write pipelines without any logged indication.

## Remediation

Audit every hook invocation site and ensure the loop awaits each handler. For sequential semantics, use `for...of` with `await`; for parallel semantics, use `Promise.all`. Unhandled rejections must be caught and attributed to the specific plugin.

```typescript
async function invokeHook(name: string, context: HookContext): Promise<void> {
  const handlers = this.registry.get(name) ?? [];
  for (const handler of handlers) {
    await handler(context); // awaits each handler in sequence
  }
}
```

## Detection

- **ID:** `async-hooks`
- **Severity:** `high`
- **What to look for:** Check whether hook handlers can be async functions (returning Promises). Examine:
  1. Does the hook invocation code `await` handler results or call `.then()` on them?
  2. Are handlers explicitly typed to allow `Promise<void>` or `async` return types?
  3. What happens if a handler returns a Promise but the host doesn't await it? (Silent failure pattern)
  4. For tapable-based systems: are `tapAsync` and `tapPromise` variants available alongside `tap`?
  Real-world async hook examples:
  - Fastify: all hooks support async handlers natively — `async (request, reply) => {}`
  - webpack tapable: `tapAsync(name, callback)` and `tapPromise(name, handler)` alongside sync `tap`
  - Express: async middleware requires wrapping or `express-async-errors` — this is a common failure point
- **Pass criteria:** Count all async-capable hooks. Hook handlers can return Promises and the host correctly awaits them. If the system distinguishes sync and async hooks, both variants are available. Errors from async handlers are caught and propagated (not silently swallowed). At least 100% of hooks that invoke async operations must support async execution.
- **Fail criteria:** Hook handlers that return Promises are not awaited — the host fires and forgets. OR async handlers are not supported and the documentation doesn't mention this limitation. OR errors from async handlers are silently swallowed (the Promise rejection is unhandled).
- **Skip (N/A) when:** The hook system is intentionally synchronous-only AND this is documented AND the use case doesn't require async operations (e.g., synchronous transform pipelines like Babel visitors).
- **Detail on fail:** `"Hook handlers return values are ignored — the invocation loop calls handler() but does not await the result. Async handlers silently fail because their Promise rejections are never caught. Any plugin that needs to do async work (API calls, file I/O, database queries) will appear to work but silently drop errors."`
- **Remediation:** Most real-world plugins need to perform async work. If the hook system doesn't support async handlers, plugin authors will resort to workarounds (synchronous blocking, fire-and-forget) that cause subtle bugs.

  ```typescript
  // Invoking hooks with async support:
  async function invokeHook(name: string, context: HookContext): Promise<void> {
    const handlers = this.registry.get(name) ?? [];
    for (const handler of handlers) {
      await handler(context); // Sequential: await each handler
    }
  }
  ```

## External references

- iso-25010 reliability.fault-tolerance
- iso-25010 maintainability.modularity

Taxons: error-resilience, code-quality

HTML version: https://auditbuffet.com/patterns/ab-002078
