# How Hook Events Are Sent to the ai-memory Server and Response Codes Explained

> Learn how ai-memory sends hook events to its server and understand the HTTP 202 Accepted response code. Discover the details of the ai-memory POST request.

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

---

**When an agent triggers a lifecycle hook, the `ai_memory_post_hook` function in [`hooks/_lib.sh`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/_lib.sh) POSTs a JSON payload to the `/hook` endpoint with a 0.5-second timeout, and the ai-memory server returns HTTP 202 Accepted on success.**

The akitaonrails/ai-memory repository implements a fire-and-forget observability system where agents report session lifecycle events through shell hooks. Understanding how these **hook events** are transmitted to the **ai-memory server** and which **response codes** indicate success or failure is essential for debugging integration issues without blocking agent execution.

## Client-Side Hook Transmission

Hook events originate in agent lifecycle scripts (such as `session-start`, `pre-tool-use`, `post-tool-use`, and `session-end`) that source the shared library at [`hooks/_lib.sh`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/_lib.sh). This library provides the `ai_memory_post_hook` function, which constructs and transmits the HTTP request to the ai-memory server.

### Authentication and Headers

The `ai_memory_post_hook` function automatically handles authentication by checking for the `AI_MEMORY_AUTH_TOKEN` environment variable or a header file returned by `ai_memory_auth_header_file`. When a token is present, it adds an `Authorization: Bearer` header to the request. All requests include `Content-Type: application/json` to ensure proper payload parsing on the server side.

### Request Construction and Timeout

To prevent hooks from blocking agent execution, the client enforces a strict 0.5-second timeout using curl's `--max-time` flag. The function accepts the target URL as the first argument and reads the JSON payload from stdin via `--data-binary @-`. This design allows the agent to fire events asynchronously without waiting for full server processing.

```sh

# From hooks/_lib.sh

ai_memory_post_hook() {
    _amhdr=$(ai_memory_auth_header_file)
    if [ -n "${AI_MEMORY_AUTH_TOKEN:-}" ]; then
        curl -s --max-time 0.5 -X POST "$1" \
            -H "Content-Type: application/json" \
            -H "Authorization: Bearer $AI_MEMORY_AUTH_TOKEN" \
            --data-binary @-
    elif [ -n "$_amhdr" ]; then
        curl -s --max-time 0.5 -X POST "$1" \
            -H "Content-Type: application/json" \
            -H @"$_amhdr" \
            --data-binary @-
    else
        curl -s --max-time 0.5 -X POST "$1" \
            -H "Content-Type: application/json" \
            --data-binary @-
    fi
}

```

The URL construction is handled by `ai_memory_url_encode` in the same file, which appends query parameters including `event`, `agent`, `session_id`, and contextual data like `project` and `cwd` when a marker file is detected.

## Server-Side Processing and Response Codes

On the server side, the MCP router defined in [`crates/ai-memory-mcp/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/router.rs) registers the `POST /hook` route and delegates handling to `ai_memory_hooks::hook_router`. This architecture separates routing logic from the core hook processing implementation.

### The /hook Endpoint Handler

When the server receives a hook event, the handler parses the JSON payload, sanitizes the input, and persists the observation to the SQLite store. This operation is designed to be lightweight to accommodate the client's short timeout window. The implementation emphasizes durability over immediate confirmation, accepting the payload for asynchronous processing.

### HTTP Response Codes and Their Meanings

The ai-memory server uses specific HTTP status codes to communicate the result of hook ingestion:

- **202 Accepted**: Returned when the payload is successfully received and queued for storage. This indicates the hook event was accepted by the server, though processing may continue asynchronously.
- **400 Bad Request**: Returned when the JSON payload is malformed or required fields are missing in the request body.
- **500 Internal Server Error**: Returned for unexpected internal failures during processing, such as database write errors or serialization issues.

The unit tests in [`crates/ai-memory-mcp/tests/suite/stress_autoscope.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/tests/suite/stress_autoscope.rs) explicitly verify this contract:

