Skip to content

[ROB-3973] Multi instance grafana dashboard support - #2080

Merged
Avi-Robusta merged 9 commits into
masterfrom
multi-instance-grafana
May 26, 2026
Merged

Avi-Robusta merged 9 commits into
masterfrom
multi-instance-grafana

Conversation

@Avi-Robusta

@Avi-Robusta Avi-Robusta commented May 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Multi-instance Grafana support with per-call instance selection and automatic single-instance selection
    • Username/password (HTTP Basic) added as an alternative to API keys with per-instance overrides and credential precedence
    • Dashboard UI links (grafana_url) attached to tool results; non-dict outputs wrapped with URL
  • Behavior

    • Health checks probe and aggregate across instances and tolerate unreachable instances while reporting failures
  • Documentation

    • New guide on multi-instance configuration, auth options, credential inheritance, and single-instance basic-auth example
  • Tests

    • Added tests for multi-instance behavior, auth validation, inheritance, request wiring, and instance selection

Review Change Stack

Avi-Robusta and others added 2 commits May 12, 2026 15:51
`grafana/dashboards`, `grafana/loki`, and `grafana/tempo` now accept an
`instances` list and per-instance `username`/`password` basic-auth, in
addition to the existing single-instance + `api_key` shape (legacy configs
keep working unchanged). Top-level credentials act as global defaults
inherited by any instance that doesn't override them.

Tools take an optional `grafana_instance` parameter that the LLM uses to
pick a target; with a single instance configured it's auto-selected.
Health checks are tolerant — partial failures don't disable the toolset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: avi@robusta.dev <avi@robusta.dev>
Loki and Tempo revert to the master single-instance shape; the
multi-instance `instances` list and HTTP Basic auth fields now live on a
new MultiInstanceGrafanaConfig + BaseMultiInstanceGrafanaToolset pair
used only by grafana/dashboards.

Signed-off-by: avi@robusta.dev <avi@robusta.dev>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: avi@robusta.dev <avi@robusta.dev>

@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.

@Avi-Robusta Avi-Robusta changed the title Multi instance grafana support Multi instance grafana dashboard support May 24, 2026
@github-actions

github-actions Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

📜 #2 · Run @ __7c042b4__ (#26402881053) — May 25, 13:35 UTC

✅ Results of HolmesGPT evals

Automatically triggered by commit 7c042b4 on branch multi-instance-grafana

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 11/11 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 Src
✅ 09_crashpod 38.6s 5 11 $0.2754 107,254 104,945 23,923 2,309 1,058 80,443 24,502 240 — src
✅ 101_loki_historical_logs_pod_deleted 83.0s 7 17 $0.4157 168,517 163,549 28,701 4,968 919 133,319 30,230 1,177 — src
✅ 112_find_pvcs_by_uuid 19.3s 3 4 $0.2053 61,067 59,867 21,837 1,200 594 37,775 22,092 252 — src
✅ 12_job_crashing 50.4s 6 14 $0.3564 153,746 150,750 28,875 2,996 702 120,156 30,594 419 — src
✅ 176_network_policy_blocking_traffic_no_skills 52.6s 6 12 $0.3437 137,878 134,765 26,079 3,113 743 104,833 29,932 793 — src
✅ 227_count_configmaps_per_namespace[0] 21.1s 4 9 $0.2047 76,667 75,541 20,646 1,126 594 54,581 20,960 53 — src
✅ 243_pod_names_contain_service 40.1s 5 11 $0.2727 105,551 103,182 23,240 2,369 934 79,164 24,018 245 — src
✅ 24_misconfigured_pvc 43.6s 5 14 $0.2939 109,007 106,313 24,358 2,694 1,058 80,488 25,825 271 — src
✅ 43_current_datetime_from_prompt 4.6s 1 — $0.1190 17,001 16,899 16,899 102 102 0 16,899 61 — src
✅ 51_logs_summarize_errors 22.6s 4 5 $0.2037 77,113 76,039 20,921 1,074 420 55,113 20,926 32 — src
✅ 61_exact_match_counting 12.5s 3 3 $0.1518 52,720 52,358 17,870 362 215 34,484 17,874 31 — src
Total 35.3s avg 4.5 avg 10.0 avg $2.8423 1,066,521 1,044,208 28,875 22,313 1,058 780,356 263,852 3,574 —
Benchmark Comparison Details

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

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

Time comparison (seconds):

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 38.6s 45.0s ↓14% 37.6s ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 83.0s 72.3s ↑15% 73.0s ↑14%
112_find_pvcs_by_uuid (opus-4.6) 📄 19.3s 21.2s ±0% 20.9s ±0%
12_job_crashing (opus-4.6) 📄 50.4s 45.1s ↑12% 45.3s ↑11%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 52.6s 54.4s ±0% 45.8s ↑15%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 21.1s 20.5s ±0% 20.0s ±0%
243_pod_names_contain_service (opus-4.6) 📄 40.1s 36.4s ↑10% 41.1s ±0%
24_misconfigured_pvc (opus-4.6) 📄 43.6s 49.7s ↓12% 38.4s ↑14%
43_current_datetime_from_prompt (opus-4.6) 📄 4.6s 3.2s ↑43% 3.5s ↑31%
51_logs_summarize_errors (opus-4.6) 📄 22.6s 23.0s ±0% 22.3s ±0%
61_exact_match_counting (opus-4.6) 📄 12.5s 10.5s ↑19% 10.3s ↑21%
Total (all, n=11) 35.3s 34.7s — 32.6s —
Comparable (m=11, b=11) 35.3s 34.7s ±0% 32.6s ±0%

