Skip to content

operator mode: add event-driven triggers - #2133

Merged
arikalon1 merged 10 commits into
masterfrom
claude/holmesgpt-operator-mode-A2TjD
Jun 16, 2026
Merged

arikalon1 merged 10 commits into
masterfrom
claude/holmesgpt-operator-mode-A2TjD

Conversation

@aantn

@aantn aantn commented Jun 5, 2026 •

Copy link
Copy Markdown
Collaborator

operator mode improvements

Summary by CodeRabbit

  • New Features

    • Triggered Health Checks (alpha): automatic, per-deployment rollout health checks (image/template changes) with configurable delay and cooldown; optional inline one-time checks for CI/CD gating.
  • Documentation

    • Comprehensive Triggered Health Checks guide added and linked from the operator overview.
    • Deployment verification docs rewritten to compare automatic triggers vs. inline gating and include timing/usage tips.

claude added 3 commits June 5, 2026 07:29
Diagnose why operator mode feels half-baked (only create-once and cron
triggering), map the gap to real user flows (deploy verification, incident
triage, remediation), and propose a phased plan headlined by a
HealthCheckTrigger CRD for event-driven investigations.

Signed-off-by: Claude <noreply@anthropic.com>
…heck

Mirror ScheduledHealthCheck (HealthCheckSpec + trigger source, spawns
HealthCheck children) rather than a reference to another CRD, since a
HealthCheck is an executed instance, not a reusable template.

Signed-off-by: Claude <noreply@anthropic.com>
Add an event-driven, self-contained TriggeredHealthCheck CRD (the sibling of
ScheduledHealthCheck) that fires an investigation whenever a matching Deployment
rolls out a new pod template.

- models: TriggeredHealthCheckSpec/Status, DeploymentRolloutTrigger, conditions
- trigger_executor: in-memory rollout detection (no kopf annotations on user
  Deployments), label-selector matching, query-token rendering, rollout settle
  wait, HealthCheck spawn with ownerReferences, status recording with per-
  Deployment cooldown and history
- handlers/triggeredhealthcheck: validate + Ready condition; low-level Deployment
  event watcher that fans rollouts out to matching triggers
- CRD manifest, RBAC (triggeredhealthchecks + deployments watch)
- component tests for helpers, rollout detection, cooldown, and spawn path
- docs: new Triggered Health Checks page, nav, index, deployment-verification

Reuses the existing HealthCheck execution/history/notification path; each fire
spawns a HealthCheck child that becomes the execution record.

Signed-off-by: Claude <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@github-actions

github-actions Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

⚠️ 1 older run truncated

Older runs were omitted to stay under GitHub's 64KB comment size limit.


✅ Results of HolmesGPT evals

Automatically triggered by commit a58e825 on branch claude/holmesgpt-operator-mode-A2TjD

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 14/14 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Max input Output Max output Cached Non-cached Reasoning Compactions Denied commands Src
✅ 09_crashpod 31.4s 4 8 $0.2109 69,982 68,324 19,551 1,658 694 47,961 20,363 100 — — src
✅ 101_loki_historical_logs_pod_deleted 45.2s 4 9 $0.2357 69,878 67,313 19,683 2,565 849 46,821 20,492 404 — — src
✅ 112_find_pvcs_by_uuid 15.1s 2 2 $0.1479 32,058 31,167 16,845 891 546 14,319 16,848 163 — — src
✅ 12_job_crashing 37.1s 5 10 $0.2462 93,303 91,337 20,768 1,966 518 68,438 22,899 193 — — src
✅ 176_network_policy_blocking_traffic_no_skills 30.6s 4 10 $0.2115 67,781 66,080 19,581 1,701 515 45,457 20,623 263 — — src
✅ 227_count_configmaps_per_namespace[0] 15.4s 3 6 $0.1490 46,347 45,644 16,659 703 437 28,981 16,663 29 — — src
✅ 243_pod_names_contain_service 30.3s 3 7 $0.1885 49,429 47,736 18,034 1,693 655 29,233 18,503 284 — — src
✅ 24_misconfigured_pvc 31.8s 5 10 $0.2220 87,046 85,359 19,000 1,687 594 64,649 20,710 44 — — src
✅ 254_elasticsearch_dr_test_log_check 64.6s 9 14 $0.3120 129,081 125,011 19,536 4,070 954 103,495 21,516 244 — — src
✅ 259_wrong_cluster_logs_confusion 66.5s 8 11 $0.2799 110,695 106,735 17,027 3,960 1,516 88,779 17,956 891 — — src
✅ 260_global_es_remote_cluster_logs 62.7s 5 8 $0.2661 72,983 68,793 17,110 4,190 1,841 50,703 18,090 990 — — src
✅ 43_current_datetime_from_prompt 4.6s 1 — $0.1017 14,428 14,306 14,306 122 122 0 14,306 78 — — src
✅ 51_logs_summarize_errors 20.5s 3 2 $0.1575 47,123 46,306 17,323 817 418 28,979 17,327 33 — — src
✅ 61_exact_match_counting 8.4s 2 1 $0.1146 29,150 28,929 14,643 221 152 14,283 14,646 34 — — src
Total 33.2s avg 4.1 avg 7.5 avg $2.8434 919,284 893,040 20,768 26,244 1,841 632,098 260,942 3,750 — —
Benchmark Comparison Details

