Skip to content

Operational Alerts

Netclaw emits operational alerts when something happens that you or your ops tooling should know about: a provider going down, a channel disconnecting, the daemon crashing. Netclaw delivers these as outbound webhook POSTs to URLs you configure, with native Slack Block Kit formatting or a generic JSON envelope.

  1. Run netclaw config → Telemetry & Alerting.
  2. Add a webhook URL and choose Slack or Generic format.
  3. Save. The daemon restarts itself on the config change, and you’ll get a daemon.started alert confirming delivery works. Repeats of the same type and source inside the 300-second deduplication window collapse into one alert.

That’s it for most installs. For scripted deployments or headless servers, see Manual configuration below.

Type StringSeverityWhat Happened
daemon.startedInfoDaemon finished startup. Includes PID.
daemon.stoppingInfoGraceful shutdown initiated. Includes reason.
daemon.crashingCriticalUnhandled exception - the process is going down.
update.availableInfoA newer netclaw binary exists in the release feed.
provider.failoverWarningPrimary LLM provider failed; traffic moved to fallback.
provider.unreachableCriticalAll configured LLM providers are unavailable.
channel.disconnectedWarningSlack, Discord, or Mattermost connection lost.
channel.reconnectedInfoThe Slack connection came back after a drop. Slack only.
provider.auth.expiredWarningA provider’s OAuth credentials couldn’t be refreshed. The summary says to run netclaw provider, or netclaw provider add <name> <type> --auth oauth-device to sign in again. A rejected OpenAI refresh raises it too.
mcp.auth.expiredWarningMCP OAuth token expired and refresh was rejected.
mcp.server.disconnectedWarningConnection to an MCP server dropped.
mcp.server.reconnectedInfoMCP server reconnected successfully after a previous disconnect.
webhook.receivedInfoA valid inbound webhook delivery was accepted and queued.
webhook.route.invalidWarningA webhook route file is missing or invalid.
reminder.execution.failedWarningA scheduled reminder failed to execute. A failed reply post on a current_session reminder is reminder.delivery.failed instead.
reminder.delivery.failedWarningA reminder ran and its session admitted it, but the reply post failed for good. Not retried. Context: reminderId, sessionId, channel, failureKind, error.
reminder.status.query_failedWarningThe 12-hour sweep or startup reconciliation couldn’t read the status of a past one-shot reminder. It is left as is and retried on the next pass.
reminder.tombstone.invalidWarningThe end record of a finished one-shot reminder under schedules/reminders/ended/ can’t be read, so its history is unreadable and it doesn’t run.
reminder.schedule.failedWarningA reminder couldn’t be scheduled for its next run. Repeated failures auto-disable it.
reminder.settlement.failedWarningA reminder ran, but recording its outcome failed.
reminder.tool_deniedWarningA reminder ran, but one of its tool calls was denied (policy, a hard deny, or an approval with nobody to answer). The run is recorded as denied. One alert per reminder per distinct reason, until a run succeeds.
reminder.auto_disabledCriticalReminder disabled after repeated consecutive failures.
reminder.schema.invalid_droppedWarningInvalid reminder definitions were dropped at startup.
reminder.schema.legacy_rejectedWarningLegacy reminder definitions missing trust fields were rejected at startup.
background-job.schema.legacy_rejectedWarningLegacy background job definitions missing trust fields were rejected at startup.
memory.embedding_model.unavailableWarningThe local embedding model couldn’t be downloaded or loaded. Recall falls back to lexical search. Fires once per daemon run.
memory.relevance_model.unavailableWarningThe relevance-gate model couldn’t be downloaded or loaded, so recall isn’t filtered by the cross-encoder. Fires once per daemon run.

All configured destinations receive all alert types - there’s no per-destination filtering.

Use netclaw config → Telemetry & Alerting to add, edit, or remove webhook targets at any time. In Docker you can set the same fields as environment variables, which need a docker restart to take effect:

Terminal window
-e NETCLAW_Notifications__Webhooks__0__Url=https://alerts.example.com/netclaw \
-e NETCLAW_Notifications__Webhooks__0__Name=ops \
-e NETCLAW_Notifications__Webhooks__0__Format=Generic \
-e NETCLAW_Notifications__Webhooks__0__Headers__X-Api-Key=your-key

