Skip to main content

Alert rules

This page lists the alert rules that the chart ships in files/hephaestus-alerts.yml, and gives the reason for each threshold. The rule file is the home of each threshold, window, and summary. hephaestus-alerts.test.yml beside the rules in the repository contains a promtool unit test for each alert. The chart package does not contain it. Each test gives series that fire the alert and series that do not. CI runs these tests for each change to the rules.

Severity​

Each alert has one of three severity labels:

  • critical: data, isolation or the service is at risk now. Send these alerts to a person at once. These alerts are critical:
    • HephaestusPostgresVolumeFilling, HephaestusWalArchiveFailing and HephaestusBackupStale.
    • HephaestusDatabaseTransactionIdAgeHigh and HephaestusWebhookReceiverDown.
    • HephaestusAgentSandboxUnisolated.
    • HephaestusApiErrorBudgetBurnFast and HephaestusApiLatencyBudgetBurnFast.
  • warning: a fault needs work during working hours. All other alerts are warnings.
  • none: HephaestusWatchdog only. It fires while the monitoring works. Route it to an outside dead-man check.

Labels and exporters​

The rules read the exported Prometheus names, not the dotted Micrometer names. The rules of the group hephaestus read the application's metrics. Budget admission and committed agent-job outcomes can occur in different roles, so their rules aggregate all scraped roles. They need these target labels from the scrape configuration:

  • job="hephaestus" on each target of the runtime roles.
  • runtime_role, with the value server, worker or webhook.
  • A different instance on each target.

HephaestusMetricsUnavailable fires only for a target that Prometheus still discovers. When service discovery loses all targets of a role, no up series remains. To detect that, add a rule for each role that you run, for example absent(up{job="hephaestus",runtime_role="server"}).

The rules of the group hephaestus-kubernetes read cluster metrics. Each rule needs its exporter, scraped by the same Prometheus:

ExporterMetricsAlerts
kube-state-metricskube_cronjob_status_last_successful_time, kube_cronjob_created, kube_cronjob_labels, kube_deployment_status_replicas_available, kube_deployment_status_replicas_unavailable, kube_deployment_spec_replicas, kube_deployment_labelsHephaestusBackupStale, HephaestusWebhookReceiverDown, HephaestusWebhookReceiverUnready
The kubelet's /metrics endpoint, with a storage driver that reports volume statisticskubelet_volume_stats_available_bytes, kubelet_volume_stats_capacity_bytesHephaestusPostgresVolumeFilling, HephaestusJetStreamVolumeFilling
node-exporter on each nodenode_filesystem_avail_bytes, node_filesystem_size_bytes, node_filesystem_readonlyHephaestusNodeDiskFilling
cert-managercertmanager_certificate_expiration_timestamp_secondsHephaestusCertificateExpiring

The group hephaestus-pull-deploy reads the metrics of the pull-based deployment reconciler. node-exporter's textfile collector on each host that runs the reconciler exports them. Load this group only when Prometheus scrapes such a host: HephaestusDeployMetricsMissing fires when no host reports.

A rule whose exporter is not scraped has no series and never fires. Before you rely on a rule, query its metrics in Prometheus. The kube-state-metrics, kubelet and cert-manager rules have no series outside Kubernetes.

The webhook rules find the release's webhook Deployment, <release>-webhook, by the chart's labels app.kubernetes.io/name=hephaestus and app.kubernetes.io/component=webhook. kube-state-metrics exports these labels only when you allow them:

  • Start kube-state-metrics with --metric-labels-allowlist=deployments=[app.kubernetes.io/name,app.kubernetes.io/component],cronjobs=[app.kubernetes.io/name,app.kubernetes.io/component,app.kubernetes.io/instance].
  • With kube-prometheus-stack, set the value kube-state-metrics.metricLabelsAllowlist to ["deployments=[app.kubernetes.io/name,app.kubernetes.io/component]", "cronjobs=[app.kubernetes.io/name,app.kubernetes.io/component,app.kubernetes.io/instance]"].