Master baseline: latest master-* experiment (post-merge regression eval)
Status: 14 test/model combinations loaded

Benchmark baseline: latest ci-benchmark experiment on master
Status: 17 test/model combinations loaded

Time comparison (seconds):

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 31.4s 28.6s ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 45.2s 57.6s ↓22% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 15.1s 14.4s ±0% — —
12_job_crashing (opus-4.6) 📄 37.1s 34.8s ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 30.6s 33.5s ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 15.4s 16.2s ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 30.3s 30.1s ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 31.8s 32.9s ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 64.6s 66.3s ±0% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 66.5s 74.3s ↓10% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 62.7s 54.3s ↑15% — —
43_current_datetime_from_prompt (opus-4.6) 📄 4.6s 3.7s ↑23% — —
51_logs_summarize_errors (opus-4.6) 📄 20.5s 18.7s ±0% — —
61_exact_match_counting (opus-4.6) 📄 8.4s 7.8s ±0% — —
Total (all, n=14) 33.2s 33.8s — — —
Comparable (m=14, b=0) 33.2s 33.8s ±0% — —

Cost comparison:

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 $0.2109 $0.2078 ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 $0.2357 $0.2928 ↓20% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 $0.1479 $0.1565 ±0% — —
12_job_crashing (opus-4.6) 📄 $0.2462 $0.2440 ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 $0.2115 $0.2340 ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 $0.1490 $0.1485 ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 $0.1885 $0.1877 ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 $0.2220 $0.2254 ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 $0.3120 $0.2901 ±0% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 $0.2799 $0.3142 ↓11% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 $0.2661 $0.2418 ↑10% — —
43_current_datetime_from_prompt (opus-4.6) 📄 $0.1017 $0.1017 ±0% — —
51_logs_summarize_errors (opus-4.6) 📄 $0.1575 $0.1571 ±0% — —
61_exact_match_counting (opus-4.6) 📄 $0.1146 $0.1146 ±0% — —
Total (all, n=14) $0.2031 $0.2083 — — —
Comparable (m=14, b=0) $0.2031 $0.2083 ±0% — —

Total tokens comparison:

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 69,982 69,643 ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 69,878 112,358 ↓38% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 32,058 33,614 ±0% — —
12_job_crashing (opus-4.6) 📄 93,303 95,498 ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 67,781 71,617 ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 46,347 46,324 ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 49,429 49,466 ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 87,046 87,845 ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 129,081 133,188 ±0% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 110,695 118,418 ±0% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 72,983 72,135 ±0% — —
43_current_datetime_from_prompt (opus-4.6) 📄 14,428 14,429 ±0% — —
51_logs_summarize_errors (opus-4.6) 📄 47,123 47,096 ±0% — —
61_exact_match_counting (opus-4.6) 📄 29,150 29,149 ±0% — —
Total (all, n=14) 65,663 70,056 — — —
Comparable (m=14, b=0) 65,663 70,056 ±0% — —

Cached tokens comparison:

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 47,961 47,667 ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 46,821 85,705 ↓45% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 14,319 14,319 ±0% — —
12_job_crashing (opus-4.6) 📄 68,438 71,522 ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 45,457 46,821 ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 28,981 28,979 ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 29,233 29,245 ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 64,649 65,561 ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 103,495 111,091 ±0% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 88,779 93,558 ±0% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 50,703 51,153 ±0% — —
43_current_datetime_from_prompt (opus-4.6) 📄 — — — — —
51_logs_summarize_errors (opus-4.6) 📄 28,979 28,923 ±0% — —
61_exact_match_counting (opus-4.6) 📄 14,283 14,283 ±0% — —
Total (all, n=14) 45,150 49,202 — — —
Comparable (m=13, b=0) 48,623 52,987 ±0% — —