Cost comparison:

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 $0.2754 $0.3180 ↓13% $0.2799 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 $0.4157 $0.3938 ±0% $0.4212 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 $0.2053 $0.2067 ±0% $0.2055 ±0%
12_job_crashing (opus-4.6) 📄 $0.3564 $0.3270 ±0% $0.3155 ↑13%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 $0.3437 $0.3638 ±0% $0.3301 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 $0.2047 $0.2047 ±0% $0.2055 ±0%
243_pod_names_contain_service (opus-4.6) 📄 $0.2727 $0.2544 ±0% $0.2887 ±0%
24_misconfigured_pvc (opus-4.6) 📄 $0.2939 $0.3273 ↓10% $0.2854 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 $0.1190 $0.1189 ±0% $0.1182 ±0%
51_logs_summarize_errors (opus-4.6) 📄 $0.2037 $0.2060 ±0% $0.2052 ±0%
61_exact_match_counting (opus-4.6) 📄 $0.1518 $0.1518 ±0% $0.1511 ±0%
Total (all, n=11) $0.2584 $0.2611 — $0.2551 —
Comparable (m=11, b=11) $0.2584 $0.2611 ±0% $0.2551 ±0%

Total tokens comparison:

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 107,254 137,003 ↓22% 106,269 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 168,517 168,348 ±0% 171,838 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 61,067 61,245 ±0% 60,885 ±0%
12_job_crashing (opus-4.6) 📄 153,746 148,569 ±0% 134,896 ↑14%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 137,878 145,034 ±0% 138,789 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 76,667 76,677 ±0% 76,302 ±0%
243_pod_names_contain_service (opus-4.6) 📄 105,551 102,005 ±0% 126,664 ↓17%
24_misconfigured_pvc (opus-4.6) 📄 109,007 134,521 ↓19% 107,150 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 17,001 16,999 ±0% 16,898 ±0%
51_logs_summarize_errors (opus-4.6) 📄 77,113 77,352 ±0% 77,029 ±0%
61_exact_match_counting (opus-4.6) 📄 52,720 52,721 ±0% 52,431 ±0%
Total (all, n=11) 96,956 101,861 — 97,196 —
Comparable (m=11, b=11) 96,956 101,861 ±0% 97,196 ±0%

Cached tokens comparison:

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 80,443 107,950 ↓25% 78,682 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 133,319 133,698 ±0% 134,429 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 37,775 37,830 ±0% 37,612 ±0%
12_job_crashing (opus-4.6) 📄 120,156 117,736 ±0% 105,026 ↑14%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 104,833 110,553 ±0% 108,400 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 54,581 54,584 ±0% 53,987 ±0%
243_pod_names_contain_service (opus-4.6) 📄 79,164 77,030 ±0% 100,135 ↓21%
24_misconfigured_pvc (opus-4.6) 📄 80,488 104,742 ↓23% 79,251 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 — — — — —
51_logs_summarize_errors (opus-4.6) 📄 55,113 55,205 ±0% 54,949 ±0%
61_exact_match_counting (opus-4.6) 📄 34,484 34,483 ±0% 34,287 ±0%
Total (all, n=11) 70,941 75,801 — 71,523 —
Comparable (m=10, b=10) 78,036 83,381 ±0% 78,676 ±0%

Turns comparison:

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 5 6 ↓17% 5 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 7 7 ±0% 7 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 3 3 ±0% 3 ±0%
12_job_crashing (opus-4.6) 📄 6 6 ±0% 6 ±0%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 6 6 ±0% 6 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 4 4 ±0% 4 ±0%
243_pod_names_contain_service (opus-4.6) 📄 5 5 ±0% 6 ↓17%
24_misconfigured_pvc (opus-4.6) 📄 5 6 ↓17% 5 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 1 1 ±0% 1 ±0%
51_logs_summarize_errors (opus-4.6) 📄 4 4 ±0% 4 ±0%
61_exact_match_counting (opus-4.6) 📄 3 3 ±0% 3 ±0%
Total (all, n=11) 4.5 4.6 — 4.5 —
Comparable (m=11, b=11) 4.5 4.6 ±0% 4.5 ±0%

Tool calls comparison:

