# How ai-memory Handles Hook Event Timeouts: Per-Webhook Configuration and Failure Policies

> Learn how ai-memory manages hook event timeouts with per-webhook settings, default timeouts, and failure policies for robust error handling within your AdmissionChain.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: how-to-guide
- Published: 2026-09-01

---

**ai-memory handles hook event timeouts through per-webhook `timeout_ms` configuration with a 2,000 ms default, a hard 30,000 ms upper bound enforced by the `webhook_timeout` helper, and `FailurePolicy`—based propagation or suppression of timeout errors in the `AdmissionChain` defined in [`crates/ai-memory-wiki/src/admission.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/admission.rs).**

The ai-memory project implements admission webhooks for page write, delete, and purge operations through a robust timeout mechanism that balances operator flexibility with system stability. This article examines the timeout handling implementation in the Rust-based wiki engine, tracing how configuration values flow through the `AdmissionChain` to `reqwest` client requests and how failures are handled according to operator-defined policies.

## Per-Hook Timeout Configuration in WebhookConfig

Every webhook registered in ai-memory is described by a `WebhookConfig` struct that includes explicit timeout control. The `timeout_ms` field allows operators to specify how long the `AdmissionChain` should wait for a webhook response before treating it as failed.

If an operator omits the timeout value, the system falls back to `default_timeout_ms()`, which returns **2,000 ms** as a safe default. This prevents unconfigured webhooks from causing unpredictable behavior while still providing a reasonable window for typical webhook processing.

```rust
// From crates/ai-memory-wiki/src/admission.rs (lines 33-35)
fn default_timeout_ms() -> u64 {
    2000 // 2 seconds default
}

```

When constructing webhook configurations, operators can override this default for individual hooks based on expected latency:

```rust
use ai_memory_wiki::{AdmissionChain, WebhookConfig, FailurePolicy, AdmissionOp};

/// Build a chain with a custom timeout (5 seconds) that aborts on failure.
let hook = WebhookConfig {
    name: "my_hook".into(),
    url: "http://example.com/hook".into(),
    timeout_ms: 5_000,               // custom per-hook timeout
    failure_policy: FailurePolicy::Reject,
    events: vec![AdmissionOp::WritePage],
    blocking: true,
};
let chain = AdmissionChain::new(vec![hook])?;

```

## Hard Upper Bound with webhook_timeout Helper

To prevent misconfigured webhooks from hanging the write path indefinitely, ai-memory enforces a **30,000 ms maximum timeout** through the `webhook_timeout` helper function. This clamping mechanism ensures that even an operator-specified timeout of one hour would be reduced to the safe maximum.

```rust
// From crates/ai-memory-wiki/src/admission.rs (lines 76-78)
fn webhook_timeout(timeout_ms: u64) -> std::time::Duration {
    // Minimum 1 ms, maximum 30,000 ms.
    std::time::Duration::from_millis(timeout_ms.clamp(1, 30_000))
}

```

The helper applies both a floor (1 ms) and ceiling (`MAX_WEBHOOK_TIMEOUT_MS = 30_000`) to every timeout value. This two-sided clamping prevents edge cases like zero-duration timeouts while capping worst-case latency impact on the write path.

## Applying Timeouts to Blocking and Async Webhooks

The `AdmissionChain` applies the clamped timeout to webhook requests through the `reqwest` HTTP client. For **blocking webhooks**, the timeout is set directly on the request builder:

```rust
// From crates/ai-memory-wiki/src/admission.rs (lines 61-63)
let response = self
    .client
    .post(&hook.url)
    .timeout(webhook_timeout(hook.timeout_ms))
    .json(&payload)
    .send()
    .await;

```

For **fire-and-forget async webhooks**, the same `webhook_timeout` helper ensures consistent timeout enforcement:

```rust
// From crates/ai-memory-wiki/src/admission.rs (lines 136-138)
let request = self
    .client
    .post(&hook.url)
    .timeout(webhook_timeout(hook.timeout_ms))
    .json(&payload)
    .build()?;

```

This unified approach guarantees that all webhook invocations—regardless of blocking semantics—respect the same timeout boundaries.

## Timeout Error Handling and FailurePolicy

When a webhook request exceeds its configured timeout, `reqwest` returns an `Err`. The `AdmissionChain` handles this through its error classification system, which respects the webhook's `failure_policy`:

- **`FailurePolicy::Reject`** – The timeout error propagates, causing the entire page write/delete/purge operation to abort with an `Io` error.
- **`FailurePolicy::Ignore`** – The timeout is logged as a warning, but the operation continues as if the webhook had succeeded.

This behavior applies uniformly to all request failures, including timeouts, connection errors, and DNS resolution failures:

```rust
// Error handling pattern from crates/ai-memory-wiki/src/admission.rs (lines 24-33)
match result {
    Ok(response) => { /* process 2xx or 204 responses */ }
    Err(e) => {
        warn!("Webhook {} failed: {}", hook.name, e);
        if hook.failure_policy == FailurePolicy::Reject {
            return Err(Error::Io(e.into()));
        }
        // Policy is Ignore: continue operation
    }
}

```

## Async Webhook Concurrency Limits

For non-blocking webhooks, ai-memory implements additional protection through a **semaphore-capped queue**. The `MAX_ASYNC_ADMISSION_IN_FLIGHT = 256` constant limits concurrent in-flight async webhook requests:

- If the queue has capacity, the async webhook spawns immediately with its configured timeout.
- If the queue is saturated, the webhook is **dropped** and a warning is emitted.

This mechanism prevents memory exhaustion during webhook storms while preserving latency bounds for operations that do proceed. Blocking webhooks bypass this semaphore—they execute synchronously with timeout enforcement directly on the request.

## Summary

- **Default timeout**: 2,000 ms via `default_timeout_ms()` when `timeout_ms` is unspecified.
- **Maximum timeout**: 30,000 ms hard cap enforced by `webhook_timeout()` clamping helper.
- **Request application**: Timeout passed to `reqwest` via `.timeout()` for both blocking and async webhooks.
- **Failure handling**: `FailurePolicy::Reject` aborts the operation; `FailurePolicy::Ignore` logs and continues.
- **Async limits**: 256 concurrent in-flight requests capped by semaphore; excess dropped with warning.

## Frequently Asked Questions

### What happens if a webhook times out in ai-memory?

If a webhook exceeds its configured `timeout_ms`, `reqwest` returns an error that the `AdmissionChain` catches and logs as a warning. The consequence depends on the webhook's `failure_policy`: with `Reject`, the error propagates and aborts the page operation; with `Ignore`, the operation continues unaffected. This logic is implemented in [`crates/ai-memory-wiki/src/admission.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/admission.rs).

### What is the maximum webhook timeout allowed in ai-memory?

The maximum webhook timeout is **30,000 ms** (30 seconds), defined by `MAX_WEBHOOK_TIMEOUT_MS` and enforced through the `webhook_timeout` helper function. Any operator-specified timeout exceeding this value is clamped to 30 seconds, preventing indefinite hangs on the write path.

### How do I configure a custom timeout for a specific webhook?

Set the `timeout_ms` field in your `WebhookConfig` struct. Values are automatically clamped between 1 ms and 30,000 ms. For example: `timeout_ms: 5_000` for a 5-second timeout. If omitted, the system uses the 2,000 ms default from `default_timeout_ms()`.

### Does ai-memory handle async webhooks differently for timeouts?

Async webhooks use the same `webhook_timeout` helper for request timeouts, but they execute through a semaphore-limited queue (`MAX_ASYNC_ADMISSION_IN_FLIGHT = 256`). If the queue is full, the webhook is dropped rather than queued. Blocking webhooks execute synchronously with timeout enforcement and are not subject to this concurrency limit.