Turns comparison:

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 4 4 ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 4 6 ↓33% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 2 2 ±0% — —
12_job_crashing (opus-4.6) 📄 5 5 ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 4 4 ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 3 3 ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 3 3 ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 5 5 ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 9 10 ↓10% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 8 8 ±0% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 5 5 ±0% — —
43_current_datetime_from_prompt (opus-4.6) 📄 1 1 ±0% — —
51_logs_summarize_errors (opus-4.6) 📄 3 3 ±0% — —
61_exact_match_counting (opus-4.6) 📄 2 2 ±0% — —
Total (all, n=14) 4.1 4.4 — — —
Comparable (m=14, b=0) 4.1 4.4 ±0% — —

Tool calls comparison:

Test case This branch master (4h ago) Δ vs master benchmark (2d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 8 8 ±0% — —
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 9 12 ↓25% — —
112_find_pvcs_by_uuid (opus-4.6) 📄 2 2 ±0% — —
12_job_crashing (opus-4.6) 📄 10 11 ±0% — —
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 10 11 ±0% — —
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 6 6 ±0% — —
243_pod_names_contain_service (opus-4.6) 📄 7 7 ±0% — —
24_misconfigured_pvc (opus-4.6) 📄 10 11 ±0% — —
254_elasticsearch_dr_test_log_check (opus-4.6) 📄 14 13 ±0% — —
259_wrong_cluster_logs_confusion (opus-4.6) 📄 11 12 ±0% — —
260_global_es_remote_cluster_logs (opus-4.6) 📄 8 8 ±0% — —
43_current_datetime_from_prompt (opus-4.6) 📄 — — — — —
51_logs_summarize_errors (opus-4.6) 📄 2 2 ±0% — —
61_exact_match_counting (opus-4.6) 📄 1 1 ±0% — —
Total (all, n=14) 7.0 8.0 — — —
Comparable (m=13, b=0) 7.5 8.0 ±0% — —

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
📖 Legend
Icon Meaning
✅ The test was successful
➖ The test was skipped
⚠️ The test failed but is known to be flaky or known to fail
🚧 The test had a setup failure (not a code regression)
🔧 The test failed due to mock data issues (not a code regression)
🚫 The test was throttled by API rate limits/overload
❌ The test failed and should be fixed before merging the PR
🔄 Re-run evals manually

⚠️ Warning: /eval comments always run using the workflow from master, not from this PR branch. If you modified the GitHub Action (e.g., added secrets or env vars), those changes won't take effect.

To test workflow changes, use the GitHub CLI or Actions UI instead:

gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/holmesgpt-operator-mode-A2TjD -f markers=regression -f filter=

Option 1: Comment on this PR with /eval:

/eval
tags: regression

Or with more options (one per line):

/eval
model: gpt-4o
tags: regression
id: 09_crashpod
iterations: 5

Run evals on a different branch (e.g., master) for comparison:

/eval
branch: master
tags: regression
Option Description
model Model(s) to test (default: same as automatic runs)
tags Pytest tags / markers (no default - runs all tests!)
id Eval ID / pytest -k filter (use /list to see valid eval names)
iterations Number of runs, max 10
branch Run evals on a different branch (for cross-branch comparison)

Quick re-run: Use /rerun to re-run the most recent /eval on this PR with the same parameters.

Option 2: Trigger via GitHub Actions UI → "Run workflow"

Option 3: Add PR labels to include extra evals (applies to both automatic runs and /eval comments):

Label Effect
evals-tag-<name> Run tests with tag <name> alongside regression
evals-id-<name> Run a specific eval by test ID
evals-model-<name> Override the model (use model list name, e.g. sonnet-4.5)

Examples: evals-tag-easy, evals-id-09_crashpod, evals-model-sonnet-4.5

🏷️ Valid tags

benchmark, chain-of-causation, compaction, confluence, context_window, conversation_worker, coralogix, counting, database, datadog, datetime, db-connectors, easy, elasticsearch, embeds, fast, frontend, grafana, hard, images, integration, kafka, kubernetes, leaked-information, logs, loki, manual, mcp, medium, metrics, multi-cluster, network, newrelic, no-cicd, numerical, one-test, port-forward, prometheus, question-answer, regression, skills, slackbot, storage, token-limit, toolset-limitation, traces, transparency, victorialogs

🤖 Valid models

deepseek-chat, deepseek-r1-reasoner, deepseek-reasoner, deepseek-v3.2-chat, fable-5, gemini-3-flash-preview, gemini-3-pro-preview, gemini-3.1-pro-preview, gpt-4.1, gpt-5.2-high-reasoning, gpt-5.3-codex, gpt-5.4, gpt-5.5, haiku-4.5, kimi-2.5, kimi-2.5-openrouter, opus-4.5, opus-4.6, opus-4.7, opus-4.8, qwen-next-80B-instruct, qwen-next-80B-thinking, sonnet-4.5, sonnet-4.6


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/holmesgpt-operator-mode-A2TjD -f markers=regression -f filter=

@github-actions

github-actions Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker images ready for 2d0d02a73 (built in 5m 54s)

⚠️ Warning: does not support ARM (ARM images are built on release only - not on every PR)

Use these tags to pull the images for testing.

📋 Copy commands

⚠️ Temporary images are deleted after 30 days. Copy to a permanent registry before using them:

gcloud auth configure-docker us-central1-docker.pkg.dev
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes:2d0d02a73
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes:2d0d02a73 me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:2d0d02a73
docker push me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:2d0d02a73
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes-operator:2d0d02a73
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes-operator:2d0d02a73 me-west1-docker.pkg.dev/robusta-development/development/holmes-operator-dev:2d0d02a73
docker push me-west1-docker.pkg.dev/robusta-development/development/holmes-operator-dev:2d0d02a73

Patch Helm values in one line (choose the chart you use):

HolmesGPT chart:

helm upgrade --install holmesgpt ./helm/holmes \
  --set registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set image=holmes-dev:2d0d02a73 \
  --set operator.registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set operator.image=holmes-operator-dev:2d0d02a73

Robusta wrapper chart:

helm upgrade --install robusta robusta/robusta \
  --reuse-values \
  --set holmes.registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set holmes.image=holmes-dev:2d0d02a73 \
  --set holmes.operator.registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set holmes.operator.image=holmes-operator-dev:2d0d02a73

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an alpha TriggeredHealthCheck CRD and operator implementation that watches Deployments for pod-template rollouts, debounces/optionally delays triggers, creates owned HealthCheck resources with templated queries, records trigger history/cooldowns in status, updates RBAC, and adds docs and component tests.

Changes

TriggeredHealthCheck Feature Implementation

Layer / File(s) Summary
Design and user documentation
docs/operator/triggered-health-checks.md, docs/operator/.nav.yml, docs/operator/deployment-verification.md, docs/operator/index.md
User-facing docs introduce TriggeredHealthCheck (alpha) with examples, templating tokens, spec reference (enabled, deploymentRollout.selector.matchLabels, delaySeconds, cooldownSeconds, timeout, mode, model, destinations), timing semantics, operational notes, and navigation/index updates.
CRD schema and Pydantic models
helm/holmes/crds/triggeredhealthcheck.yaml, holmes_operator/models.py
New namespaced CRD triggeredhealthchecks.holmesgpt.dev with spec (required deploymentRollout and query, defaults/enums) and status (lastTriggerTime/deployment/count, cooldowns, pending queue, history, conditions) plus corresponding Pydantic models and condition enum.
Rollout detection and trigger executor
holmes_operator/trigger_executor.py
In-memory per-deployment baseline cache for pod-template/image detection, selector matching, query template rendering, cooldown checks, compute_fire_at/due_pending helpers, add_pending/remove_pending with status patch retry, HealthCheck name/object builder, spawn_check creation via k8s API, and record_trigger status updates with history truncation.
Kopf handlers, operator wiring, and RBAC
holmes_operator/handlers/triggeredhealthcheck.py, holmes_operator/operator.py, helm/holmes/templates/operator-rbac.yaml
Kopf create/update handlers validate spec and set READY/TRIGGER_FAILED; Deployment event handler detects rollouts, filters enabled triggers by selector/cooldown, schedules or spawns checks; periodic timer claims due pending entries; operator import registers handlers; RBAC extended for TriggeredHealthCheck CRD and Deployment watch/read.
Component tests
tests/holmes_operator/test_triggeredhealthcheck_component.py
Pytest component suite covering selector matching, image extraction, query rendering, rollout detection and forget behavior, cooldown scenarios, pending queue debounce/removal, compute_fire_at, and spawn_check end-to-end assertions for HealthCheck creation and status patches.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • arikalon1
  • moshemorad
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'operator mode: add event-driven triggers' clearly and specifically describes the main change: adding event-driven trigger functionality to the operator mode. It accurately reflects the core purpose of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify

netlify Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit a58e825
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6a31410cb796f60008b131cc
😎 Deploy Preview https://deploy-preview-2133--holmes-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
helm/holmes/crds/triggeredhealthcheck.yaml (1)

70-76: 💤 Low value

Consider quoting the default value for consistency.

The default: monitor at line 73 is unquoted, while the enum values at lines 75-76 (alert, monitor) are quoted strings. For consistency and clarity, consider quoting the default as default: "monitor".

✨ Proposed fix for consistency
                 mode:
                   type: string
                   description: "Execution mode: 'alert' sends notifications on failure, 'monitor' logs only"
-                  default: monitor
+                  default: "monitor"
                   enum:
                     - alert
                     - monitor
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@helm/holmes/crds/triggeredhealthcheck.yaml` around lines 70 - 76, The default
value for the CRD field "mode" is unquoted while the enum entries are quoted;
update the default under the "mode" schema (field name: mode, property: default)
to use a quoted string (default: "monitor") so it matches the quoted enum values
('alert', 'monitor') for consistency and clarity.
holmes_operator/handlers/triggeredhealthcheck.py (1)

180-236: ⚡ Quick win

Consider optimistic concurrency for condition updates.

The condition update performs a read (line 198-205) and patch (line 225-233) without including resourceVersion in the patch body. If another process updates the resource between the read and patch, this update may overwrite those changes.

While less critical than history/cooldown updates (which use retry logic in record_trigger), condition updates could still race. Consider either:

  • Including resourceVersion from the read (line 199) in the patch body's metadata, similar to lines 399-411 in trigger_executor.py, or
  • Adding retry-on-409 logic like _patch_status_with_retry.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes_operator/handlers/triggeredhealthcheck.py` around lines 180 - 236, The
set_triggeredhealthcheck_condition function reads the resource and patches its
status without optimistic-concurrency control; fetch
resource["metadata"]["resourceVersion"] after the get_namespaced_custom_object
call and include it as metadata.resourceVersion in the patch body passed to
context.k8s_api.patch_namespaced_custom_object_status, or alternatively wrap the
patch in retry-on-409 logic similar to _patch_status_with_retry so concurrent
updates don't get lost; update the call site that builds body={"status":
{"conditions": conditions}} inside set_triggeredhealthcheck_condition
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/operator/triggered-health-checks.md`:
- Line 30: Replace the fenced code blocks (the triple-backtick markers like
```yaml and closing ```) with indented code blocks by removing the ``` markers
and prefixing each line of the code block with four spaces (do this for both
occurrences where fenced blocks exist), or alternatively update the project's
markdownlint configuration to allow fenced code blocks (MD046) if fencing was
intentional; target the code fence markers (``` and ```yaml) to locate the
blocks to change.