Test case This branch master (3h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 11 13 ↓15% 11 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 17 17 ±0% 17 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 4 4 ±0% 4 ±0%
12_job_crashing (opus-4.6) 📄 14 13 ±0% 14 ±0%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 12 16 ↓25% 15 ↓20%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 9 9 ±0% 9 ±0%
243_pod_names_contain_service (opus-4.6) 📄 11 9 ↑22% 11 ±0%
24_misconfigured_pvc (opus-4.6) 📄 14 15 ±0% 14 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 — — — — —
51_logs_summarize_errors (opus-4.6) 📄 5 5 ±0% 5 ±0%
61_exact_match_counting (opus-4.6) 📄 3 3 ±0% 3 ±0%
Total (all, n=11) 9.1 10.4 — 10.3 —
Comparable (m=10, b=10) 10.0 10.4 ±0% 10.3 ±0%

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
⚠️ 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 7eff009 on branch multi-instance-grafana

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 11/11 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 Src
✅ 09_crashpod 35.9s 5 10 $0.2668 105,477 103,341 23,460 2,136 974 79,307 24,034 243 — src
✅ 101_loki_historical_logs_pod_deleted 80.2s 8 20 $0.4325 197,118 192,160 29,018 4,958 929 161,737 30,423 864 — src
✅ 112_find_pvcs_by_uuid 20.3s 3 4 $0.2057 61,174 59,976 21,898 1,198 622 37,823 22,153 295 — src
✅ 12_job_crashing 62.7s 8 17 $0.3684 187,366 183,958 26,534 3,408 858 156,283 27,675 500 — src
✅ 176_network_policy_blocking_traffic_no_skills 53.8s 5 14 $0.3264 115,061 111,782 26,593 3,279 1,299 83,876 27,906 640 — src
✅ 227_count_configmaps_per_namespace[0] 20.2s 4 9 $0.2079 76,701 75,573 20,660 1,128 594 53,979 21,594 53 — src
✅ 243_pod_names_contain_service 34.4s 5 9 $0.2541 101,866 99,939 22,351 1,927 890 76,633 23,306 138 — src
✅ 24_misconfigured_pvc 51.7s 6 15 $0.3337 134,687 131,409 25,263 3,278 1,061 103,829 27,580 550 — src
✅ 43_current_datetime_from_prompt 4.2s 1 — $0.0121 16,999 16,899 16,899 100 100 16,896 3 60 — src
✅ 51_logs_summarize_errors 22.8s 4 5 $0.2032 76,611 75,480 20,639 1,131 476 54,836 20,644 32 — src
✅ 61_exact_match_counting 11.3s 3 3 $0.1518 52,728 52,366 17,876 362 215 34,486 17,880 31 — src
Total 36.1s avg 4.7 avg 10.6 avg $2.7624 1,125,788 1,102,883 29,018 22,905 1,299 859,685 243,198 3,406 —
Benchmark Comparison Details

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

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

Time comparison (seconds):

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 35.9s 32.7s ±0% 37.6s ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 80.2s 78.3s ±0% 73.0s ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 20.3s 17.6s ↑16% 20.9s ±0%
12_job_crashing (opus-4.6) 📄 62.7s 47.4s ↑32% 45.3s ↑38%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 53.8s 62.7s ↓14% 45.8s ↑18%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 20.2s 19.8s ±0% 20.0s ±0%
243_pod_names_contain_service (opus-4.6) 📄 34.4s 39.5s ↓13% 41.1s ↓16%
24_misconfigured_pvc (opus-4.6) 📄 51.7s 43.8s ↑18% 38.4s ↑35%
43_current_datetime_from_prompt (opus-4.6) 📄 4.2s 3.8s ±0% 3.5s ↑18%
51_logs_summarize_errors (opus-4.6) 📄 22.8s 21.7s ±0% 22.3s ±0%
61_exact_match_counting (opus-4.6) 📄 11.3s 10.6s ±0% 10.3s ±0%
Total (all, n=11) 36.1s 34.3s — 32.6s —
Comparable (m=11, b=11) 36.1s 34.3s ±0% 32.6s ↑11%

Cost comparison:

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 $0.2668 $0.2583 ±0% $0.2799 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 $0.4325 $0.4138 ±0% $0.4212 ±0%
112_find_pvcs_by_uuid (opus-4.6) 📄 $0.2057 $0.1900 ±0% $0.2055 ±0%
12_job_crashing (opus-4.6) 📄 $0.3684 $0.3142 ↑17% $0.3155 ↑17%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 $0.3264 $0.3862 ↓15% $0.3301 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 $0.2079 $0.2031 ±0% $0.2055 ±0%
243_pod_names_contain_service (opus-4.6) 📄 $0.2541 $0.2729 ±0% $0.2887 ↓12%
24_misconfigured_pvc (opus-4.6) 📄 $0.3337 $0.3010 ↑11% $0.2854 ↑17%
43_current_datetime_from_prompt (opus-4.6) 📄 $0.0121 $0.1189 ↓90% $0.1182 ↓90%
51_logs_summarize_errors (opus-4.6) 📄 $0.2032 $0.2040 ±0% $0.2052 ±0%
61_exact_match_counting (opus-4.6) 📄 $0.1518 $0.1518 ±0% $0.1511 ±0%
Total (all, n=11) $0.2511 $0.2558 — $0.2551 —
Comparable (m=11, b=11) $0.2511 $0.2558 ±0% $0.2551 ±0%

Total tokens comparison:

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 105,477 84,961 ↑24% 106,269 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 197,118 171,846 ↑15% 171,838 ↑15%
112_find_pvcs_by_uuid (opus-4.6) 📄 61,174 58,072 ±0% 60,885 ±0%
12_job_crashing (opus-4.6) 📄 187,366 154,948 ↑21% 134,896 ↑39%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 115,061 172,978 ↓33% 138,789 ↓17%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 76,701 76,661 ±0% 76,302 ±0%
243_pod_names_contain_service (opus-4.6) 📄 101,866 105,543 ±0% 126,664 ↓20%
24_misconfigured_pvc (opus-4.6) 📄 134,687 130,101 ±0% 107,150 ↑26%
43_current_datetime_from_prompt (opus-4.6) 📄 16,999 16,999 ±0% 16,898 ±0%
51_logs_summarize_errors (opus-4.6) 📄 76,611 76,989 ±0% 77,029 ±0%
61_exact_match_counting (opus-4.6) 📄 52,728 52,725 ±0% 52,431 ±0%
Total (all, n=11) 102,344 100,166 — 97,196 —
Comparable (m=11, b=11) 102,344 100,166 ±0% 97,196 ±0%

Cached tokens comparison:

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 79,307 58,055 ↑37% 78,682 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 161,737 135,478 ↑19% 134,429 ↑20%
112_find_pvcs_by_uuid (opus-4.6) 📄 37,823 36,442 ±0% 37,612 ±0%
12_job_crashing (opus-4.6) 📄 156,283 126,891 ↑23% 105,026 ↑49%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 83,876 139,279 ↓40% 108,400 ↓23%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 53,979 54,887 ±0% 53,987 ±0%
243_pod_names_contain_service (opus-4.6) 📄 76,633 79,165 ±0% 100,135 ↓23%
24_misconfigured_pvc (opus-4.6) 📄 103,829 101,761 ±0% 79,251 ↑31%
43_current_datetime_from_prompt (opus-4.6) 📄 16,896 — — — —
51_logs_summarize_errors (opus-4.6) 📄 54,836 55,033 ±0% 54,949 ±0%
61_exact_match_counting (opus-4.6) 📄 34,486 34,484 ±0% 34,287 ±0%
Total (all, n=11) 78,153 74,680 — 71,523 —
Comparable (m=10, b=10) 84,279 82,148 ±0% 78,676 ±0%

Turns comparison:

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 5 4 ↑25% 5 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 8 7 ↑14% 7 ↑14%
112_find_pvcs_by_uuid (opus-4.6) 📄 3 3 ±0% 3 ±0%
12_job_crashing (opus-4.6) 📄 8 7 ↑14% 6 ↑33%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 5 7 ↓29% 6 ↓17%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 4 4 ±0% 4 ±0%
243_pod_names_contain_service (opus-4.6) 📄 5 5 ±0% 6 ↓17%
24_misconfigured_pvc (opus-4.6) 📄 6 6 ±0% 5 ↑20%
43_current_datetime_from_prompt (opus-4.6) 📄 1 1 ±0% 1 ±0%
51_logs_summarize_errors (opus-4.6) 📄 4 4 ±0% 4 ±0%
61_exact_match_counting (opus-4.6) 📄 3 3 ±0% 3 ±0%
Total (all, n=11) 4.7 4.6 — 4.5 —
Comparable (m=11, b=11) 4.7 4.6 ±0% 4.5 ±0%

Tool calls comparison:

Test case This branch master (1h ago) Δ vs master benchmark (1d ago) Δ vs benchmark
09_crashpod (opus-4.6) 📄 10 10 ±0% 11 ±0%
101_loki_historical_logs_pod_deleted (opus-4.6) 📄 20 17 ↑18% 17 ↑18%
112_find_pvcs_by_uuid (opus-4.6) 📄 4 4 ±0% 4 ±0%
12_job_crashing (opus-4.6) 📄 17 13 ↑31% 14 ↑21%
176_network_policy_blocking_traffic_no_skills (opus-4.6) 📄 14 16 ↓12% 15 ±0%
227_count_configmaps_per_namespace[0] (opus-4.6) 📄 9 9 ±0% 9 ±0%
243_pod_names_contain_service (opus-4.6) 📄 9 11 ↓18% 11 ↓18%
24_misconfigured_pvc (opus-4.6) 📄 15 14 ±0% 14 ±0%
43_current_datetime_from_prompt (opus-4.6) 📄 — — — — —
51_logs_summarize_errors (opus-4.6) 📄 5 5 ±0% 5 ±0%
61_exact_match_counting (opus-4.6) 📄 3 3 ±0% 3 ±0%
Total (all, n=11) 9.6 10.2 — 10.3 —
Comparable (m=10, b=10) 10.6 10.2 ±0% 10.3 ±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 multi-instance-grafana -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, 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, 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, haiku-4.5, kimi-2.5, kimi-2.5-openrouter, opus-4.5, opus-4.6, opus-4.7, 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 multi-instance-grafana -f markers=regression -f filter=

@github-actions

github-actions Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker images ready for 1390304c (built in 4m 35s)

⚠️ 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:1390304c
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes:1390304c me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:1390304c
docker push me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:1390304c
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes-operator:1390304c
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes-operator:1390304c me-west1-docker.pkg.dev/robusta-development/development/holmes-operator-dev:1390304c
docker push me-west1-docker.pkg.dev/robusta-development/development/holmes-operator-dev:1390304c

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:1390304c \
  --set operator.registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set operator.image=holmes-operator-dev:1390304c

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:1390304c \
  --set holmes.operator.registry=me-west1-docker.pkg.dev/robusta-development/development \
  --set holmes.operator.image=holmes-operator-dev:1390304c

@coderabbitai

coderabbitai Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

This PR extends the Grafana toolset to support configuring and routing requests to multiple Grafana instances, with per-instance authentication/overrides, aggregated health checks, instance-aware request/render helpers, tool parameter threading, tests, and documentation.

Changes

Grafana multi-instance support

Layer / File(s) Summary
Multi-instance config schema and validation
holmes/plugins/toolsets/grafana/common.py
Introduces GrafanaInstance, build_auth(), and MultiInstanceGrafanaConfig; refactors get_base_url() to accept either a config or instance and adds validators to coerce/normalize instances, synthesize a default, enforce unique names, and inherit global defaults.
Toolset base routing infrastructure
holmes/plugins/toolsets/grafana/base_grafana_toolset.py
Adds GRAFANA_INSTANCE_PARAM_DESCRIPTION. Implements BaseMultiInstanceGrafanaToolset that builds an internal _instances map from config, validates config class, runs health checks, aggregates health across instances, and resolves instances for tool calls (_get_instance).
Toolset imports and shared parameter
holmes/plugins/toolsets/grafana/toolset_grafana.py
Update imports to multi-instance types and introduce the shared grafana_instance tool parameter and switch dashboard config to MultiInstanceGrafanaConfig.
URL helpers, attach, and renderer probing
holmes/plugins/toolsets/grafana/toolset_grafana.py
Change _build_grafana_dashboard_url() to accept GrafanaInstance, add _attach_grafana_url() to include UI links in results, make renderer probing instance-aware, and rewrite health_check() to probe each instance and aggregate failures.
Request and render helpers parameterized for instances
holmes/plugins/toolsets/grafana/toolset_grafana.py
Refactor BaseGrafanaTool._make_grafana_request() and BaseGrafanaRenderTool._make_render_request() to accept GrafanaInstance and derive timeout/retries/base URL/headers/auth/verify from it; pass instance through _render_to_result(); add instance-tagged error handling.
Dashboard tools with instance routing
holmes/plugins/toolsets/grafana/toolset_grafana.py
Extend SearchDashboards, GetDashboardByUID, GetHomeDashboard, and GetDashboardTags with grafana_instance parameter, resolve the instance per-invocation, return structured errors on resolution failure, and attach instance-aware UI URLs.
Render tools with instance routing
holmes/plugins/toolsets/grafana/toolset_grafana.py
Extend RenderPanel and RenderDashboard schemas with grafana_instance, resolve instance in _invoke, and thread the resolved instance into render requests and dashboard URL construction.
Multi-instance tests and test fixture updates
tests/plugins/toolsets/grafana/test_grafana_multi_instance.py, tests/plugins/toolsets/test_json_filter_mixin.py, tests/plugins/toolsets/test_verify_tool_urls.py
Adds comprehensive tests for single- and multi-instance shapes, auth XOR validation, build_auth() behavior, request headers on the wire, _get_instance() resolution, and threading; updates existing tests to initialize _instances and mock request signatures with an instance parameter.
User-facing documentation
docs/data-sources/builtin-toolsets/grafanadashboards.md
Adds "Multiple Grafana Instances" section detailing instances configuration, credential precedence and mutual-exclusivity, health-check tolerance, and a single-instance username/password example.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HolmesGPT/holmesgpt#1075: Prior Grafana dashboard tooling and unified request pipeline changes that this multi-instance work extends.
  • HolmesGPT/holmesgpt#1498: Related Grafana config/header/key schema changes in common.py that overlap with this PR's config evolution.
  • HolmesGPT/holmesgpt#1280: Overlapping changes to Grafana dashboard tools and invocation/path logic that affect json-filter behavior.

Suggested labels

codex

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.15% 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.
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.
Title check ✅ Passed The title 'Multi instance grafana dashboard support' accurately describes the primary change: adding multi-instance support to Grafana dashboard toolset configuration and operations.

✏️ 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 May 24, 2026

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 292f54c
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6a12888586f2e800088a5561
😎 Deploy Preview https://deploy-preview-2080--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.

@netlify

netlify Bot commented May 24, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 7eff009
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6a1555719f268a00086666fe
😎 Deploy Preview https://deploy-preview-2080--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: 4

🧹 Nitpick comments (3)
tests/plugins/toolsets/grafana/test_grafana_multi_instance.py (2)

101-103: ⚡ Quick win

Move function-local imports to module scope.

These imports should be hoisted to the top-level import block to keep import behavior consistent and deterministic.

Proposed diff
 import base64
+import logging
 from unittest.mock import MagicMock

 import pytest
 import requests
 import responses
 from pydantic import ValidationError
 from requests.auth import HTTPBasicAuth

@@
-from holmes.plugins.toolsets.grafana.toolset_grafana import GrafanaToolset
+from holmes.plugins.toolsets.grafana.toolset_grafana import (
+    GrafanaDashboardConfig,
+    GrafanaToolset,
+)
@@
     def test_top_level_api_url_with_instances_logs_warning(self, caplog):
-        import logging
         with caplog.at_level(logging.WARNING):
             GrafanaConfig(
                 api_url="http://ignored",
                 instances=[{"name": "real", "api_url": "http://real"}],
             )
@@
 def _toolset_with(**config) -> GrafanaToolset:
     """Build a GrafanaToolset with state populated directly (no network probe)."""
-    from holmes.plugins.toolsets.grafana.toolset_grafana import GrafanaDashboardConfig
-
     ts = GrafanaToolset()
     ts._grafana_config = GrafanaDashboardConfig(**config)

As per coding guidelines: **/*.py: Always place Python imports at the top of the file, not inside functions or methods.

Also applies to: 174-177

🤖 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 `@tests/plugins/toolsets/grafana/test_grafana_multi_instance.py` around lines
101 - 103, The test contains function-local imports (logging and possibly
others) in test_top_level_api_url_with_instances_logs_warning and similarly at
lines around 174-177; move those imports to module scope by hoisting "import
logging" (and any other imports used only inside tests at those locations, e.g.,
caplog-related imports if present) to the top of the file's import block so
imports are consistent and deterministic—update the top-of-file imports and
remove the in-function import statements in the test functions (reference
functions: test_top_level_api_url_with_instances_logs_warning and the other test
at ~174-177).

174-174: ⚡ Quick win

Annotate _toolset_with kwargs for typing compliance.

Please type **config to satisfy the repository typing requirement for Python code.

Proposed diff
+from typing import Any
@@
-def _toolset_with(**config) -> GrafanaToolset:
+def _toolset_with(**config: Any) -> GrafanaToolset:

As per coding guidelines: **/*.py: Type hints required (mypy configuration in pyproject.toml).

🤖 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 `@tests/plugins/toolsets/grafana/test_grafana_multi_instance.py` at line 174,
Annotate the var-kwargs on the helper by adding a typing for **config (e.g.,
**config: Any) and import Any from typing; update the signature of _toolset_with
to use that annotated **config so it satisfies mypy/type-hint rules while
preserving the GrafanaToolset return type and existing behavior.
tests/plugins/toolsets/test_verify_tool_urls.py (1)

421-421: ⚡ Quick win

Add type hints to the updated mock helper signature.

The new multi-instance-aware mock function should be annotated to match the repository typing rule.

Proposed diff
+from typing import Any
@@
-        def mock_make_request(instance, endpoint, params, query_params=None, timeout=30):
+        def mock_make_request(
+            instance: Any,
+            endpoint: str,
+            params: dict[str, Any],
+            query_params: dict[str, Any] | None = None,
+            timeout: int = 30,
+        ) -> MagicMock:

As per coding guidelines: **/*.py: Type hints required (mypy configuration in pyproject.toml).

🤖 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 `@tests/plugins/toolsets/test_verify_tool_urls.py` at line 421, The new
multi-instance-aware mock function mock_make_request lacks type annotations;
update its signature to include typing (e.g., import and use Optional, Dict,
Any, and int) so parameters like instance, endpoint, params, query_params:
Optional[Dict[str, Any]] = None, timeout: int = 30 are annotated and the return
type is specified (e.g., -> Any or -> Dict[str, Any]) to satisfy the repository
mypy rules.
🤖 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/data-sources/builtin-toolsets/grafanadashboards.md`:
- Around line 196-204: The fenced YAML code block under the Grafana dashboards
subsection triggers markdownlint rule MD046; replace the triple-backtick fenced
block with an indented code block by removing the backticks and prefixing each
YAML line with the correct indentation for the subsection so the snippet
beginning with the toolsets: grafana/dashboards lines becomes an indented code
block; ensure the block content (toolsets:, grafana/dashboards:, enabled:,
config:, api_url:, username:, password:) retains relative indentation so
markdown renders the same while satisfying MD046.

In `@holmes/plugins/toolsets/grafana/common.py`:
- Around line 372-376: The top-level auth fields (api_key, username, password)
must be validated the same way as per-instance auth to avoid silently preferring
api_key; update _normalize_and_resolve_globals() to detect and reject mixed
top-level credentials when instances is set by ensuring exactly one auth method
is provided: either api_key alone OR username and password together (not api_key
plus username/password, and not incomplete basic auth), and raise a clear
validation/error when the combination is invalid; apply the same validation
logic you use for per-instance entries to the top-level api_key, username,
password handling.

In `@holmes/plugins/toolsets/grafana/toolset_grafana.py`:
- Around line 196-205: health_check currently calls tool._make_grafana_request
and only catches raw exceptions; update health_check to use the structured
response returned by _make_grafana_request (inspect result.status and
result.body/response) instead of relying on exceptions, and when building
failures include detailed context: instance.name, the resolved request URL,
query params, HTTP status code and response body/message; use those fields to
construct the failure strings passed to _aggregate_health_results; apply the
same pattern to the other dashboard tools that call _make_grafana_request in the
225-270 region so all callers consume result.status and include full API error
details for LLM self-correction.
- Around line 120-130: The probe currently only checks the first instance
(first_instance) when grafana_config.enable_rendering, so render tools may be
skipped if a later instance supports the image renderer; update the logic in the
block that calls get_base_url(first_instance) and
self._try_add_render_tools(first_instance) to iterate over all entries in
self._instances (e.g., for instance in self._instances.values()), probe each
instance (log with get_base_url(instance)), call
self._try_add_render_tools(instance) for each, and stop early if the tools
(self.tools or specific grafana_render_panel/grafana_render_dashboard) have been
successfully added to avoid redundant probes.

---

Nitpick comments:
In `@tests/plugins/toolsets/grafana/test_grafana_multi_instance.py`:
- Around line 101-103: The test contains function-local imports (logging and
possibly others) in test_top_level_api_url_with_instances_logs_warning and
similarly at lines around 174-177; move those imports to module scope by
hoisting "import logging" (and any other imports used only inside tests at those
locations, e.g., caplog-related imports if present) to the top of the file's
import block so imports are consistent and deterministic—update the top-of-file
imports and remove the in-function import statements in the test functions
(reference functions: test_top_level_api_url_with_instances_logs_warning and the
other test at ~174-177).
- Line 174: Annotate the var-kwargs on the helper by adding a typing for
**config (e.g., **config: Any) and import Any from typing; update the signature
of _toolset_with to use that annotated **config so it satisfies mypy/type-hint
rules while preserving the GrafanaToolset return type and existing behavior.

In `@tests/plugins/toolsets/test_verify_tool_urls.py`:
- Line 421: The new multi-instance-aware mock function mock_make_request lacks
type annotations; update its signature to include typing (e.g., import and use
Optional, Dict, Any, and int) so parameters like instance, endpoint, params,
query_params: Optional[Dict[str, Any]] = None, timeout: int = 30 are annotated
and the return type is specified (e.g., -> Any or -> Dict[str, Any]) to satisfy
the repository mypy rules.
🪄 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: 0c905529-f0eb-44d0-84ae-a3adcf3c99bf

📥 Commits

Reviewing files that changed from the base of the PR and between e620f57 and 6dfa6a6.

📒 Files selected for processing (7)
  • docs/data-sources/builtin-toolsets/grafanadashboards.md
  • holmes/plugins/toolsets/grafana/base_grafana_toolset.py
  • holmes/plugins/toolsets/grafana/common.py
  • holmes/plugins/toolsets/grafana/toolset_grafana.py
  • tests/plugins/toolsets/grafana/test_grafana_multi_instance.py
  • tests/plugins/toolsets/test_json_filter_mixin.py
  • tests/plugins/toolsets/test_verify_tool_urls.py

Comment thread docs/data-sources/builtin-toolsets/grafanadashboards.md Outdated
Comment thread holmes/plugins/toolsets/grafana/common.py
Comment thread holmes/plugins/toolsets/grafana/toolset_grafana.py Outdated
Comment thread holmes/plugins/toolsets/grafana/toolset_grafana.py
The LLM only sees `StructuredToolResult.data`, not `.url`, so dashboard
links were invisible to it. Inject `grafana_url` into the data payload
in the four dashboard tools (search, get-by-uid, get-home, get-tags)
so the model can cite the correct instance link in its responses.

Signed-off-by: avi@robusta.dev <avi@robusta.dev>

@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.

🧹 Nitpick comments (1)
holmes/plugins/toolsets/grafana/toolset_grafana.py (1)

642-667: ⚡ Quick win

Include instance name and response body in render error messages.

The error messages don't identify which Grafana instance failed, which is important in multi-instance setups. Additionally, the HTTPError handler omits the response body that often contains actionable error details.

♻️ Suggested improvement
         except requests.HTTPError as e:
             status_code = (
                 e.response.status_code if e.response is not None else "unknown"
             )
+            response_text = e.response.text[:500] if e.response is not None else ""
             query_string = urlencode(query_params, doseq=True)
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Grafana render API returned HTTP {status_code}: {e}. "
+                error=f"[{instance.name}] Grafana render API returned HTTP {status_code}. "
                 f"Render path: {render_path}?{query_string}. "
-                f"Ensure the grafana-image-renderer plugin is installed and running.",
+                f"Response: {response_text}. "
+                f"Ensure the grafana-image-renderer plugin is installed and running." if not response_text else "",
                 params=params,
             )
         except requests.ConnectionError as e:
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Failed to connect to Grafana render API at {render_path}: {e}",
+                error=f"[{instance.name}] Failed to connect to Grafana render API at {render_path}: {e}",
                 params=params,
             )
         except requests.Timeout:
             query_string = urlencode(query_params, doseq=True)
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Grafana render request timed out for {render_path}?{query_string}. "
+                error=f"[{instance.name}] Grafana render request timed out for {render_path}?{query_string}. "
                 f"The panel may be too complex or the renderer is overloaded.",
                 params=params,
             )

As per coding guidelines: "All toolsets MUST return detailed error messages from underlying APIs to enable LLM self-correction, including the exact query/command executed, time ranges/parameters/filters used, and full API error response (status code and message)".

🤖 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/plugins/toolsets/grafana/toolset_grafana.py` around lines 642 - 667,
Include the Grafana instance identifier and the full HTTP response body in the
render error messages: in the requests.HTTPError handler (where render_path,
query_params, params and StructuredToolResult are used) append the instance
identifier that was used to build render_path (e.g., grafana_url or
self.instance_name) and, when e.response is not None, include
e.response.status_code and the response body text (e.response.text) in the error
string; do the same for the ConnectionError and Timeout handlers (include the
same instance identifier and the exception message/response body when available)
so all StructuredToolResult.error messages contain instance, exact render
path+query, and API response details.
🤖 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.

Nitpick comments:
In `@holmes/plugins/toolsets/grafana/toolset_grafana.py`:
- Around line 642-667: Include the Grafana instance identifier and the full HTTP
response body in the render error messages: in the requests.HTTPError handler
(where render_path, query_params, params and StructuredToolResult are used)
append the instance identifier that was used to build render_path (e.g.,
grafana_url or self.instance_name) and, when e.response is not None, include
e.response.status_code and the response body text (e.response.text) in the error
string; do the same for the ConnectionError and Timeout handlers (include the
same instance identifier and the exception message/response body when available)
so all StructuredToolResult.error messages contain instance, exact render
path+query, and API response details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b2eeda0e-9a11-466d-a379-13a48dc8952c

📥 Commits

Reviewing files that changed from the base of the PR and between 6dfa6a6 and 7c1d19b.

📒 Files selected for processing (1)
  • holmes/plugins/toolsets/grafana/toolset_grafana.py

In a heterogeneous multi-instance setup, only the first configured
Grafana was probed for the image-renderer plugin. If that instance
lacked the plugin, `grafana_render_panel` and `grafana_render_dashboard`
were never registered even when a later instance had it. Loop through
all instances and stop as soon as one exposes the renderer.

Signed-off-by: avi@robusta.dev <avi@robusta.dev>
Two assertions in test_json_filter_mixin still expected the raw filter
output, but the GetDashboardByUID tool now wraps results with the
Grafana UI URL (added in 7c1d19b "Expose Grafana UI URL in tool result
data for LLM visibility"). Update the expected payloads to match the
wrapped shape: dicts get `grafana_url` merged in; non-dict jq outputs
become `{grafana_url, results}`.

Signed-off-by: avi@robusta.dev <avi@robusta.dev>
- common.py: Apply XOR auth validation to top-level credentials. Rejects
  mixed `api_key` + `username/password` and incomplete basic auth so
  config errors surface up front instead of api_key silently winning
  during the per-instance fall-through.
- toolset_grafana.py: `_make_grafana_request` now returns structured
  ERROR results for HTTPError/Timeout/ConnectionError, prefixed with
  `[instance.name]` and including the resolved URL+query and HTTP
  response body — required for LLM self-correction per CLAUDE.md.
  `health_check` consumes the structured `result.status` instead of
  catching raw exceptions, so per-instance failures propagate with
  full context.
- toolset_grafana.py: Render-tool errors (HTTPError/ConnectionError/
  Timeout) gain the same `[instance.name]` prefix and include response
  body (first 500 chars) on HTTP failures.
- tests: hoist function-local `import logging` / `GrafanaDashboardConfig`
  in test_grafana_multi_instance.py to module scope. Annotate
  `_toolset_with(**config: Any)` and the `mock_make_request` helper in
  test_verify_tool_urls.py for mypy.
- grafanadashboards.md: convert the username/password top-level fenced
  YAML block to an indented code block to satisfy markdownlint MD046.

Signed-off-by: avi@robusta.dev <avi@robusta.dev>

@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 (2)
docs/data-sources/builtin-toolsets/grafanadashboards.md (2)

107-111: ⚠️ Potential issue | 🟠 Major

Fix invalid grafana_list_instances reference in Grafana multi-instance docs

docs/data-sources/builtin-toolsets/grafanadashboards.md (lines 109 and 190) tells users to run grafana_list_instances to see configured instance names, but the Grafana toolset does not implement any grafana_list_instances tool/function. Instead, instance names come from toolsets.grafana/dashboards.config.instances[].name (and unknown grafana_instance errors list configured names); update the doc to remove/replace the grafana_list_instances guidance, and rely on the toolset status string for the health-check note.

🤖 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/data-sources/builtin-toolsets/grafanadashboards.md` around lines 107 -
111, Replace the invalid grafana_list_instances reference in the Grafana
multi-instance docs: remove the instruction to run grafana_list_instances and
instead tell users to read instance names from
toolsets.grafana/dashboards.config.instances[].name (and note that unknown
grafana_instance errors will list configured names), and update the health-check
sentence to refer to the toolset status string rather than the nonexistent
function.

190-190: ⚠️ Potential issue | 🟠 Major

Fix “toolset status string” wording (and the grafana_list_instances reference) in Grafana dashboards docs

  • There is no user-facing concept named “toolset status string” in the code; toolset health is surfaced via pretty_print_toolset_status() (status/error table) and cached in toolsets_status.json (especially the error field).
  • For the Grafana multi-instance tolerant health check, when at least one instance is reachable health_check() returns (True, ""), so unreachable-instance details won’t appear in the status/error table (they’re emitted in the warning log instead). The joined failure details populate Toolset.error only when all instances fail.
  • grafana_list_instances is only referenced in the docs and isn’t present in the repo as an actual tool/command.
🤖 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/data-sources/builtin-toolsets/grafanadashboards.md` at line 190, Update
the Grafana dashboards doc wording to reference actual code artifacts: replace
“toolset status string” with the status/error table produced by
pretty_print_toolset_status() and the cached status in toolsets_status.json (not
a generic string); clarify that health_check() for the multi-instance tolerant
check returns (True, "") when at least one instance is reachable so
unreachable-instance details are emitted to warnings (and only set Toolset.error
when all instances fail); and remove or mark as inaccurate the reference to
grafana_list_instances since that command/tool does not exist in the repo.
🧹 Nitpick comments (2)
docs/data-sources/builtin-toolsets/grafanadashboards.md (2)

192-202: 💤 Low value

Consider adding deployment tabs for consistency.

This subsection shows a single code snippet without the tab structure (Holmes CLI / Holmes Helm Chart / Robusta Helm Chart) used throughout the rest of the documentation. For consistency with the rest of the file and to help users across different deployment methods, consider restructuring this subsection to use the same tab pattern.

Additionally, this subsection is nested under "Multiple Grafana Instances" but describes single-instance usage. Consider whether it should be:

  1. A sibling section to "Multiple Grafana Instances" (e.g., "## Alternative Authentication Methods"), or
  2. Moved to the main Configuration section as an authentication option
🤖 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/data-sources/builtin-toolsets/grafanadashboards.md` around lines 192 -
202, The "Username/password authentication (single instance)" subsection
currently lacks the deployment-tabs pattern and sits under "Multiple Grafana
Instances"; update it to match other docs by adding the Holmes CLI / Holmes Helm
Chart / Robusta Helm Chart tab structure around the example (same tab names and
ordering used elsewhere) and either move this subsection out from under
"Multiple Grafana Instances" into a sibling section (e.g., "Alternative
Authentication Methods") or relocate it into the main Configuration section as
an authentication option so the heading and placement reflect single-instance
usage; target the "Username/password authentication (single instance)" heading
and the surrounding "Multiple Grafana Instances" section when making the change.

167-188: ⚡ Quick win

Add staging instance example to Robusta tab for consistency.

The Holmes CLI and Helm Chart examples show three instances including a staging instance with an api_key override (lines 130-133, 162-164), but the Robusta example only shows two instances. For consistency and to demonstrate per-instance credential overrides across all deployment methods, add the staging instance to the Robusta example.

📝 Proposed addition
             instances:
               - name: prod-eu
                 api_url: https://grafana.eu-west-1.internal
               - name: prod-us
                 api_url: https://grafana.us-east-1.internal
+              - name: staging
+                api_url: https://grafana.staging.internal
+                api_key: "{{ env.STAGING_GRAFANA_API_KEY }}"

You'll also need to add the corresponding environment variable at the top:

     holmes:
       additionalEnvVars:
         - name: GRAFANA_PASSWORD
           valueFrom:
             secretKeyRef:
               name: grafana-credentials
               key: password
+        - name: STAGING_GRAFANA_API_KEY
+          valueFrom:
+            secretKeyRef:
+              name: grafana-credentials
+              key: staging-api-key
       toolsets:
🤖 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/data-sources/builtin-toolsets/grafanadashboards.md` around lines 167 -
188, Add the missing "staging" instance to the Robusta Helm Chart example under
toolsets.grafana/dashboards.config.instances (matching the Holmes CLI and Helm
examples) and include a per-instance credential override (e.g., an api_key field
for the staging instance); also add the corresponding environment variable entry
under holmes.additionalEnvVars (so the template value like "{{
env.GRAFANA_STAGING_API_KEY }}" resolves) to demonstrate per-instance credential
overrides consistently across examples.
🤖 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/data-sources/builtin-toolsets/grafanadashboards.md`:
- Around line 107-111: Replace the invalid grafana_list_instances reference in
the Grafana multi-instance docs: remove the instruction to run
grafana_list_instances and instead tell users to read instance names from
toolsets.grafana/dashboards.config.instances[].name (and note that unknown
grafana_instance errors will list configured names), and update the health-check
sentence to refer to the toolset status string rather than the nonexistent
function.
- Line 190: Update the Grafana dashboards doc wording to reference actual code
artifacts: replace “toolset status string” with the status/error table produced
by pretty_print_toolset_status() and the cached status in toolsets_status.json
(not a generic string); clarify that health_check() for the multi-instance
tolerant check returns (True, "") when at least one instance is reachable so
unreachable-instance details are emitted to warnings (and only set Toolset.error
when all instances fail); and remove or mark as inaccurate the reference to
grafana_list_instances since that command/tool does not exist in the repo.

---

Nitpick comments:
In `@docs/data-sources/builtin-toolsets/grafanadashboards.md`:
- Around line 192-202: The "Username/password authentication (single instance)"
subsection currently lacks the deployment-tabs pattern and sits under "Multiple
Grafana Instances"; update it to match other docs by adding the Holmes CLI /
Holmes Helm Chart / Robusta Helm Chart tab structure around the example (same
tab names and ordering used elsewhere) and either move this subsection out from
under "Multiple Grafana Instances" into a sibling section (e.g., "Alternative
Authentication Methods") or relocate it into the main Configuration section as
an authentication option so the heading and placement reflect single-instance
usage; target the "Username/password authentication (single instance)" heading
and the surrounding "Multiple Grafana Instances" section when making the change.
- Around line 167-188: Add the missing "staging" instance to the Robusta Helm
Chart example under toolsets.grafana/dashboards.config.instances (matching the
Holmes CLI and Helm examples) and include a per-instance credential override
(e.g., an api_key field for the staging instance); also add the corresponding
environment variable entry under holmes.additionalEnvVars (so the template value
like "{{ env.GRAFANA_STAGING_API_KEY }}" resolves) to demonstrate per-instance
credential overrides consistently across examples.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 624da601-b896-41ff-972e-f6331c214d1d

📥 Commits

Reviewing files that changed from the base of the PR and between 419cfe8 and 943932d.

📒 Files selected for processing (6)
  • docs/data-sources/builtin-toolsets/grafanadashboards.md
  • holmes/plugins/toolsets/grafana/common.py
  • holmes/plugins/toolsets/grafana/toolset_grafana.py
  • tests/plugins/toolsets/grafana/test_grafana_multi_instance.py
  • tests/plugins/toolsets/test_json_filter_mixin.py
  • tests/plugins/toolsets/test_verify_tool_urls.py

@Avi-Robusta Avi-Robusta changed the title Multi instance grafana dashboard support [ROB-3973] Multi instance grafana dashboard support May 26, 2026
Comment thread holmes/plugins/toolsets/grafana/common.py
Comment thread holmes/plugins/toolsets/grafana/common.py
Comment thread holmes/plugins/toolsets/grafana/common.py
@Avi-Robusta
Avi-Robusta enabled auto-merge (squash) May 26, 2026 08:10
@Avi-Robusta
Avi-Robusta merged commit 91a67ed into master May 26, 2026
19 of 22 checks passed
@Avi-Robusta
Avi-Robusta deleted the multi-instance-grafana branch May 26, 2026 08:14
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.

2 participants