Without these labels, the two webhook rules find no Deployment and never fire.

The other rules find the release's objects by the names that the chart gives them: data-<release>-postgresql-0, data-<release>-nats-0, and the CronJobs <release>-postgresql-backup-full and <release>-postgresql-backup-diff. Another application in the cluster can use the same names, for example another PostgreSQL chart. To limit these rules to the release, add a matcher for its namespace, for example namespace="hephaestus". Do the same for HephaestusCertificateExpiring, which covers every certificate that cert-manager manages in the cluster.

The chart shortens a name <release>-<component> above 52 characters, and then the name loses its suffix and gets a hash. Thus, the limit depends on the component:

ObjectShortened when the release name is longer than
The backup CronJobs (postgresql-backup)34 characters
The PostgreSQL volume (postgresql)41 characters
The NATS volume (nats)47 characters

HephaestusBackupStale then finds the backup CronJobs by their labels, which the cronjobs part of the allowlist above exports. For the volume rules, change their name patterns.

Alerts​

AlertSignalWhy this threshold
HephaestusMetricsUnavailableupSeveral failed scrapes in a row, not one missed scrape
HephaestusInboxRetentionFailingintegration_consumer_inbox_retention_failures_totalCleanup runs every 6 hours. More than one failure in 24 hours leaves old delivery keys in PostgreSQL. Ingestion continues, but the tables can grow until cleanup succeeds
HephaestusConsumerPoisonintegration_consumer_poison_totalEach poison event is a delivery that the application stopped retrying
HephaestusConsumerHandlerTimedOutintegration_message_process_duration_seconds_count{outcome="timed_out"}A handler ran longer than hephaestus.integration.consumer.handler-timeout and gave its event back to the stream
HephaestusStreamPollStalewebhook_stream_poll_age_secondsThree times the default 60-second monitor interval. NaN means a stream was never polled successfully, not an age of zero
HephaestusStreamPollMissingNo webhook_stream_poll_age_seconds from a scraped receiverThe stream monitor is gone, which a failed scrape does not show
HephaestusStreamBacklogwebhook_stream_consumer_pending: events that the broker did not deliver to a consumerUnder steady load, a few events wait between two fetches. A backlog above that level at each evaluation for 15 minutes shows consumers that fall behind or do not fetch. At its bound, the stream removes its oldest message, also when a consumer still needs it. The gauge counts an event once for each consumer that it matches
HephaestusWebhookRetentionBelowAgeBoundwebhook_stream_oldest_message_age_seconds under 90 % of webhook_stream_age_bound_seconds while webhook_stream_bytes_utilization is above 0.9A stream at its byte bound is normal. It matters only when that bound holds less than the age bound promises. A young stream does not fire
HephaestusWebhookStreamPastAgeBoundwebhook_stream_oldest_message_age_seconds above 110 % of webhook_stream_age_bound_secondsA stream holds deliveries longer than its setting allows, usually because startup withholds a shorter age. On Slack and Outline, this is personal content past its horizon
HephaestusEventsDelayedintegration_message_lag_seconds, per kindThe backlog alerts count events. This one measures the wait: more than 10 % of at least 20 events in 10 minutes wait over 5 minutes, for 15 minutes. A catch-up after a restart stays silent, and so does an instance with fewer events
HephaestusStreamConsumerStoppedwebhook_stream_consumer_pending above 0 for 6 hours without a pauseThe backlog alert needs more than 50 events, so it stays silent on a quiet stream. Events that outlive the stream's retention are lost
HephaestusWebhookPublishFailingwebhook_publish_total{outcome="failure"}GitHub redelivery covers a short stall of the broker, not a long one
HephaestusWebhookReceiverBusywebhook_rejected_total{reason="busy"}Large deliveries arrive faster than the receiver's memory holds them. A short burst recovers by itself
HephaestusWebhookRedeliveryNotRunninggithub_webhook_redelivery_totalRedelivery requests that GitHub refused or that could not read the App's deliveries. One refusal can be a race. Three in 30 minutes are not
HephaestusWebhookRedeliveryTruncatedgithub_webhook_redelivery_total{outcome="truncated"}One truncated run can be a burst. Three in two hours mean that failed events can age out unrecovered
HephaestusWebhookRedeliveriesRequestedgithub_webhook_redelivery_total{outcome="requested"} divided by webhook_publish_total{stream="github",outcome="success"}, over one hourThe server asks for a redelivery when GitHub recorded no successful delivery of an event. This includes a delivery that the receiver never saw, for example at an ingress timeout or while the receiver restarted. One event can be asked for up to five times. A ratio scales with traffic: more than 1 request for each 20 received deliveries is more than occasional network faults. A minimum of three requests keeps a single redelivery in a quiet hour quiet. A client error from the receiver, such as from a wrong webhook secret, gives no request and no count. The alert stays up for up to one hour after the requests stop. Only GitHub reports its delivery outcomes: GitLab, Slack and Outline have no such metric
HephaestusWebhookEventsUnrecoveredgithub_webhook_unrecoveredEach counted event failed all six delivery attempts
HephaestusBudgetExhaustedllm_budget_exhausted_totalA workspace crossed the instance-funded monthly cap
HephaestusBudgetBlockedllm_budget_blocked_totalWork refused at a cap: at admission, or as a model call of a running job (surface="model_call"). A single refusal is expected at a cap. Refusals that continue for 15 minutes need a decision on the budget, whichever surface refused them
HephaestusModelRefusalsReachCallersllm_pool_upstream_throttled_total less llm_pool_upstream_absorbed_total, per poolCallers wait out most refusals. Refusals that reach callers for 15 minutes mean a spent account, a pause longer than a call can wait, or an outage
HephaestusModelLimitAtFloorllm_pool_limit at 2 while llm_pool_limit_decreases_total or llm_pool_first_token_baseline_resets_total risesA pool that an admin capped at 2 does not fire it. A provider that refuses or queues even two calls for 30 minutes does
HephaestusAgentJobsFailingagent_job_total with failed or timed_outAll terminal failures, whatever their cause. Three in one hour point to the platform also on a small instance that runs a few reviews each day. Cancellations do not count. The alert stays up for up to one hour after the last failure
HephaestusConversationModelWaitLongllm_pool_permit_wait_seconds of interactive calls, per poolA reader waits for the answer. More than 10 % of at least 10 calls over 5 seconds in 15 minutes
HephaestusReviewModelWaitLongllm_pool_permit_wait_seconds of batch and background calls, per poolReview calls queue by design. Calls over two minutes hold a sandbox idle. More than a quarter of at least 20 calls in one hour, for 15 minutes
HephaestusModelPermitsExpiredllm_pool_permits_expired_total, per poolThree in one hour. A worker that stops with calls in flight leaves them to expire. The count rises when the pool next admits a call, so on an idle pool it comes late
HephaestusModelPermitRenewalFailingllm_pool_lease_renewal_failures_totalThree in 15 minutes. A worker renews each permit several times per lease, so one failure is a passing error
HephaestusPrivacyJobFailingprivacy_job_completed_total with failure or incomplete, per exported_jobTwice in 26 hours: an hourly sweep failed twice, or a daily job failed on two days. Erasure is a legal duty, so two are enough
HephaestusPrivacyJobNotRunningNo privacy_job_completed_total of account_erasure or export_retention in four hours, for 90 minutesBoth sweep each hour, so four hours are more than one missed run. The 90 minutes let a new install reach its first run
HephaestusResultsMarkedIncorrectpractice_result_marked_total{kind="incorrect"}, set less removed, over practice_result_recorded_total with met or not_met, in seven daysA wrong claim costs more trust than a missing one. More than 5 %, with at least 5 marks. The ratio compares mark events with recorded results: a mark on an older result also counts
HephaestusReviewSandboxFailingagent_job_infra_retry_requeued_total{cause="sandbox"}The sandbox could not start, initialize or finish, and the attempt runs again. Three in one hour point to the cluster: the sandbox namespace, its quota, the agent image or the nodes. The alert stays up for up to one hour after the last failure
HephaestusSandboxGatewayRefusingsandbox_gateway_refused_total, per reason, without attempt_endedThe sandbox gateway refused 60 requests for one reason in ten minutes. source_limit, principal_limit, unauthenticated, forbidden, oversized, body_budget and unknown_route each name what it refused. An attempt that ended gets attempt_ended, which is normal and does not count
HephaestusSandboxGatewayConnectionsHighsandbox_gateway_connections over sandbox_gateway_connections_limitA sandbox gateway held more than 80 percent of its connections for 5 minutes. At the limit, new model calls wait
HephaestusGitCacheNearlyFullgit_cache_bytes over git_cache_limit_bytesThe volume of Git mirrors, review checkouts and staged archives stayed above 90 percent for 10 minutes. A role evicts mirrors above 80 percent, so this means that what is left cannot go: running reviews, or one very large mirror. At its size limit the kubelet evicts the whole Pod
HephaestusGitMirrorsEvictedgit_mirror_evictions_totalFive kept mirrors were evicted in one hour. Each eviction costs a full fetch at the next capture, so the monitored repositories do not fit the volume
HephaestusAttemptKeysNotSyncingattempt_key_sync_failures_total, per taskA worker could not publish its key or load the other keys. It keeps the keys that it knows, so running attempts continue. A new worker's tokens may fail on the others
HephaestusReviewUnreachableagent_job_infra_retry_requeued_total with cause server_unreachable or provider_unreachableThe sandbox reached neither the server's gateway nor the model provider. The cause label tells which. Three in one hour point to the network policy or to the provider. The alert stays up for up to one hour after the last failure
HephaestusReviewServerErroragent_job_server_error_totalThe server answered a review's results with its own error. The review keeps the practices that it staged, and the rest end with EXECUTION_FAILED, with no retry. One is a defect in the server, so the alert fires on the first. The alert stays up for up to one hour after the last error
HephaestusPersonErasureInterruptedperson_data_erasure_failures_totalAn erasure stopped after its retry allowance, met a permanent store failure, or could not record its failure state. The counter has no person or request labels. Automatic retry alone does not increment it. The alert stays up for up to one hour after the last failure
HephaestusReviewPreparationFailingagent_job_infra_retry_requeued_total{cause="preparation"}An attempt failed before its sandbox started, for example because the reviewed source was not captured yet. A few retries are normal after a push. Retries that continue point to provider sync or the database
HephaestusFeedbackReadbackUnknownpractice_feedback_readback_total{outcome="unknown"}, over one dayHephaestus could not tell whether a copy reached the work, so the feedback stays Unconfirmed. The copy may be missing, and a retry could post it twice. Readback runs only when a write was in doubt, so one count already needs a look. A proved absence does not fire: there a resend is safe. The alert stays up for one day after the last count
HephaestusReviewStageSlowagent_job_stage_duration_seconds, by stageThree samples in six hours above 30 minutes for sandbox, or above 10 minutes for another stage. A stage that never ends has no sample: the attempt's deadline ends it, and HephaestusAgentJobsFailing reports that
HephaestusReviewWorkerLostagent_run_reaped_total{kind="review",outcome="attempts_exhausted"}Each count is a review that failed because the holder of each of its attempts stopped renewing its lease. Workers that stop that often are evicted, out of memory, or on lost nodes. The alert stays up for one hour after the count
HephaestusAgentRunReaperFailingagent_run_reaper_failure_totalThe reaper failed at least ten times in ten minutes, for ten minutes. It skips a run that it cannot take back until its next pass, so one broken run does not hold back the others. A run that fails on every pass stays leased and its review waits. The agent.run.reaper.failed event names the run
HephaestusAgentSandboxReaperFailingagent_sandbox_reaper_failure_totalThe sandbox reaper failed at least ten times in ten minutes, for ten minutes. A sandbox that it cannot delete keeps its Pod and its share of the sandbox quota, but its token no longer works. The event agent.sandbox.claim_delete_failed names the sandbox
HephaestusAgentSandboxDispatchFailingagent_sandbox_dispatch_failure_totalThe dispatch failed at least ten times in ten minutes, for ten minutes. No new sandbox starts, so queued reviews wait. The event agent.sandbox.dispatch.failed names the error
HephaestusAgentSandboxUnisolatedagent_sandbox_revoked_total{reason="unisolated"}A sandbox could still reach the Kubernetes API 10 seconds after it started. Its driver started none of its run's work and gave the run back, and the server took the sandbox back. The event agent.sandbox.taken_back with sandbox.reason unisolated names the sandbox
HephaestusReviewFeedbackUnconfirmedagent_job_composition_stopped_total with outcome unrecordedEach count is a review that closed as Partial because every attempt ended before it confirmed where its feedback went. The results stay.
HephaestusOutboxStalledaction_oldest_due_seconds, by kindAn action of the kind has been due for more than 15 minutes, for 10 minutes. No server replica carries it out, or an action that does not end holds its lane
HephaestusOutboxUncertainaction_ended_total{state="UNCERTAIN"}, by kindAn action ended uncertain in the last hour: its write may have reached the provider, and nothing confirms it. An uncertain email is never sent again. The alert stays up for one hour after the count
HephaestusAgentQueueStalledagent_queue_oldest_age_secondsA practice review usually runs for 10 to 16 minutes. A wait of several review durations shows that no worker claims work
HephaestusSandboxIsolationFailedsandbox_isolation_passed of a workerA later probe failed, and the worker starts no sandbox until a probe passes. The wait covers the first probe at the start
HephaestusWalArchiveFailingdatabase_wal_archive_failing, and database_wal_archive_sample_failures_totalPostgreSQL retries a failed segment about once each minute, so a short fault of the bucket clears itself. A longer failure stops point-in-time recovery after the last archived segment. A server that cannot read the archiver for the same time cannot show that the archive works, so the alert also fires then
HephaestusDatabaseConnectionTimeoutshikaricp_connections_timeout_total, by runtime_roleThe pool could not give a connection within its timeout, so the request or the job that asked failed. A single timeout is a failure that a user meets. The alert stays up for up to 15 minutes after the last timeout
HephaestusDatabasePoolSlowhikaricp_connections_acquire_seconds, by runtime_roleThe sizing target is an acquire p95 below 50 ms (Sizing). More than 5 % of the acquires of 30 minutes above 50 ms misses it. A process with fewer than 300 acquires in 30 minutes does not fire, so a few slow acquires during a database restart do not count
HephaestusTenancyViolationstenancy_violation_total, by table and modeEach count is a statement that read a workspace's table without binding the workspace. In throw mode the server refused it, so its request or job failed. One count is a defect, so the alert has no threshold
HephaestusBrowserFaultsRisingbrowser_faults_totalA page that fails for one reader repeats its failure. Twenty of one kind in one hour point to a fault that many readers meet, such as a release that broke a page. The alert stays up for up to one hour after the faults stop
HephaestusContainerOomKilledkube_pod_container_status_last_terminated_reason and kube_pod_container_status_restarts_total, or kube_pod_container_status_terminated_reason, with OOMKilledEach kill loses work in flight. It reads every namespace. On a shared cluster, add a namespace matcher. A sandbox Pod that the worker deletes quickly can leave no sample
HephaestusWebhookReceiverDownkube_deployment_status_replicas_available of the webhook Deployment, while its desired replicas are above 0No available receiver means that every provider delivery fails. The wait covers the start of a replacement pod. A Deployment scaled to 0 for maintenance does not fire. A receiver that is unavailable for a shorter time than the wait, for example for 16 seconds to 2 minutes, does not fire. HephaestusWebhookRedeliveriesRequested can then fire for the GitHub deliveries that it lost, when they reach its threshold. Deliveries of other providers have no such alert
HephaestusWebhookReceiverUnreadykube_deployment_status_replicas_unavailable of the webhook Deployment, while another replica is availableThe other replicas serve, so this is a warning. The wait is longer than the 10 minutes that a new pod waits for the schema
HephaestusBackupStaleThe newest successful full or differential backup of a release, or the creation time of CronJobs with no successful backupA differential backup runs each day. The hours above one day let a full backup run
HephaestusPostgresVolumeFillingFree space of the PostgreSQL volumeA failed WAL archive fills the volume. The warning gives time to repair the archive. The critical level comes shortly before PostgreSQL stops. The warning covers only the band above the critical level, so the two never fire together
HephaestusJetStreamVolumeFillingFree space of the JetStream volumeThe volume is max_file plus 25 %, so a broker at its storage budget leaves 20 % free. Below the threshold, something outside the budget fills the volume. A full file system wedges the broker
HephaestusNodeDiskFillingFree space of a writable node file system, not the volumes that the kubelet mounts for podsThe kubelet's image garbage collection starts at 85 % use and frees space to 80 %. When the space stays low, eviction comes next. The volume rules cover pod volumes
HephaestusDeployReconcileFailinghephaestus_deploy_reconcile_successThe timer runs the reconciler each minute. A run that fails for 15 minutes does not clear itself. For example, a chart that refuses the host's values fails each run in the same way. The host then keeps its old release
HephaestusDeployReconcileStalehephaestus_deploy_reconcile_timestamp_secondsThe reconciler writes the timestamp when a run ends, and one apply may take up to the unit's 30-minute start timeout. No finished run for 40 minutes means that the timer or the unit stopped
HephaestusDeployMetricsMissingNo hephaestus_deploy_reconcile_timestamp_secondsThe textfile collector, the metrics file or the reconciler is gone, which no other rule of the group shows
HephaestusDeployToolingPendinghephaestus_deploy_tooling_pendingThe host runs tooling that no apply recorded. The next promotion of the release that the host runs ends it. An hour leaves time for that promotion
HephaestusCertificateExpiringcertmanager_certificate_expiration_timestamp_secondscert-manager renews a certificate when one third of its lifetime remains: 15 days of a 45-day certificate. For a certificate with a lifetime of less than 21 days, lower the threshold. cert-manager reports 0 for a certificate that it did not issue yet. Therefore, the alert also fires when a first issuance takes longer than its wait
HephaestusFeedbackSendRetryingpractice_feedback_send_attempts at half practice_feedback_send_readback_limit for one minuteA send repeatedly needs recovery or readback. A manual retry is a new send with its own count
HephaestusFeedbackSendMetricsUnavailableAn absent or NaN attempt gauge for five minutes on a scraped serverThe database read did not succeed, so the retry alert has no reliable input

The WAL archive rule reads the server's WAL archive metrics. It also applies to an external PostgreSQL that archives its WAL. No rule reads the age of the last archived segment. PostgreSQL switches a segment only after WAL activity, so an idle database archives nothing, and an age rule would fire falsely. An archive command that hangs without a failure stops the next backup, because pgBackRest waits for the backup's WAL in the archive. HephaestusBackupStale and HephaestusPostgresVolumeFilling then fire.

Load the alert rules gives the setup, and Service level objectives gives the objectives.