In `@holmes_operator/trigger_executor.py`:
- Around line 270-332: The code leaves a cooldown unrecorded if record_trigger
fails after the HealthCheck is created; modify settle_and_spawn so the cooldown
is reliably recorded: call and await record_trigger(check_name, namespace,
deployment, old_image, new_image, api=k8s_api) before invoking
k8s_api.create_namespaced_custom_object (or, alternatively, add an idempotency
check using k8s_api.get_namespaced_custom_object to detect an existing
HealthCheck with the same labels generated by
generate_check_name/build_healthcheck_object and skip creation), and if you keep
creation-first, implement a compensation path that deletes the newly created
HealthCheck on record_trigger failure; reference functions/methods:
settle_and_spawn, record_trigger, create_namespaced_custom_object,
build_healthcheck_object, generate_check_name.

---

Nitpick comments:
In `@helm/holmes/crds/triggeredhealthcheck.yaml`:
- Around line 70-76: The default value for the CRD field "mode" is unquoted
while the enum entries are quoted; update the default under the "mode" schema
(field name: mode, property: default) to use a quoted string (default:
"monitor") so it matches the quoted enum values ('alert', 'monitor') for
consistency and clarity.

In `@holmes_operator/handlers/triggeredhealthcheck.py`:
- Around line 180-236: The set_triggeredhealthcheck_condition function reads the
resource and patches its status without optimistic-concurrency control; fetch
resource["metadata"]["resourceVersion"] after the get_namespaced_custom_object
call and include it as metadata.resourceVersion in the patch body passed to
context.k8s_api.patch_namespaced_custom_object_status, or alternatively wrap the
patch in retry-on-409 logic similar to _patch_status_with_retry so concurrent
updates don't get lost; update the call site that builds body={"status":
{"conditions": conditions}} inside set_triggeredhealthcheck_condition
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97259fbc-226b-4e18-aeaf-b44374930782