```rust
// From crates/ai-memory-mcp/tests/suite/stress_autoscope.rs
let resp = client.request(hook_request(...)).await.unwrap();
assert_eq!(resp.status(), StatusCode::ACCEPTED, "hook must accept");

```

This test ensures that under load, the server consistently returns 202 Accepted for valid hook events, confirming the fire-and-forget semantics expected by the client implementation.

## Practical Code Examples

You can send hook events directly using standard HTTP tools or programmatic clients. The examples below demonstrate the expected request format and how to verify the 202 Accepted response.

### Sending Hooks with cURL

For debugging or custom integrations, you can replicate the client behavior using cURL with the same 0.5-second timeout used by the shell library:

```sh

# Example: Manual session-start hook submission

payload='{"event":"session-start","session_id":"abc123","cwd":"/home/user/project"}'
curl -s --max-time 0.5 -X POST \
     "http://localhost:49374/hook?event=session-start&agent=claude-code" \
     -H "Content-Type: application/json" \
     --data-binary "$payload"

# Expected: HTTP/1.1 202 Accepted

```

### Rust Client Implementation

When integrating with Rust applications using `reqwest`, expect the 202 status code to confirm successful acceptance:

```rust
use reqwest::StatusCode;

async fn send_hook_event() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let resp = client
        .post("http://localhost:49374/hook?event=session-start&agent=claude-code")
        .header("Content-Type", "application/json")
        .body(r#"{"session_id":"abc123","cwd":"/home/user/project"}"#)
        .send()
        .await?;
    
    assert_eq!(resp.status(), StatusCode::ACCEPTED);
    Ok(())
}

```

## Summary

- Hook events are transmitted via `ai_memory_post_hook` in [`hooks/_lib.sh`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/_lib.sh), which uses curl with a 0.5-second timeout to ensure non-blocking execution.
- The client automatically injects `Authorization: Bearer` headers when `AI_MEMORY_AUTH_TOKEN` is set, or reads headers from the auth file helper.
- Server-side routing in [`crates/ai-memory-mcp/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/router.rs) handles the `POST /hook` endpoint through the `ai_memory_hooks::hook_router` handler.
- Successful ingestion returns **HTTP 202 Accepted**, while malformed requests return **400 Bad Request** and server errors return **500 Internal Server Error**.
- The response contract is verified by stress tests in [`crates/ai-memory-mcp/tests/suite/stress_autoscope.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/tests/suite/stress_autoscope.rs).

## Frequently Asked Questions

### What happens if the ai-memory server is offline when a hook event is sent?

The client-side curl command in [`hooks/_lib.sh`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/_lib.sh) uses a 0.5-second timeout (`--max-time 0.5`) and silent mode (`-s`), which means failed connections or timeouts will fail silently without blocking the agent. The hook event is lost, but the agent continues execution uninterrupted, maintaining the fire-and-forget design philosophy.

### How does authentication work for hook events in ai-memory?

Authentication is handled by the `ai_memory_post_hook` function checking for the `AI_MEMORY_AUTH_TOKEN` environment variable first. If present, it adds `Authorization: Bearer <token>` to the request. If not, it falls back to checking the file returned by `ai_memory_auth_header_file` and includes those headers instead. If neither is available, the request proceeds without authentication headers, suitable for local development environments.

### Why does the server return 202 Accepted instead of 200 OK for hook events?

The **HTTP 202 Accepted** status indicates that the server has received the hook event and queued it for processing, but has not yet completed persisting the observation to the SQLite store. This status code accurately represents the asynchronous nature of the ingestion pipeline and confirms that the client can safely disconnect after the timeout without waiting for full disk write confirmation.

### What payload fields are required when sending hook events to ai-memory?

While the exact schema is validated by the server handler, the standard hook payload includes `event` (the lifecycle type), `session_id` (unique identifier), and `cwd` (current working directory). Query parameters appended to the URL include `agent` (identifier string) and `event` type. Missing required fields in the JSON body will result in a **400 Bad Request** response according to the server's validation logic.