For scripted or headless installs, add outbound webhook targets directly in ~/.netclaw/config/netclaw.json. Merge the Notifications block into your existing config if one is already there.

{
"Notifications": {
"Webhooks": [
{
"Url": "https://hooks.slack.com/services/T00/B00/xxx",
"Name": "ops-slack",
"Format": "Slack"
},
{
"Url": "https://your-monitoring.example.com/alerts",
"Name": "pagerduty-relay",
"Format": "Generic",
"Headers": {
"Authorization": "Bearer your-token"
}
}
],
"DeduplicationWindowSeconds": 300,
"MaxRetries": 2,
"TimeoutSeconds": 10
}
}

The daemon restarts itself after a valid edit to netclaw.json.

Each webhook target has:

FieldRequiredDescription
UrlYesEndpoint to POST alerts to
NameNoHuman-readable label for logs. Logs show (unnamed webhook) if omitted, never the URL, because URLs can hold secrets
FormatNoSlack or Generic (default). URLs containing hooks.slack.com auto-detect as Slack.
HeadersNoCustom HTTP headers (auth tokens, API keys)

Top-level notification settings:

FieldDefaultDescription
DeduplicationWindowSeconds300Suppress duplicate alerts within this window (details below)
MaxRetries2Retry attempts for failed deliveries
TimeoutSeconds10HTTP timeout per delivery attempt

Every alert arrives as a JSON POST with Content-Type: application/json and a User-Agent of Netclaw/<version> (+https://netclaw.dev; sha=<commit>) and X-Netclaw-Component: webhook, plus any custom headers you set. This is the daemon.stopping alert a Docker container sent on docker stop:

{
"alertId": "ad0d316727bf",
"type": "daemon.stopping",
"severity": "info",
"summary": "Netclaw daemon stopping: daemon-stop",
"timestamp": "2026-10-07T15:34:12.323848+00:00",
"source": "netclaw",
"hostname": "a9fdada82d02",
"service": {
"name": "netclawd",
"instanceId": "a9fdada82d02:33",
"version": "0.28.2"
},
"context": {
"pid": "33",
"reason": "daemon-stop",
"drainOutcome": "drained",
"activeSessions": "0",
"drainedSessions": "0",
"timedOutSessions": "0",
"restartReminders": "0"
}
}

alertId is a short hex string, and every context value is a string. When a session doesn’t acknowledge a stop or restart drain, daemon.stopping also carries timedOutSessionIds, a comma-separated list of those session IDs, and drainOutcome is timeout. The type and severity values come from the table above, with severity in lowercase.

The service object is the service identity sourced from the OpenTelemetry environment variables. instanceId always has a value (it defaults to {hostname}:{pid}), and namespace appears only when you set it. When several netclaw instances post to the same endpoint, that’s how you tell which one fired. The context object varies by alert type, carrying whatever extra detail is relevant to that event.

When Format is Slack, netclaw sends Block Kit messages with severity-colored headers and structured fields:

  • Critical alerts get :red_circle:
  • Warnings get :warning:
  • Info events get :information_source:

Each message includes a text fallback for notification previews. The blocks payload has the header, summary, metadata (severity, type, timestamp, hostname), and alert-specific context.

Deduplication - Identical alerts (same type and source) within the deduplication window are suppressed. This prevents notification storms when a provider flaps or a connection drops and reconnects rapidly.

Retries with backoff - Failed deliveries retry with exponential backoff and jitter. With the default MaxRetries of 2:

AttemptBase DelayRange (with ±25% jitter)
12s1.5s – 2.5s
24s3s – 5s

Backoff caps at 30 seconds for higher retry counts. Netclaw doesn’t retry client errors (4xx) - only server errors (5xx) and timeouts trigger retries.

Bounded queue - Netclaw buffers alerts in a 256-slot in-memory queue. When the queue fills, the oldest buffered alert is dropped to make room for the new one, so the daemon always accepts new alerts without blocking.