📥 Commits

Reviewing files that changed from the base of the PR and between 4d0a70f and e91f809.

📒 Files selected for processing (12)
  • docs/design/2026-06-05_operator-mode-improvements.md
  • docs/operator/.nav.yml
  • docs/operator/deployment-verification.md
  • docs/operator/index.md
  • docs/operator/triggered-health-checks.md
  • helm/holmes/crds/triggeredhealthcheck.yaml
  • helm/holmes/templates/operator-rbac.yaml
  • holmes_operator/handlers/triggeredhealthcheck.py
  • holmes_operator/models.py
  • holmes_operator/operator.py
  • holmes_operator/trigger_executor.py
  • tests/holmes_operator/test_triggeredhealthcheck_component.py

Comment thread docs/operator/triggered-health-checks.md
Comment thread holmes_operator/trigger_executor.py Outdated
claude added 2 commits June 5, 2026 09:12
…fe scheduling

Add a deliberate post-rollout delay (delaySeconds), distinct from settleTimeout:
settleTimeout waits for the rollout to become available; delaySeconds waits a
chosen period (minutes to days) so slow-burn regressions (leaks, pool exhaustion)
can surface before Holmes evaluates.

Delayed fires are persisted in status.pending and reconciled by a kopf timer, so
they survive operator restarts (verified end-to-end: a pending fire ran correctly
after killing and restarting the operator mid-delay). Pending entries are claimed
before spawning (no double-run) and debounced per Deployment (a newer rollout
replaces a still-pending one).

- models: delaySeconds field, PendingCheck, status.pending
- trigger_executor: compute_fire_at, due_pending, add_pending (debounced),
  remove_pending
- handler: delay branch in the rollout watcher + on.timer reconciler
- CRD: delaySeconds spec field, status.pending schema
- tests: pending-queue add/remove/due/debounce

Validated end-to-end against a real k3s cluster with the real Holmes API driving
an LLM investigation (kubectl queries -> pass/fail verdict in the HealthCheck).

Signed-off-by: Claude <noreply@anthropic.com>
Run rollout checks 5 minutes after the rollout by default so transient
post-deploy issues have time to surface. Set delaySeconds: 0 to check as soon
as the rollout settles.

Signed-off-by: Claude <noreply@anthropic.com>
@aantn aantn changed the title docs(operator): design proposal for event-driven triggers operator mode: add event-driven triggers Jun 7, 2026
claude added 2 commits June 7, 2026 10:14
The TriggeredHealthCheck design has shipped; the proposal doc is no longer needed.

Signed-off-by: Claude <noreply@anthropic.com>
The two waits were framed as either/or alternatives and the run order was
described backwards. Rewrite around the actual behaviour: a rollout schedules
the check after a fixed delaySeconds soak (persisted, survives restart), then a
settleTimeout-capped wait for the rollout to become Available runs just before
the check. Add a timeline, a fixed-vs-capped comparison table, the meaning of 0
for each, concrete example timings, and disambiguate the two restart concerns.

Signed-off-by: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/operator/triggered-health-checks.md (1)

33-57: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a default Claude 4.5 model in the primary manifest example.

The example is missing spec.model, so it doesn’t follow the docs rule to default reference examples to the latest Claude 4.5 model. Add, for example, model: anthropic/claude-sonnet-4-5-20250929.

As per coding guidelines, “Primary documentation examples should use the latest Anthropic Claude models …” and “Reference documentation examples should use the latest Claude 4.5 models …”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/operator/triggered-health-checks.md` around lines 33 - 57, The example
TriggeredHealthCheck manifest is missing spec.model; add a default Claude 4.5
model entry under spec (e.g., set spec.model to
"anthropic/claude-sonnet-4-5-20250929") in the primary YAML example so the
TriggeredHealthCheck resource includes the required model reference and follows
the docs guideline for using the latest Claude 4.5 model.

Source: Coding guidelines

♻️ Duplicate comments (1)
docs/operator/triggered-health-checks.md (1)

103-106: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the timeline block lint violations (language + fence style).

Line 103 uses a fenced block without language (MD040), and current lint also expects indented style (MD046). Please either convert this timeline to the repository’s expected style or update lint config consistently.

Based on learnings from prior review context, a similar MD046 issue was already flagged for this file and should be handled consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/operator/triggered-health-checks.md` around lines 103 - 106, Replace the
fenced code block timeline in triggered-health-checks.md with the
repository-expected indented code block style to satisfy MD046 (remove the ```
fence and indent each timeline line by four spaces) and ensure no language tag
is added (MD040); keep the exact timeline content (the ASCII arrows and labels:
"rollout detected ──► wait delaySeconds ──► wait for rollout to be Available ──►
check runs" and the two parenthetical lines) so the lint rule passes
consistently with the prior MD046 fix in this file.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/operator/triggered-health-checks.md`:
- Around line 33-57: The example TriggeredHealthCheck manifest is missing
spec.model; add a default Claude 4.5 model entry under spec (e.g., set
spec.model to "anthropic/claude-sonnet-4-5-20250929") in the primary YAML
example so the TriggeredHealthCheck resource includes the required model
reference and follows the docs guideline for using the latest Claude 4.5 model.

---

Duplicate comments:
In `@docs/operator/triggered-health-checks.md`:
- Around line 103-106: Replace the fenced code block timeline in
triggered-health-checks.md with the repository-expected indented code block
style to satisfy MD046 (remove the ``` fence and indent each timeline line by
four spaces) and ensure no language tag is added (MD040); keep the exact
timeline content (the ASCII arrows and labels: "rollout detected ──► wait
delaySeconds ──► wait for rollout to be Available ──► check runs" and the two
parenthetical lines) so the lint rule passes consistently with the prior MD046
fix in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5acef14a-4f8f-44b5-ae4e-796cd66a4bf7

📥 Commits

Reviewing files that changed from the base of the PR and between adbc376 and 652760b.

📒 Files selected for processing (1)
  • docs/operator/triggered-health-checks.md

claude added 2 commits June 7, 2026 10:37
…s knob

Remove settleTimeout. Having two interacting waits (a cap on waiting for the
rollout to become Available, plus a fixed delay) was hard to understand. Collapse
to one plain-English field:

  delaySeconds = how long to wait after a rollout before running the check
                 (default 5 min; 0 = immediately; up to 7 days)

If the wait is shorter than the rollout, the check just reports that the rollout
hasn't finished yet, so no separate settle logic is needed. Drop wait_for_rollout
and the settle step; rename settle_and_spawn -> spawn_check.

Also make TriggeredHealthCheck the primary, first-class example on the deployment
verification page (declare-once auto-verification), keeping the inline HealthCheck
pattern for synchronous CI/CD gating, with a when-to-use-which table.

Verified end-to-end on a live k3s cluster with the real Holmes API + LLM: default
300 schedules the check 5 min out; a short delay fires through the timer into a
real investigation (kubectl query -> pass verdict in the HealthCheck).

Signed-off-by: Claude <noreply@anthropic.com>
Previously the spawned HealthCheck only knew the rollout facts if the author
wired the {{ .deployment }}/{{ .namespace }}/{{ .old.image }}/{{ .new.image }}
tokens into their query; a terse query like 'Is it healthy?' gave the model no
deployment or namespace to work with. Now a structured context header (deployment,
namespace, previous/new image) is always prepended to the query, so the model has
the facts regardless of how the query is written. Tokens still work for inline use.

Verified end-to-end on a live k3s cluster with Opus 4.8 via OpenRouter using a
terse query that never named the namespace: broken rollout (bad image) -> fail
citing the ImagePullBackOff pod; healthy rollout -> pass. Server logs confirm the
model queried the correct namespace purely from the injected context.

Signed-off-by: Claude <noreply@anthropic.com>

@arikalon1 arikalon1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work

@arikalon1
arikalon1 merged commit ca1a427 into master Jun 16, 2026
18 of 19 checks passed
@arikalon1
arikalon1 deleted the claude/holmesgpt-operator-mode-A2TjD branch June 16, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants