Skip to content

Remove automated investigation API and consolidate to conversational interface - #1688

Merged
aantn merged 15 commits into
masterfrom
claude/investigate-endpoint-audit-S5ufh
Mar 8, 2026
Merged

aantn merged 15 commits into
masterfrom
claude/investigate-endpoint-audit-S5ufh

Conversation

@aantn

@aantn aantn commented Mar 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR removes the automated investigation API endpoint (/api/investigate) and associated infrastructure, consolidating all issue analysis functionality into the conversational chat interface. The changes simplify the codebase by eliminating duplicate investigation logic and structured output handling that was specific to the investigation workflow.

Key Changes

  • Removed Investigation API: Deleted /api/investigate endpoint and InvestigateRequest/InvestigationResult models from the HTTP API
  • Removed Investigation Module: Deleted holmes/core/investigation.py which contained the investigate_issues() function
  • Removed Structured Output Handling: Deleted holmes/core/investigation_structured_output.py and all related structured output formatting logic (sections, JSON schema generation, markdown parsing)
  • Removed Investigation Prompts: Deleted investigation-specific prompt templates:
    • investigation_procedure.jinja2 (task management and parallel execution rules)
    • investigation_output_format.jinja2 (structured output formatting)
    • generic_ask_for_issue_conversation.jinja2 (issue conversation system prompt)
  • Removed Investigation Tests: Deleted comprehensive test suite for investigation functionality including:
    • tests/llm/test_investigate.py (main investigation tests)
    • tests/test_investigate_structured_output.py (structured output tests)
    • tests/test_issue_investigator.py (issue investigator tests)
    • All 16 investigation test fixtures with Kubernetes manifests and test cases
  • Refactored alertmanager Command: Updated holmes/main.py to use the new _investigate_issue() helper function that calls ToolCallingLLM.prompt_call() directly instead of the removed IssueInvestigator.investigate() method
  • Simplified Conversations Module: Removed build_issue_chat_messages() and related issue-specific conversation logic from holmes/core/conversations.py
  • Updated Tool Calling LLM: Removed investigation-specific parameters and methods from ToolCallingLLM class
  • Updated Config: Removed IssueInvestigator type hint and related factory methods
  • Updated Documentation: Removed /api/investigate endpoint documentation from HTTP API reference
  • Updated General Instructions: Removed conditional inclusion of investigation procedure from prompt templates

Implementation Details

  • The new _investigate_issue() helper in holmes/main.py provides a lightweight wrapper around ToolCallingLLM.prompt_call() for the alertmanager command, replacing the removed investigation infrastructure
  • All investigation functionality is now accessible through the conversational /api/chat endpoint, which provides the same capabilities with a more unified interface
  • Removed the todowrite_enabled conditional logic from prompt templates, simplifying the instruction hierarchy

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP

Summary by CodeRabbit

  • Breaking Changes

    • Removed investigation-focused API endpoints and functionality; chat-based interface is now the primary method for analyzing issues.
    • Removed investigation-related configuration methods from SDK.
  • New Features

    • Added tool approval workflow with decision tracking.
    • Enhanced chat responses with follow-up actions and expanded metadata.
  • Documentation

    • Updated API and SDK documentation to reflect chat-centric interface.
    • Removed investigation-specific examples and endpoint descriptions.
  • Tests

    • Consolidated test suite to focus on chat and ask functionality; investigation test fixtures removed.

claude added 2 commits March 5, 2026 20:18
… endpoints

Purge the investigate endpoint and its issue_chat follow-up endpoint from the
server, keeping only the general /api/chat endpoint. This removes:

- Three server endpoints: /api/investigate, /api/stream/investigate, /api/issue_chat
- Issue-chat-specific models: IssueChatRequest, ToolCallConversationResult,
  IssueInvestigationResult, ConversationInvestigationResult, HolmesConversationHistory,
  HolmesConversationIssueContext, ConversationType, ConversationRequest
- build_issue_chat_messages() and truncate_tool_outputs() from conversations.py
- stream_investigate_formatter() from stream.py
- Related tests and API documentation

The investigation module (holmes/core/investigation.py), InvestigateRequest, and
InvestigationResult models are preserved as they are used by the LLM eval test suite.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
…estigator

Phase 2 of investigation endpoint removal:
- Port all CLI investigate commands (alertmanager, jira, ticket, github,
  pagerduty, opsgenie) to use ToolCallingLLM directly via prompt_call()
- Remove IssueInvestigator class and sections parameter from ToolCallingLLM
- Remove investigation factory methods from Config
- Delete investigation infrastructure: investigation.py,
  investigation_structured_output.py, prompt templates, LLM eval tests
- Clean up all references across test utils, docs, and prompt templates

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

📜 Run @ 181cd33 (#22807112075)

✅ Results of HolmesGPT evals

Automatically triggered by commit 181cd33 on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 31.1s 6 11 $0.2434 124,193 122,409 1,784 98,383 24,026 — 507 —
✅ 101_loki_historical_logs_pod_deleted 30.6s 4 8 $0.2195 83,231 81,280 1,951 58,609 22,671 — 787 —
✅ 111_pod_names_contain_service 31.3s 5 12 $0.2403 106,045 103,937 2,108 80,305 23,632 — 493 —
✅ 112_find_pvcs_by_uuid 21.3s 4 5 $0.1973 80,357 79,169 1,188 56,815 22,354 — 480 —
✅ 12_job_crashing 29.5s 5 11 $0.2424 108,348 106,614 1,734 80,907 25,707 — 463 —
✅ 176_network_policy_blocking_traffic_no_runbooks 39.7s 6 15 $0.2933 142,228 139,483 2,745 112,544 26,939 — 734 —
✅ 24_misconfigured_pvc 33.0s 6 15 $0.2493 124,492 122,501 1,991 98,276 24,225 — 566 —
✅ 43_current_datetime_from_prompt 3.9s 1 — $0.1114 17,499 17,388 111 0 17,388 — 111 —
✅ 61_exact_match_counting 11.0s 3 2 $0.1499 56,334 55,943 391 36,428 19,515 — 172 —
Total 25.7s avg 4.4 avg 9.9 avg $1.9468 842,727 828,724 14,003 622,267 206,457 — 787 —
Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: Success - 61 test/model combinations loaded

Benchmark experiment:

Time comparison (seconds):

Test case This branch master Diff
09_crashpod (opus-4.5) 31.1s 29.5s ±0%
101_loki_historical_logs_pod_deleted (opus-4.5) 30.6s — —
111_pod_names_contain_service (opus-4.5) 31.3s 34.2s ±0%
112_find_pvcs_by_uuid (opus-4.5) 21.3s 31.0s ↓31%
12_job_crashing (opus-4.5) 29.5s 29.7s ±0%
176_network_policy_blocking_traffic_no_runbooks (opus-4.5) 39.7s 42.7s ±0%
24_misconfigured_pvc (opus-4.5) 33.0s 38.7s ↓15%
43_current_datetime_from_prompt (opus-4.5) 3.9s — —
61_exact_match_counting (opus-4.5) 11.0s — —

Benchmark has no cost, total tokens, cached tokens data. Will appear after the next weekly benchmark run.

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
📜 Run @ 4743647 (#22806905076)

✅ Results of HolmesGPT evals

Automatically triggered by commit 4743647 on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 36.4s 7 13 $0.2783 151,370 149,244 2,126 122,830 26,414 — 527 —
✅ 101_loki_historical_logs_pod_deleted 41.5s 6 11 $0.2667 127,043 124,542 2,501 99,755 24,787 — 549 —
✅ 111_pod_names_contain_service 30.3s 5 12 $0.2373 105,240 103,293 1,947 79,282 24,011 — 460 —
✅ 112_find_pvcs_by_uuid 22.8s 4 5 $0.2081 82,117 80,859 1,258 56,911 23,948 — 458 —
✅ 12_job_crashing 40.4s 7 16 $0.3053 166,538 164,050 2,488 135,785 28,265 — 475 —
✅ 176_network_policy_blocking_traffic_no_runbooks 35.5s 7 14 $0.2749 154,851 152,874 1,977 126,866 26,008 — 603 —
✅ 24_misconfigured_pvc 30.2s 5 14 $0.2424 106,052 104,036 2,016 79,469 24,567 — 673 —
✅ 43_current_datetime_from_prompt 4.5s 1 — $0.1114 17,499 17,388 111 0 17,388 — 111 —
✅ 61_exact_match_counting 14.9s 4 4 $0.1669 77,160 76,642 518 56,532 20,110 — 220 —
Total 28.5s avg 5.1 avg 11.1 avg $2.0914 987,870 972,928 14,942 757,430 215,498 — 673 —
Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: Success - 61 test/model combinations loaded

Benchmark experiment:

Time comparison (seconds):

Test case This branch master Diff
09_crashpod (opus-4.5) 36.4s 29.5s ↑23%
101_loki_historical_logs_pod_deleted (opus-4.5) 41.5s — —
111_pod_names_contain_service (opus-4.5) 30.3s 34.2s ↓11%
112_find_pvcs_by_uuid (opus-4.5) 22.8s 31.0s ↓26%
12_job_crashing (opus-4.5) 40.4s 29.7s ↑36%
176_network_policy_blocking_traffic_no_runbooks (opus-4.5) 35.5s 42.7s ↓17%
24_misconfigured_pvc (opus-4.5) 30.2s 38.7s ↓22%
43_current_datetime_from_prompt (opus-4.5) 4.5s — —
61_exact_match_counting (opus-4.5) 14.9s — —

Benchmark has no cost, total tokens, cached tokens data. Will appear after the next weekly benchmark run.

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
📜 Run @ 469c69e (#22806844973)

✅ Results of HolmesGPT evals

Automatically triggered by commit 469c69e on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 30.4s 5 12 $0.2583 109,017 106,971 2,046 79,645 27,326 — 598 —
✅ 101_loki_historical_logs_pod_deleted 50.2s 7 15 $0.3055 160,953 158,023 2,930 131,194 26,829 — 762 —
✅ 111_pod_names_contain_service 27.8s 4 11 $0.2211 82,903 81,116 1,787 57,235 23,881 — 708 —
✅ 112_find_pvcs_by_uuid 24.3s 5 5 $0.2079 101,235 99,961 1,274 78,035 21,926 — 371 —
✅ 12_job_crashing 28.0s 5 11 $0.2399 108,800 107,114 1,686 81,793 25,321 — 441 —
✅ 176_network_policy_blocking_traffic_no_runbooks 36.2s 6 16 $0.2843 138,397 136,069 2,328 108,261 27,808 — 505 —
✅ 24_misconfigured_pvc 37.5s 7 16 $0.2692 146,019 143,853 2,166 118,707 25,146 — 487 —
✅ 43_current_datetime_from_prompt 4.3s 1 — $0.1118 17,515 17,388 127 0 17,388 — 127 —
✅ 61_exact_match_counting 14.8s 4 4 $0.1674 77,229 76,694 535 56,569 20,125 — 237 —
Total 28.2s avg 4.9 avg 11.2 avg $2.0654 942,068 927,189 14,879 711,439 215,750 — 762 —
Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: Success - 61 test/model combinations loaded

Benchmark experiment:

Time comparison (seconds):

Test case This branch master Diff
09_crashpod (opus-4.5) 30.4s 29.5s ±0%
101_loki_historical_logs_pod_deleted (opus-4.5) 50.2s — —
111_pod_names_contain_service (opus-4.5) 27.8s 34.2s ↓19%
112_find_pvcs_by_uuid (opus-4.5) 24.3s 31.0s ↓22%
12_job_crashing (opus-4.5) 28.0s 29.7s ±0%
176_network_policy_blocking_traffic_no_runbooks (opus-4.5) 36.2s 42.7s ↓15%
24_misconfigured_pvc (opus-4.5) 37.5s 38.7s ±0%
43_current_datetime_from_prompt (opus-4.5) 4.3s — —
61_exact_match_counting (opus-4.5) 14.8s — —

Benchmark has no cost, total tokens, cached tokens data. Will appear after the next weekly benchmark run.

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
📜 Run @ 71ccc8d (#22806755030)

✅ Results of HolmesGPT evals

Automatically triggered by commit 71ccc8d on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 27.7s 5 11 $0.2357 107,367 105,584 1,783 81,478 24,106 — 551 —
✅ 101_loki_historical_logs_pod_deleted 38.7s 5 10 $0.2610 111,900 109,542 2,358 83,869 25,673 — 799 —
✅ 111_pod_names_contain_service 30.2s 5 11 $0.2355 104,809 102,953 1,856 78,759 24,194 — 480 —
✅ 112_find_pvcs_by_uuid 27.6s 6 5 $0.2241 120,401 118,955 1,446 96,604 22,351 — 332 —
✅ 12_job_crashing 31.2s 5 12 $0.2466 109,770 107,901 1,869 82,201 25,700 — 515 —
✅ 176_network_policy_blocking_traffic_no_runbooks 37.0s 6 15 $0.2681 131,872 129,670 2,202 103,708 25,962 — 501 —
✅ 24_misconfigured_pvc 27.7s 5 11 $0.2281 102,934 101,191 1,743 77,739 23,452 — 451 —
✅ 43_current_datetime_from_prompt 4.1s 1 — $0.1114 17,499 17,388 111 0 17,388 — 111 —
✅ 61_exact_match_counting 14.0s 4 4 $0.1668 77,138 76,620 518 56,519 20,101 — 220 —
Total 26.5s avg 4.7 avg 9.9 avg $1.9775 883,690 869,804 13,886 660,877 208,927 — 799 —
Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: Success - 61 test/model combinations loaded

Benchmark experiment:

Time comparison (seconds):

Test case This branch master Diff
09_crashpod (opus-4.5) 27.7s 29.5s ±0%
101_loki_historical_logs_pod_deleted (opus-4.5) 38.7s — —
111_pod_names_contain_service (opus-4.5) 30.2s 34.2s ↓12%
112_find_pvcs_by_uuid (opus-4.5) 27.6s 31.0s ↓11%
12_job_crashing (opus-4.5) 31.2s 29.7s ±0%
176_network_policy_blocking_traffic_no_runbooks (opus-4.5) 37.0s 42.7s ↓13%
24_misconfigured_pvc (opus-4.5) 27.7s 38.7s ↓28%
43_current_datetime_from_prompt (opus-4.5) 4.1s — —
61_exact_match_counting (opus-4.5) 14.0s — —

Benchmark has no cost, total tokens, cached tokens data. Will appear after the next weekly benchmark run.

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)
📜 Run @ bf8b253 (#22806538013)

✅ Results of HolmesGPT evals

Automatically triggered by commit bf8b253 on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 34.3s 6 12 $0.2557 126,362 124,419 1,943 98,959 25,460 — 557 —
✅ 101_loki_historical_logs_pod_deleted 42.5s 7 10 $0.2660 148,313 146,113 2,200 122,082 24,031 — 420 —
✅ 111_pod_names_contain_service 34.8s 6 12 $0.2486 124,450 122,445 2,005 98,490 23,955 — 486 —
✅ 112_find_pvcs_by_uuid 26.9s 5 5 $0.2055 101,019 99,832 1,187 77,932 21,900 — 328 —
✅ 12_job_crashing 32.5s 5 11 $0.2379 108,096 106,374 1,722 81,548 24,826 — 453 —
✅ 176_network_policy_blocking_traffic_no_runbooks 37.7s 6 14 $0.2740 131,231 128,936 2,295 102,290 26,646 — 486 —
✅ 24_misconfigured_pvc 34.5s 5 15 $0.2480 107,032 104,891 2,141 79,908 24,983 — 709 —
✅ 43_current_datetime_from_prompt 5.1s 1 — $0.1126 17,545 17,388 157 0 17,388 — 157 —
✅ 61_exact_match_counting 17.1s 4 4 $0.1680 77,292 76,737 555 56,596 20,141 — 257 —
Total 29.5s avg 5.0 avg 10.4 avg $2.0162 941,340 927,135 14,205 717,805 209,330 — 709 —
Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: Success - 61 test/model combinations loaded

Benchmark experiment:

Time comparison (seconds):

Test case This branch master Diff
09_crashpod (opus-4.5) 34.3s 29.5s ↑16%
101_loki_historical_logs_pod_deleted (opus-4.5) 42.5s — —
111_pod_names_contain_service (opus-4.5) 34.8s 34.2s ±0%
112_find_pvcs_by_uuid (opus-4.5) 26.9s 31.0s ↓13%
12_job_crashing (opus-4.5) 32.5s 29.7s ±0%
176_network_policy_blocking_traffic_no_runbooks (opus-4.5) 37.7s 42.7s ↓12%
24_misconfigured_pvc (opus-4.5) 34.5s 38.7s ↓11%
43_current_datetime_from_prompt (opus-4.5) 5.1s — —
61_exact_match_counting (opus-4.5) 17.1s — —

Benchmark has no cost, total tokens, cached tokens data. Will appear after the next weekly benchmark run.

Comparison indicators:

  • ±0% — diff under 10% (within noise threshold)
  • ↑N%/↓N% — diff 10-25%
  • ↑N%/↓N% — diff over 25% (significant)

✅ Results of HolmesGPT evals

Automatically triggered by commit 573ccbf on branch claude/investigate-endpoint-audit-S5ufh

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost Total tokens Input Output Cached Non-cached Reasoning Max output Compactions
✅ 09_crashpod 31.5s 5 12 $0.1608 108,965 106,953 2,012 96,431 10,522 — 598 —
✅ 101_loki_historical_logs_pod_deleted 35.8s 5 10 $0.1660 110,552 108,302 2,250 98,319 9,983 — 777 —
✅ 111_pod_names_contain_service 27.2s 4 11 $0.1235 83,066 81,260 1,806 74,543 6,717 — 729 —
✅ 112_find_pvcs_by_uuid 23.0s 4 5 $0.1003 80,394 79,147 1,247 74,002 5,145 — 477 —
✅ 12_job_crashing 31.6s 6 11 $0.1478 128,718 127,038 1,680 119,596 7,442 — 441 —
✅ 176_network_policy_blocking_traffic_no_runbooks 46.4s 7 16 $0.2047 160,294 157,665 2,629 147,118 10,547 — 801 —
✅ 24_misconfigured_pvc 38.0s 7 17 $0.1772 150,582 148,397 2,185 139,734 8,663 — 746 —
✅ 43_current_datetime_from_prompt 4.8s 1 — $0.0125 17,540 17,388 152 17,379 9 — 152 —
✅ 61_exact_match_counting 11.7s 3 2 $0.0521 56,200 55,848 352 53,169 2,679 — 164 —
Total 27.8s avg 4.7 avg 10.5 avg $1.1451 896,311 881,998 14,313 820,291 61,707 — 801 —

Benchmark comparison unavailable: No ci-benchmark experiments found

Benchmark Comparison Details

Baseline: latest ci-benchmark experiment on master

Status: No ci-benchmark experiments found

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/investigate-endpoint-audit-S5ufh -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
filter: 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!)
filter 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 in automatic regression runs:

Label Effect
evals-tag-<name> Run tests with tag <name> alongside regression
evals-id-<name> Run a specific eval by test ID

Examples: evals-tag-easy, evals-id-09_crashpod

🏷️ Valid tags

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


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/investigate-endpoint-audit-S5ufh -f markers=regression -f filter=

@github-actions

github-actions Bot commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker images ready for 72b09984 (built in 5m 11s)

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

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

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

@netlify

netlify Bot commented Mar 6, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 573ccbf
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/69ad3652aa3aa7000800a4f2
😎 Deploy Preview https://deploy-preview-1688--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.

@github-actions

github-actions Bot commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

🔬 CLI Performance Benchmark

🟡 Startup Time (no LLM)

Measures holmes version execution time (imports + initialization)

Metric PR Master Change
Cold Start 11.44s 10.15s +12.6%
Warm Mean 5.13s 4.56s +12.6%
Warm Min 5.04s 4.55s
Warm Max 5.20s 4.56s

🟡 Full CLI with LLM

Measures holmes ask execution time (OpenRouter + Haiku 4.5)

Metric PR Master Change
Cold Start 30.48s 23.02s +32.4%
Warm Mean 7.46s 7.16s +4.2%
Warm Min 7.36s 6.98s
Warm Max 7.58s 7.36s

PR: 72b09984 | Master: ab74f401 | Iterations: 5

The eval-regression workflow hardcoded tests/llm/test_investigate.py in pytest
args, but this file was deleted in the previous commit. This caused pytest to
error with "file or directory not found", and the `|| true` masked the failure,
resulting in "No eval report was generated" on the PR.

Now dynamically checks which test files exist before passing them to pytest.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 7, 2026 •

Copy link
Copy Markdown
Contributor

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

This pull request removes the investigation feature from the codebase, including the /api/investigate and /api/issue_chat endpoints, the core investigation module, investigation-specific models and prompts, and related test fixtures. The eval regression workflow is updated to dynamically discover tests, removing references to test_investigate.

Changes

Cohort / File(s) Summary
Workflow and Config
.github/workflows/eval-regression.yaml, holmes/config.py
Updated eval regression workflow to dynamically discover test files instead of hardcoding test_investigate; removed create_issue_investigator and create_console_issue_investigator factory methods from Config.
API Documentation
docs/reference/http-api.md, docs/reference/python-sdk.md
Removed /api/investigate, /api/stream/investigate, and /api/issue_chat endpoints from documentation; removed create_console_issue_investigator() from Python SDK docs; shifted remaining docs from investigation/RCA context to chat context.
Core Investigation Modules
holmes/core/investigation.py, holmes/core/investigation_structured_output.py
Deleted entire investigation orchestration module and investigation structured-output utilities, removing investigate_issues, get_investigation_context, and all related section-processing logic.
Core Model Updates
holmes/core/models.py
Removed investigation-related models (InvestigationResult, InvestigateRequest, ToolCallConversationResult, ConversationInvestigationResponse, HolmesConversationHistory, IssueChatRequest); added new models for tool approval workflow (PendingToolApproval, ToolApprovalDecision, FollowUpAction, ChatResponse).
Tool-Calling LLM Refactoring
holmes/core/tool_calling_llm.py, holmes/core/conversations.py
Removed sections parameter from LLM method signatures; deleted IssueInvestigator class; removed investigation-specific imports and build_issue_chat_messages function.
Server Endpoints
server.py, holmes/utils/stream.py
Removed investigation endpoints (investigate_issues, stream_investigate_issues, issue_conversation) and associated request handlers; removed stream_investigate_formatter function.
Prompt Templates
holmes/plugins/prompts/generic_investigation.jinja2, holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2, holmes/plugins/prompts/investigation_output_format.jinja2
Deleted investigation-specific prompt templates; added new _investigation_additions.jinja2 with investigation query guidelines.
Main Entry Point
holmes/main.py
Added internal _investigate_issue helper; refactored investigate commands to use storage-backed AI initialization and per-issue analysis via the new helper instead of direct investigation API calls.
Test Infrastructure
tests/llm/test_investigate.py, tests/llm/conftest.py, tests/llm/utils/..., tests/test_server_endpoints.py, tests/test_investigate_structured_output.py, tests/test_issue_investigator.py
Removed test_investigate.py test file; updated conftest.py to remove investigate test type; removed investigation-related test utilities and fixtures; removed API test for /api/issue_chat; removed structured-output tests.
Test Fixtures and Evaluation
tests/llm/fixtures/test_investigate/*, tests/llm/utils/reporting/github_reporter.py, tests/test_ai_safety_prompt.py, tests/test_prompt.py, Makefile
Deleted entire test_investigate fixture directory hierarchy (including 01_oom_kill, 02_crashloop_backoff, 03_cpu_throttling, 05_crashpod, 06_job_failure, 07_job_syntax_error, 09_high_latency, 10_KubeDeploymentReplicasMismatch, 15_dns_resolution, 16_dns_resolution_no_tool); removed investigate statistics from github_reporter; removed investigation templates from safety checks; removed test-llm-investigate make target.
Braintrust and Langfuse Integration
tests/llm/utils/braintrust.py, tests/llm/utils/langfuse.py, tests/llm/utils/property_manager.py
Updated braintrust logging to support CompactionResult and LLMResult; removed InvestigateTestCase handling; updated Langfuse to remove InvestigateTestCase input extraction; simplified property_manager result type annotations.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Add checks logic #1377 — Both PRs modify the public API/test surface to remove/replace investigation flows; PR #1377 introduces a new "checks" subsystem that replaces investigate functionality.
  • Add HTTP API documentation #592 — PR #592 introduced the /api/investigate and /api/issue_chat endpoints that this PR removes.
  • ROB-2405: document stream events #1073 — Both PRs modify the API documentation with overlapping changes to SSE/streaming endpoint references and investigation-specific field removals.

Suggested reviewers

  • arikalon1
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective: removing the automated investigation API and consolidating functionality to the conversational interface.

✏️ 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.

@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

Caution

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

⚠️ Outside diff range comments (1)
tests/llm/utils/braintrust.py (1)

303-313: ⚠️ Potential issue | 🟠 Major

AttributeError when test_case lacks conversation_history attribute.

HolmesTestCase (per its definition with extra="forbid") does not have a conversation_history attribute. When test_case is not an AskHolmesTestCase, this line will raise AttributeError. Line 259 correctly uses hasattr for the same check.

Proposed fix using hasattr guard
-    elif test_case.conversation_history:  # compaction test case
+    elif hasattr(test_case, "conversation_history") and test_case.conversation_history:  # compaction test case
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 303 - 313, The branch using "elif
test_case.conversation_history" will raise AttributeError for HolmesTestCase
instances that don't have conversation_history; change the guard to use
hasattr(test_case, "conversation_history") and ensure it also checks truthiness
(e.g., if hasattr(test_case, "conversation_history") and
test_case.conversation_history:) so only AskHolmesTestCase-like objects enter
that block; update the condition in the function where the block containing
format_conversation_as_markdown and expected assignment runs to use this
hasattr-based check.
🧹 Nitpick comments (2)
tests/llm/utils/braintrust.py (1)

209-219: Minor: Redundant conditional after hasattr check.

When hasattr(result, "result") returns True, result is necessarily truthy, making the ternary condition if result redundant.

Simplified version
     if error:
-        if hasattr(result, "result"):
-            output = result.result if result else str(error)
-        else:
-            output = str(error)
+        output = result.result if result and hasattr(result, "result") else str(error)
         scores = scores or {}
     else:
-        if hasattr(result, "result"):
-            output = result.result if result else ""
-        else:
-            output = ""
+        output = result.result if result and hasattr(result, "result") else ""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 209 - 219, The conditional that
checks hasattr(result, "result") is followed by redundant ternary checks of
result (e.g., output = result.result if result else ...); simplify by removing
the needless `if result` branches and directly use result.result when
hasattr(result, "result") is true, updating both the error and non-error
branches (variables: error, result, scores, and output) so that output is
assigned directly from result.result (or the appropriate fallback string when no
result attribute exists) and keep the existing scores = scores or {} assignment.
holmes/main.py (1)

492-496: Consider using logging.exception for exception logging.

The static analysis tool suggests using logging.exception instead of logging.error with exc_info=e. While functionally similar, logging.exception is more idiomatic when logging exceptions.

♻️ Suggested change
-            logging.error("Failed to fetch issues from alertmanager", exc_info=e)
+            logging.exception("Failed to fetch issues from alertmanager")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 492 - 496, The except block handling
source.fetch_issues() currently uses logging.error with exc_info=e; change it to
use logging.exception to be more idiomatic: in the try/except around
source.fetch_issues() (where issues is assigned) replace logging.error("Failed
to fetch issues from alertmanager", exc_info=e) with a logging.exception call
that logs the same message so the current exception info is included
automatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/eval-regression.yaml:
- Around line 664-669: The workflow builds TEST_FILES by appending two potential
test paths into the TEST_FILES array then sets PYTEST_ARGS using
"${TEST_FILES[@]}", but it doesn't guard for an empty TEST_FILES so pytest may
be invoked with no paths; change the workflow to check the length of TEST_FILES
(e.g., test if ${`#TEST_FILES`[@]} -eq 0) and if empty, fail the job early with a
clear message (or exit 1) before constructing/using PYTEST_ARGS and running
pytest with EVAL_MARKER_EXPR; apply the same guard where TEST_FILES/PYTEST_ARGS
is repeated later in the file.

In `@holmes/main.py`:
- Around line 630-632: Wrap uses of result.result (an Optional[str]) with a
null-safe expression before calling .replace(), e.g., compute a safe string like
safe_text = result.result or "" (or a clear fallback like "<no result>") and
call safe_text.replace(...); update the three occurrences shown (the
console.print(Markdown(...)) call where Rule() and f"AI analysis of {issue.url}"
are printed) and apply the same defensive fix to the other similar sites (the
occurrences referenced at lines 745, 825, 905, and 982) so Markdown(...) never
receives None and .replace() cannot raise AttributeError.

---

Outside diff comments:
In `@tests/llm/utils/braintrust.py`:
- Around line 303-313: The branch using "elif test_case.conversation_history"
will raise AttributeError for HolmesTestCase instances that don't have
conversation_history; change the guard to use hasattr(test_case,
"conversation_history") and ensure it also checks truthiness (e.g., if
hasattr(test_case, "conversation_history") and test_case.conversation_history:)
so only AskHolmesTestCase-like objects enter that block; update the condition in
the function where the block containing format_conversation_as_markdown and
expected assignment runs to use this hasattr-based check.

---

Nitpick comments:
In `@holmes/main.py`:
- Around line 492-496: The except block handling source.fetch_issues() currently
uses logging.error with exc_info=e; change it to use logging.exception to be
more idiomatic: in the try/except around source.fetch_issues() (where issues is
assigned) replace logging.error("Failed to fetch issues from alertmanager",
exc_info=e) with a logging.exception call that logs the same message so the
current exception info is included automatically.

In `@tests/llm/utils/braintrust.py`:
- Around line 209-219: The conditional that checks hasattr(result, "result") is
followed by redundant ternary checks of result (e.g., output = result.result if
result else ...); simplify by removing the needless `if result` branches and
directly use result.result when hasattr(result, "result") is true, updating both
the error and non-error branches (variables: error, result, scores, and output)
so that output is assigned directly from result.result (or the appropriate
fallback string when no result attribute exists) and keep the existing scores =
scores or {} assignment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 014700cc-7619-44e4-a4ef-bdf9e3a98dab

📥 Commits

Reviewing files that changed from the base of the PR and between 777eb56 and 9ef121f.

📒 Files selected for processing (81)
  • .github/workflows/eval-regression.yaml
  • docs/reference/http-api.md
  • docs/reference/python-sdk.md
  • holmes/config.py
  • holmes/core/conversations.py
  • holmes/core/investigation.py
  • holmes/core/investigation_structured_output.py
  • holmes/core/models.py
  • holmes/core/tool_calling_llm.py
  • holmes/main.py
  • holmes/plugins/prompts/_general_instructions.jinja2
  • holmes/plugins/prompts/_noflag_general_instructions.jinja2
  • holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2
  • holmes/plugins/prompts/generic_investigation.jinja2
  • holmes/plugins/prompts/investigation_output_format.jinja2
  • holmes/plugins/prompts/investigation_procedure.jinja2
  • holmes/utils/stream.py
  • server.py
  • tests/core/test_prompt.py
  • tests/llm/fixtures/test_investigate/01_oom_kill/fast_oom_deployment.yaml
  • tests/llm/fixtures/test_investigate/01_oom_kill/investigate_request.json
  • tests/llm/fixtures/test_investigate/01_oom_kill/issue_data.json
  • tests/llm/fixtures/test_investigate/01_oom_kill/resource_instructions.json
  • tests/llm/fixtures/test_investigate/01_oom_kill/test_case.yaml
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/investigate_request.json
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/issue_data.json
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/resource_instructions.json
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/test_case.yaml
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/investigate_request.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/issue_data.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/manifest.yaml
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/resource_instructions.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/test_case.yaml
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/toolsets.yaml
  • tests/llm/fixtures/test_investigate/05_crashpod/investigate_request.json
  • tests/llm/fixtures/test_investigate/05_crashpod/issue_data.json
  • tests/llm/fixtures/test_investigate/05_crashpod/resource_instructions.json
  • tests/llm/fixtures/test_investigate/05_crashpod/test_case.yaml
  • tests/llm/fixtures/test_investigate/06_job_failure/investigate_request.json
  • tests/llm/fixtures/test_investigate/06_job_failure/issue_data.json
  • tests/llm/fixtures/test_investigate/06_job_failure/resource_instructions.json
  • tests/llm/fixtures/test_investigate/06_job_failure/test_case.yaml
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/investigate_request.json
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/issue_data.json
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/resource_instructions.json
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/test_case.yaml
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/Dockerfile
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/app.py
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/build.sh
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/manifest.yaml
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/requirements.txt
  • tests/llm/fixtures/test_investigate/09_high_latency/investigate_request.json
  • tests/llm/fixtures/test_investigate/09_high_latency/issue_data.json
  • tests/llm/fixtures/test_investigate/09_high_latency/resource_instructions.json
  • tests/llm/fixtures/test_investigate/09_high_latency/test_case.yaml
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/investigate_request.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/issue_data.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/resource_instructions.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/test_case.yaml
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/toolsets.yaml
  • tests/llm/fixtures/test_investigate/15_dns_resolution/investigate_request.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/issue_data.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/manifest.yaml
  • tests/llm/fixtures/test_investigate/15_dns_resolution/resource_instructions.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/test_case.yaml
  • tests/llm/fixtures/test_investigate/15_dns_resolution/toolsets.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/investigate_request.json
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/issue_data.json
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/manifest.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/resource_instructions.json
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/test_case.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/toolsets.yaml
  • tests/llm/test_investigate.py
  • tests/llm/utils/braintrust.py
  • tests/llm/utils/langfuse.py
  • tests/llm/utils/property_manager.py
  • tests/llm/utils/test_case_utils.py
  • tests/test_ai_safety_prompt.py
  • tests/test_investigate_structured_output.py
  • tests/test_issue_investigator.py
  • tests/test_server_endpoints.py
💤 Files with no reviewable changes (72)
  • tests/llm/fixtures/test_investigate/05_crashpod/resource_instructions.json
  • tests/llm/fixtures/test_investigate/06_job_failure/test_case.yaml
  • holmes/plugins/prompts/generic_investigation.jinja2
  • tests/llm/fixtures/test_investigate/01_oom_kill/test_case.yaml
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/resource_instructions.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/test_case.yaml
  • holmes/utils/stream.py
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/test_case.yaml
  • tests/llm/fixtures/test_investigate/15_dns_resolution/issue_data.json
  • tests/llm/fixtures/test_investigate/06_job_failure/issue_data.json
  • holmes/plugins/prompts/investigation_procedure.jinja2
  • holmes/plugins/prompts/_general_instructions.jinja2
  • tests/llm/fixtures/test_investigate/15_dns_resolution/toolsets.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/issue_data.json
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/investigate_request.json
  • tests/llm/fixtures/test_investigate/05_crashpod/issue_data.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/manifest.yaml
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/test_case.yaml
  • holmes/core/tool_calling_llm.py
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/manifest.yaml
  • tests/llm/fixtures/test_investigate/05_crashpod/investigate_request.json
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/app.py
  • holmes/core/investigation.py
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/test_case.yaml
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/requirements.txt
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/test_case.yaml
  • tests/llm/fixtures/test_investigate/01_oom_kill/investigate_request.json
  • holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2
  • tests/test_ai_safety_prompt.py
  • tests/llm/fixtures/test_investigate/05_crashpod/test_case.yaml
  • tests/llm/fixtures/test_investigate/01_oom_kill/resource_instructions.json
  • tests/llm/fixtures/test_investigate/09_high_latency/test_case.yaml
  • tests/llm/fixtures/test_investigate/06_job_failure/resource_instructions.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/resource_instructions.json
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/toolsets.yaml
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/issue_data.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/manifest.yaml
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/toolsets.yaml
  • tests/test_investigate_structured_output.py
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/test_case.yaml
  • tests/llm/fixtures/test_investigate/09_high_latency/investigate_request.json
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/investigate_request.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/issue_data.json
  • holmes/plugins/prompts/investigation_output_format.jinja2
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/issue_data.json
  • tests/core/test_prompt.py
  • tests/llm/fixtures/test_investigate/09_high_latency/issue_data.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/resource_instructions.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/investigate_request.json
  • tests/llm/fixtures/test_investigate/07_job_syntax_error/resource_instructions.json
  • tests/test_server_endpoints.py
  • tests/llm/fixtures/test_investigate/01_oom_kill/issue_data.json
  • tests/llm/fixtures/test_investigate/09_high_latency/resource_instructions.json
  • tests/llm/fixtures/test_investigate/15_dns_resolution/investigate_request.json
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/Dockerfile
  • holmes/core/conversations.py
  • tests/llm/fixtures/test_investigate/01_oom_kill/fast_oom_deployment.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/resource_instructions.json
  • tests/llm/fixtures/test_investigate/02_crashloop_backoff/investigate_request.json
  • tests/llm/fixtures/test_investigate/06_job_failure/investigate_request.json
  • tests/llm/fixtures/test_investigate/09_high_latency/helm/build.sh
  • tests/test_issue_investigator.py
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/issue_data.json
  • tests/llm/fixtures/test_investigate/03_cpu_throttling/resource_instructions.json
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/toolsets.yaml
  • tests/llm/fixtures/test_investigate/16_dns_resolution_no_tool/manifest.yaml
  • holmes/core/models.py
  • tests/llm/utils/test_case_utils.py
  • tests/llm/utils/langfuse.py
  • tests/llm/test_investigate.py
  • tests/llm/fixtures/test_investigate/10_KubeDeploymentReplicasMismatch/investigate_request.json
  • holmes/core/investigation_structured_output.py

Comment thread .github/workflows/eval-regression.yaml Outdated
Comment thread holmes/main.py
…moval

- Makefile: remove broken test-llm-investigate target
- conftest.py: remove test_investigate from LLM_TEST_TYPES, is_llm_test(),
  and test type detection logic
- github_reporter.py: remove dead investigate_* counters and reporting
- eval-regression.yaml: remove investigate fixtures from /list command
- property_manager.py, braintrust.py: update stale comments

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
tests/llm/utils/reporting/github_reporter.py (1)

174-188: ⚠️ Potential issue | 🟡 Minor

Remove dead code from conftest.py that detects "investigate" test types.

The PR removes investigation functionality from github_reporter.py, but conftest.py still contains code at lines 810–811 that checks for test_investigate in nodeids and assigns test_type = "investigate". Since no test_investigate test functions exist in the codebase, this is dead code that should be removed as part of the cleanup. Additionally, the summary counting logic (lines 174–188) only processes test_type == "ask" results, while the detailed table (lines 214–278) iterates over all sorted_results. While this inconsistency is currently masked by the absence of investigate tests, removing the dead code in conftest.py will clarify the intended behavior: only "ask" and "unknown" test types can be produced, and "unknown" results should either be filtered or explicitly handled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/reporting/github_reporter.py` around lines 174 - 188, Remove
the dead "investigate" test_type detection in conftest (the code that looks for
"test_investigate" in nodeids and sets result["test_type"] = "investigate");
instead ensure conftest only emits "ask" or "unknown" test_type values. Then
update the summary loop that iterates sorted_results and constructs TestStatus
(the block using TestStatus(result)) to either filter out result["test_type"] !=
"ask" before counting or explicitly handle the "unknown" type (e.g., increment
an unknown counter or skip), so summary counters (ask_holmes_total,
ask_holmes_passed, etc.) stay consistent with the detailed table output. Ensure
changes reference the same result dict keys used by TestStatus and
sorted_results so behavior remains consistent.
tests/llm/utils/braintrust.py (1)

334-341: ⚠️ Potential issue | 🟡 Minor

Fix the stale get_braintrust_url() argument docs.

The docstring still lists test_suite, test_id, and test_name, but the function only accepts span_id and root_span_id. Keeping removed parameters in the args list makes the helper look callable in ways it no longer supports. As per coding guidelines, "Write clear, concise comments that explain 'why' rather than 'what'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 334 - 341, The docstring for
get_braintrust_url is stale: it still documents test_suite, test_id, and
test_name even though the function signature only accepts span_id and
root_span_id; update the docstring to accurately describe the current parameters
(span_id and root_span_id), their types/format, and the return value (the
generated Braintrust URL), or remove the obsolete Args section and replace it
with a concise description of what get_braintrust_url does and the two valid
parameters.
🧹 Nitpick comments (2)
tests/llm/conftest.py (2)

887-893: Prefer explaining the clean_test_case_id contract over naming the current producer file.

Hardcoding test_ask_holmes.py in the comment will go stale as soon as another LLM test sets the same property. A short “why” comment about keeping reporting stable across parameterized nodeids is enough here.

As per coding guidelines "Write clear, concise comments that explain 'why' rather than 'what'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/conftest.py` around lines 887 - 893, Replace the hardcoded
reference to "test_ask_holmes.py" with a concise explanation of the
clean_test_case_id contract: state that tests set
request.node.user_properties.append(("clean_test_case_id", test_case.id)) to
provide a stable test-case identifier without model-parameter suffixes (so
reporting remains stable across parameterized nodeids), and note the contract’s
caveats (it won’t be present for skipped tests or tests that fail before
user_properties are set); keep the comment short and focused on this “why”
explanation rather than naming the producer file.

48-48: Make test classification a single typed source of truth.

LLM_TEST_TYPES was updated here, but is_llm_test() still hardcodes the substring and _collect_test_results_from_stats() keeps its own test_ask_holmes/test_investigate branches. That leaves multiple classifiers to maintain in one file, so the next rename or new eval module will drift again. A typed mapping used by both helpers would make this change complete.

♻️ Suggested direction
-LLM_TEST_TYPES = ["test_ask_holmes"]
+LLM_TEST_TYPE_BY_NODEID: dict[str, str] = {
+    "test_ask_holmes": "ask",
+}

 def is_llm_test(nodeid: str) -> bool:
     """Check if a test nodeid is for an LLM test."""
-    return "test_ask_holmes" in nodeid
+    return any(name in nodeid for name in LLM_TEST_TYPE_BY_NODEID)

Then reuse the same mapping when deriving test_type in _collect_test_results_from_stats().

As per coding guidelines "Type hints are required throughout the codebase (mypy configuration in pyproject.toml)".

Also applies to: 100-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/conftest.py` at line 48, LLM test classification is duplicated:
LLM_TEST_TYPES, is_llm_test(), and _collect_test_results_from_stats() each
hardcode test kind strings; consolidate into a single typed mapping/enum
(replace LLM_TEST_TYPES with a Mapping[str, str] or TypedDict/Enum) and have
both is_llm_test() and _collect_test_results_from_stats() reference that single
source to derive test_type (update function signatures and internal logic to use
the mapping/enum), and add type hints for the new mapping and for
is_llm_test()/_collect_test_results_from_stats() return types per project typing
rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/llm/utils/braintrust.py`:
- Around line 192-199: The docstring for log_to_braintrust() claims it only
handles AskHolmesTestCase/LLMResult but the function still contains explicit
compaction branches and is invoked by compaction tests; update the contract to
match behaviour by either (A) broadening the docstring to describe both
multi-test and compaction flows (mentioning it accepts compaction inputs and how
they are compacted) or (B) remove/encapsulate the compaction-specific branches
and calls so the function truly only accepts AskHolmesTestCase/LLMResult; locate
the branching logic inside log_to_braintrust and align callers/tests accordingly
(adjust compaction tests to the new helper if you choose B).
- Line 188: Change the local import and loosened typing for the helper's
`result` parameter: move the import(s) that bring in LLMResult and
CompactionResult from inside the function to module top-level imports, then
replace the annotation `Optional[Any]` on the `result` parameter with
`Optional[Union[LLMResult, CompactionResult]]` (and add `from typing import
Optional, Union` at module scope if not present). This keeps imports compliant
with the repo rule and restores precise typing for the helper that accepts
either an LLMResult or a CompactionResult.

---

Outside diff comments:
In `@tests/llm/utils/braintrust.py`:
- Around line 334-341: The docstring for get_braintrust_url is stale: it still
documents test_suite, test_id, and test_name even though the function signature
only accepts span_id and root_span_id; update the docstring to accurately
describe the current parameters (span_id and root_span_id), their types/format,
and the return value (the generated Braintrust URL), or remove the obsolete Args
section and replace it with a concise description of what get_braintrust_url
does and the two valid parameters.

In `@tests/llm/utils/reporting/github_reporter.py`:
- Around line 174-188: Remove the dead "investigate" test_type detection in
conftest (the code that looks for "test_investigate" in nodeids and sets
result["test_type"] = "investigate"); instead ensure conftest only emits "ask"
or "unknown" test_type values. Then update the summary loop that iterates
sorted_results and constructs TestStatus (the block using TestStatus(result)) to
either filter out result["test_type"] != "ask" before counting or explicitly
handle the "unknown" type (e.g., increment an unknown counter or skip), so
summary counters (ask_holmes_total, ask_holmes_passed, etc.) stay consistent
with the detailed table output. Ensure changes reference the same result dict
keys used by TestStatus and sorted_results so behavior remains consistent.

---

Nitpick comments:
In `@tests/llm/conftest.py`:
- Around line 887-893: Replace the hardcoded reference to "test_ask_holmes.py"
with a concise explanation of the clean_test_case_id contract: state that tests
set request.node.user_properties.append(("clean_test_case_id", test_case.id)) to
provide a stable test-case identifier without model-parameter suffixes (so
reporting remains stable across parameterized nodeids), and note the contract’s
caveats (it won’t be present for skipped tests or tests that fail before
user_properties are set); keep the comment short and focused on this “why”
explanation rather than naming the producer file.
- Line 48: LLM test classification is duplicated: LLM_TEST_TYPES, is_llm_test(),
and _collect_test_results_from_stats() each hardcode test kind strings;
consolidate into a single typed mapping/enum (replace LLM_TEST_TYPES with a
Mapping[str, str] or TypedDict/Enum) and have both is_llm_test() and
_collect_test_results_from_stats() reference that single source to derive
test_type (update function signatures and internal logic to use the
mapping/enum), and add type hints for the new mapping and for
is_llm_test()/_collect_test_results_from_stats() return types per project typing
rules.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d85c8249-16eb-43a0-a048-40ca2c88334e

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef121f and 91d055b.

📒 Files selected for processing (6)
  • .github/workflows/eval-regression.yaml
  • Makefile
  • tests/llm/conftest.py
  • tests/llm/utils/braintrust.py
  • tests/llm/utils/property_manager.py
  • tests/llm/utils/reporting/github_reporter.py
💤 Files with no reviewable changes (1)
  • Makefile
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/eval-regression.yaml
  • tests/llm/utils/property_manager.py

Comment thread tests/llm/utils/braintrust.py Outdated
Comment thread tests/llm/utils/braintrust.py Outdated
aantn and others added 3 commits March 7, 2026 22:11
- Move LLMResult and AskHolmesTestCase imports to module top-level
- Type result parameter as Optional[Union[LLMResult, CompactionResult]]
- Update docstrings to reflect both ask_holmes and compaction flows
- Fix get_braintrust_url docstring listing nonexistent params
- Add guard for empty TEST_FILES array in eval workflow to fail early

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
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)
tests/llm/utils/braintrust.py (1)

344-351: ⚠️ Potential issue | 🟡 Minor

Remove parameters from the docstring that are no longer in the signature.

get_braintrust_url() only accepts span_id and root_span_id, but the docstring still lists test_suite, test_id, and test_name. That leaves the public contract stale.

As per coding guidelines, "Write clear, concise comments that explain 'why' rather than 'what'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 344 - 351, The docstring for
get_braintrust_url is stale—remove the outdated parameters test_suite, test_id,
and test_name from the docstring and update it to match the current signature
(only span_id and root_span_id); rewrite the docstring to briefly describe the
function's purpose and include parameter entries only for span_id and
root_span_id (with their optional status) and the return value so the public
contract is accurate and concise.
♻️ Duplicate comments (7)
holmes/main.py (5)

807-810: ⚠️ Potential issue | 🟡 Minor

Apply null check for result.result here as well (GitHub command).

Same issue - result.result could be None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 807 - 810, The code prints
Markdown(result.result...) without checking for None; update the block that
calls console.print(Markdown(...)) to guard against result.result being None
(same pattern as other checks) by using a conditional or coalesce to a safe
string (e.g., "" or "No analysis available") before calling Markdown,
referencing result.result, Markdown(...), console.print(...) and issue.url so
the printed analysis never receives None.

725-730: ⚠️ Potential issue | 🟡 Minor

Apply null check for result.result here as well.

Same issue as flagged above - result.result could be None.

🛡️ Defensive fix
         console.print(Rule())
         console.print(
             f"[bold green]AI analysis of {issue_to_investigate.url} {prompt}[/bold green]"
         )
-        console.print(result.result.replace("\n", "\n\n"), style="bold green")  # type: ignore
+        analysis_text = result.result or "No analysis available"
+        console.print(analysis_text.replace("\n", "\n\n"), style="bold green")
         console.print(Rule())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 725 - 730, The print block uses result.result
without a null check; update the block around
console.print(result.result.replace(...)) to first test if result.result is not
None (e.g., if result.result: or if result.result is not None:) and only call
.replace when non-null, otherwise print a fallback message or skip that print;
reference the variable result and the console.print calls in the same block so
you preserve the surrounding Rule() prints.

887-890: ⚠️ Potential issue | 🟡 Minor

Apply null check for result.result here as well (PagerDuty command).

Same issue - result.result could be None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 887 - 890, The print block assumes result.result
is a string but it can be None; update the PagerDuty command output logic (the
block that prints Rule(), console.print(f"AI analysis of {issue.url}"), and
console.print(Markdown(result.result.replace("\n", "\n\n")), ... )) to
null-check result.result first and use a safe fallback (e.g., skip the Markdown
print, show a placeholder message like "No analysis available", or coalesce to
an empty string) so you never call .replace on None; locate the variable named
result in main.py and guard its .result attribute before passing it to Markdown
or calling .replace.

614-616: ⚠️ Potential issue | 🟡 Minor

Add null check for result.result before calling .replace().

LLMResult.result is typed as Optional[str], so calling .replace() directly could raise AttributeError if the LLM fails to produce a result.

🛡️ Defensive fix
             console.print(Rule())
             console.print(f"[bold green]AI analysis of {issue.url}[/bold green]")
-            console.print(Markdown(result.result.replace("\n", "\n\n")), style="bold green")  # type: ignore
+            analysis_text = result.result or "No analysis available"
+            console.print(Markdown(analysis_text.replace("\n", "\n\n")), style="bold green")
             console.print(Rule())

The same pattern appears at lines 729, 809, 889, and 966 - apply a similar fix to those locations as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 614 - 616, Wrap uses of result.result before
calling .replace() with a null-safe check and a fallback string: check if
result.result is truthy (or use "result_text = result.result or '<no result>'")
then call result_text.replace(...). Update the three occurrences in the same
function where console.print(Markdown(result.result.replace(...))) is used (the
calls printing AI analysis via console.print and Markdown) and replicate this
null-safe pattern for the other locations that use LLMResult.result at the same
file (the instances around the existing console.print lines and the similar
usages at the other noted spots).

964-967: ⚠️ Potential issue | 🟡 Minor

Apply null check for result.result here as well (OpsGenie command).

Same issue - result.result could be None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 964 - 967, The code prints
Markdown(Markdown(result.result.replace("\n", "\n\n"))) without guarding against
result.result being None; update the block that uses result (the variable named
result in holmes/main.py) to null-check result.result before calling .replace
and passing to Markdown/console.print (e.g., use a safe fallback like an empty
string or "No result available" when result.result is None) so Markdown() always
receives a str and avoids AttributeError.
tests/llm/utils/braintrust.py (2)

188-202: 🛠️ Refactor suggestion | 🟠 Major

Move the local import to module scope and give result a real type.

Optional[Any] turns off checking in a shared helper, and the AskHolmesTestCase import inside the function still violates the repo’s Python import rule. Please import the supported types at module scope and annotate result precisely instead of documenting it in a comment.

As per coding guidelines, "Type hints required (mypy configuration in pyproject.toml)" and "ALWAYS place Python imports at the top of the file, not inside functions or methods".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 188 - 202, Move the local import
"from tests.llm.utils.test_case_utils import AskHolmesTestCase" to the top of
the module and add a precise type for the result parameter instead of
Optional[Any]; import the concrete LLM result type used across tests (e.g.,
LLMResult) at module scope and annotate the function signature as result:
Optional[LLMResult] (or the actual class name used in your codebase), keeping
scores: Optional[dict] and error: Optional[Exception] as-is; update imports at
module scope and adjust any references inside the logging helper accordingly.

192-199: ⚠️ Potential issue | 🟡 Minor

Keep the docstring aligned with the compaction branch below.

This docstring now says the helper only handles AskHolmesTestCase/LLMResult, but the function still has explicit CompactionResult handling later on. Either narrow the implementation too, or document both supported flows here.

As per coding guidelines, "Write clear, concise comments that explain 'why' rather than 'what'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/utils/braintrust.py` around lines 192 - 199, The docstring for the
shared Braintrust logging helper is out of sync with the implementation: update
the docstring of the function (the helper that takes eval_span) to document both
supported flows or narrow the code to match the docstring; specifically either
add CompactionResult (CompactionResult) to the documented accepted types along
with AskHolmesTestCase and LLMResult, or remove the explicit CompactionResult
handling in the function body so it only handles
AskHolmesTestCase/LLMResult—adjust the narrative to explain why both flows are
supported and what each path does, and reference the symbols AskHolmesTestCase,
LLMResult, CompactionResult and the helper function name so reviewers can locate
the changes.
🧹 Nitpick comments (3)
server.py (1)

59-61: Remove the stale historical comment.

# removed: add_runbooks_to_user_prompt no longer explains anything about the current code path and will just drift over time. Dropping it keeps this import section focused on the behavior that still exists. As per coding guidelines, "Write clear, concise comments that explain 'why' rather than 'what'".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server.py` around lines 59 - 61, Remove the stale inline comment "# removed:
add_runbooks_to_user_prompt" near the import of stream_chat_formatter in
server.py; locate the import statement "from holmes.utils.stream import
stream_chat_formatter" and delete only the historical comment line so the import
section contains current, purposeful comments only.
holmes/main.py (2)

476-480: Use logging.exception instead of logging.error with exc_info.

When logging exceptions in an except block, logging.exception is the idiomatic choice as it automatically includes exception info.

♻️ Suggested fix
         try:
             issues = source.fetch_issues()
         except Exception as e:
-            logging.error("Failed to fetch issues from alertmanager", exc_info=e)
+            logging.exception("Failed to fetch issues from alertmanager")
             return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 476 - 480, Replace the logging call in the
except block that handles errors from source.fetch_issues(): instead of calling
logging.error(..., exc_info=e), use logging.exception("Failed to fetch issues
from alertmanager") so the exception traceback is included idiomatically; locate
the try/except around source.fetch_issues() in holmes/main.py and swap the
logging.error call for logging.exception within that except block.

614-627: Consider extracting repeated result display logic into a helper.

The pattern of printing rules, AI analysis header, markdown result, and update handling is repeated across jira, github, pagerduty, and opsgenie commands. This could be consolidated into a helper function similar to how _investigate_issue() was extracted.

This is a nice-to-have for future maintainability and not blocking for this PR.

Also applies to: 807-817, 887-897, 964-974

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 614 - 627, The block that prints rules, header,
Markdown result, handles update/write_back_result and appends to results is
duplicated; extract it into a helper (e.g., _display_issue_result or
display_result) that accepts parameters (issue, result, update, source, console,
results) and performs console.print(Rule()), header, Markdown rendering,
optional source.write_back_result(issue.id, result) with console messages, and
results.append({"issue": issue.model_dump(), "result": result.model_dump()});
then replace the repeated blocks in jira/github/pagerduty/opsgenie command
handlers (the repeated printing/update code shown around the Rule/AI
analysis/Markdown/update logic and the other mentioned ranges) with calls to
that helper so behavior and side effects remain identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@tests/llm/utils/braintrust.py`:
- Around line 344-351: The docstring for get_braintrust_url is stale—remove the
outdated parameters test_suite, test_id, and test_name from the docstring and
update it to match the current signature (only span_id and root_span_id);
rewrite the docstring to briefly describe the function's purpose and include
parameter entries only for span_id and root_span_id (with their optional status)
and the return value so the public contract is accurate and concise.

---

Duplicate comments:
In `@holmes/main.py`:
- Around line 807-810: The code prints Markdown(result.result...) without
checking for None; update the block that calls console.print(Markdown(...)) to
guard against result.result being None (same pattern as other checks) by using a
conditional or coalesce to a safe string (e.g., "" or "No analysis available")
before calling Markdown, referencing result.result, Markdown(...),
console.print(...) and issue.url so the printed analysis never receives None.
- Around line 725-730: The print block uses result.result without a null check;
update the block around console.print(result.result.replace(...)) to first test
if result.result is not None (e.g., if result.result: or if result.result is not
None:) and only call .replace when non-null, otherwise print a fallback message
or skip that print; reference the variable result and the console.print calls in
the same block so you preserve the surrounding Rule() prints.
- Around line 887-890: The print block assumes result.result is a string but it
can be None; update the PagerDuty command output logic (the block that prints
Rule(), console.print(f"AI analysis of {issue.url}"), and
console.print(Markdown(result.result.replace("\n", "\n\n")), ... )) to
null-check result.result first and use a safe fallback (e.g., skip the Markdown
print, show a placeholder message like "No analysis available", or coalesce to
an empty string) so you never call .replace on None; locate the variable named
result in main.py and guard its .result attribute before passing it to Markdown
or calling .replace.
- Around line 614-616: Wrap uses of result.result before calling .replace() with
a null-safe check and a fallback string: check if result.result is truthy (or
use "result_text = result.result or '<no result>'") then call
result_text.replace(...). Update the three occurrences in the same function
where console.print(Markdown(result.result.replace(...))) is used (the calls
printing AI analysis via console.print and Markdown) and replicate this
null-safe pattern for the other locations that use LLMResult.result at the same
file (the instances around the existing console.print lines and the similar
usages at the other noted spots).
- Around line 964-967: The code prints
Markdown(Markdown(result.result.replace("\n", "\n\n"))) without guarding against
result.result being None; update the block that uses result (the variable named
result in holmes/main.py) to null-check result.result before calling .replace
and passing to Markdown/console.print (e.g., use a safe fallback like an empty
string or "No result available" when result.result is None) so Markdown() always
receives a str and avoids AttributeError.

In `@tests/llm/utils/braintrust.py`:
- Around line 188-202: Move the local import "from
tests.llm.utils.test_case_utils import AskHolmesTestCase" to the top of the
module and add a precise type for the result parameter instead of Optional[Any];
import the concrete LLM result type used across tests (e.g., LLMResult) at
module scope and annotate the function signature as result: Optional[LLMResult]
(or the actual class name used in your codebase), keeping scores: Optional[dict]
and error: Optional[Exception] as-is; update imports at module scope and adjust
any references inside the logging helper accordingly.
- Around line 192-199: The docstring for the shared Braintrust logging helper is
out of sync with the implementation: update the docstring of the function (the
helper that takes eval_span) to document both supported flows or narrow the code
to match the docstring; specifically either add CompactionResult
(CompactionResult) to the documented accepted types along with AskHolmesTestCase
and LLMResult, or remove the explicit CompactionResult handling in the function
body so it only handles AskHolmesTestCase/LLMResult—adjust the narrative to
explain why both flows are supported and what each path does, and reference the
symbols AskHolmesTestCase, LLMResult, CompactionResult and the helper function
name so reviewers can locate the changes.

---

Nitpick comments:
In `@holmes/main.py`:
- Around line 476-480: Replace the logging call in the except block that handles
errors from source.fetch_issues(): instead of calling logging.error(...,
exc_info=e), use logging.exception("Failed to fetch issues from alertmanager")
so the exception traceback is included idiomatically; locate the try/except
around source.fetch_issues() in holmes/main.py and swap the logging.error call
for logging.exception within that except block.
- Around line 614-627: The block that prints rules, header, Markdown result,
handles update/write_back_result and appends to results is duplicated; extract
it into a helper (e.g., _display_issue_result or display_result) that accepts
parameters (issue, result, update, source, console, results) and performs
console.print(Rule()), header, Markdown rendering, optional
source.write_back_result(issue.id, result) with console messages, and
results.append({"issue": issue.model_dump(), "result": result.model_dump()});
then replace the repeated blocks in jira/github/pagerduty/opsgenie command
handlers (the repeated printing/update code shown around the Rule/AI
analysis/Markdown/update logic and the other mentioned ranges) with calls to
that helper so behavior and side effects remain identical.

In `@server.py`:
- Around line 59-61: Remove the stale inline comment "# removed:
add_runbooks_to_user_prompt" near the import of stream_chat_formatter in
server.py; locate the import statement "from holmes.utils.stream import
stream_chat_formatter" and delete only the historical comment line so the import
section contains current, purposeful comments only.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b37b720-1be8-4bf3-9eca-b6f6d35444ec

📥 Commits

Reviewing files that changed from the base of the PR and between 91d055b and a22b68c.

📒 Files selected for processing (8)
  • holmes/config.py
  • holmes/core/tool_calling_llm.py
  • holmes/main.py
  • server.py
  • tests/llm/conftest.py
  • tests/llm/utils/braintrust.py
  • tests/llm/utils/property_manager.py
  • tests/llm/utils/reporting/github_reporter.py
💤 Files with no reviewable changes (1)
  • holmes/core/tool_calling_llm.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • holmes/config.py
  • tests/llm/conftest.py
  • tests/llm/utils/property_manager.py

The investigation_procedure.jinja2 template was accidentally deleted
in the investigation endpoint removal, but it is used by ALL CLI
commands (via generic_investigation.jinja2 -> _noflag_general_instructions.jinja2)
and by the server/chat path (via _general_instructions.jinja2 when
todowrite_enabled). This restores the template and its includes.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/eval-regression.yaml (1)

858-867: Consider extracting the repeated test file collection logic.

This block duplicates lines 661-670. While acceptable in workflow files, you could extract this into a shared shell script (e.g., .github/scripts/collect-test-files.sh) to reduce maintenance burden if the file list changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/eval-regression.yaml around lines 858 - 867, Extract the
duplicated test-file collection loop (the code that builds TEST_FILES and sets
PYTEST_ARGS) into a shared shell script named collect-test-files.sh and replace
both inline copies with a simple source/execute of that script; ensure the
script exports or prints the TEST_FILES array (or writes it to stdout for
command substitution) so the calling workflow can set TEST_FILES and then define
PYTEST_ARGS=(--no-cov "${TEST_FILES[@]}" -s -n10 -m "$EVAL_MARKER_EXPR"),
keeping the same variable names (TEST_FILES, PYTEST_ARGS) so existing references
like the for loop and the PYTEST_ARGS assignment continue to work unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/llm/utils/braintrust.py`:
- Around line 212-220: The branch handling LLMResult may set output to None and
drop the exception text; change both branches to explicitly guard result.result
for None: when error is truthy set output = result.result if (result and
getattr(result, "result", None) is not None) else str(error), ensure scores =
scores or {}, and in the non-error branch set output = result.result if (result
and getattr(result, "result", None) is not None) else "". Update references
around the variables error, result, LLMResult.result, output, and scores to use
getattr(result, "result", None) checks rather than truthiness of result.result.

---

Nitpick comments:
In @.github/workflows/eval-regression.yaml:
- Around line 858-867: Extract the duplicated test-file collection loop (the
code that builds TEST_FILES and sets PYTEST_ARGS) into a shared shell script
named collect-test-files.sh and replace both inline copies with a simple
source/execute of that script; ensure the script exports or prints the
TEST_FILES array (or writes it to stdout for command substitution) so the
calling workflow can set TEST_FILES and then define PYTEST_ARGS=(--no-cov
"${TEST_FILES[@]}" -s -n10 -m "$EVAL_MARKER_EXPR"), keeping the same variable
names (TEST_FILES, PYTEST_ARGS) so existing references like the for loop and the
PYTEST_ARGS assignment continue to work unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3aa3509-52a6-40ab-8586-5f20f0adc60e

📥 Commits

Reviewing files that changed from the base of the PR and between a22b68c and aabe819.

📒 Files selected for processing (2)
  • .github/workflows/eval-regression.yaml
  • tests/llm/utils/braintrust.py

Comment thread tests/llm/utils/braintrust.py
@aantn
aantn enabled auto-merge (squash) March 7, 2026 20:32
claude added 4 commits March 7, 2026 20:35
Investigation commands (alertmanager, jira, github, pagerduty, opsgenie) now
use the same build_system_prompt() path as the ask command, with
investigation-specific context passed via system_prompt_additions. This
removes the separate generic_investigation.jinja2 template and the
--system-prompt CLI option from all investigate subcommands.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
No test cases use expected_sections (it was part of the old investigation
system). Remove the evaluate_sections function, its call site in
property_manager, and the docs reference.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
…ions

These timestamp variables (start_timestamp, end_timestamp, etc.) were never
populated - the Issue model has no such fields. They were leftover from the
old investigation template.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
….jinja2

Just a single-line f-string instead of a dedicated template file.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
holmes/main.py (1)

609-611: ⚠️ Potential issue | 🟠 Major

Guard optional LLM output before rendering.

Line 611, Line 724, Line 800, Line 876, and Line 949 still call .replace() on result.result. LLMResult.result is optional, so a failed/empty model response will raise AttributeError and abort the command after the investigation already ran.

🛡️ Representative fix
-            console.print(Markdown(result.result.replace("\n", "\n\n")), style="bold green")  # type: ignore
+            analysis_text = result.result or "No analysis available"
+            console.print(Markdown(analysis_text.replace("\n", "\n\n")), style="bold green")

Apply the same fallback to the plain console.print(...) site in ticket.

Also applies to: 720-725, 798-800, 874-876, 947-949

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 609 - 611, result.result is optional and the
code calls result.result.replace(...) directly which can raise AttributeError
when the LLM returned None; update each console.print that renders the LLM
output (the calls that use
console.print(Markdown(result.result.replace(...)))—and the similar sites in the
ticket rendering) to guard the value first by coalescing result.result to a safe
fallback (e.g., use (result.result or "") or a message like "No response from
model") before calling .replace(), then pass that sanitized string into Markdown
and console.print so rendering never dereferences None.
🧹 Nitpick comments (2)
holmes/plugins/prompts/_investigation_additions.jinja2 (1)

7-7: This instruction does not match the current caller contract.

_investigate_issue() only sends issue context, so the model never receives a dedicated triple-quoted instruction block in this flow. Either wire those instructions through the caller or drop this stale directive from the prompt.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/plugins/prompts/_investigation_additions.jinja2` at line 7, The prompt
contains a stale directive telling the model to obey triple-quoted extra
instructions, but _investigate_issue() never provides such a block; either
remove that line from the _investigation_additions.jinja2 template or update the
caller that invokes _investigate_issue() so it forwards any triple-quoted
instruction payload into the investigation context; locate the template entry
that says "If the user provides you with extra instructions in a triple quotes
section..." and either delete it or replace it with a note that callers must
pass extra instructions via the _investigate_issue() payload, and if you choose
wiring, modify the caller to include that payload under the investigation
context key so _investigate_issue() receives it.
holmes/main.py (1)

475-479: Use logging.exception inside this handler.

Ruff flags logging.error(..., exc_info=e) here; logging.exception(...) is the cleaner form in an except block and preserves the traceback without passing the exception object manually.

♻️ Suggested cleanup
-        except Exception as e:
-            logging.error("Failed to fetch issues from alertmanager", exc_info=e)
+        except Exception:
+            logging.exception("Failed to fetch issues from alertmanager")
             return

As per coding guidelines, "Use Ruff for formatting and linting (configured in pyproject.toml)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@holmes/main.py` around lines 475 - 479, Replace the logging call inside the
except block that currently uses logging.error(..., exc_info=e) with
logging.exception to properly log the traceback in the except handler; locate
the try/except around source.fetch_issues() (the variable issues and the call
source.fetch_issues()) and change the except block to call
logging.exception("Failed to fetch issues from alertmanager") and then return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@holmes/main.py`:
- Around line 727-728: The current call to
ticket_source.source.write_back_result(issue_to_investigate.id, result) performs
a destructive write on every successful run; change this so writes are opt-in:
add a CLI flag (e.g., --update or --write-back) or an explicit confirmation
check used by the investigate flow, defaulting to read-only, and only call
ticket_source.source.write_back_result when that flag/confirmation is present;
locate and guard the write in the function where issue_to_investigate is
processed (the block that currently calls console.print and write_back_result)
so the behavior remains idempotent and non-mutating by default.

In `@holmes/plugins/prompts/_investigation_additions.jinja2`:
- Around line 2-4: The template _investigation_additions.jinja2 currently
instructs using literal placeholders ('start_timestamp', 'end_timestamp',
'start_timestamp_millis', 'end_timestamp_millis') and also swaps the
millis/string guidance; update it to either remove the inline time-window
guidance or make it accurate by (a) correcting the mapping so "string format
timestamps" reference start_timestamp/start_timestamp_end and "millisecond
timestamps" reference start_timestamp_millis/end_timestamp_millis, and (b)
ensuring _investigate_issue() supplies the actual timestamp values in the render
context (e.g., add timestamp keys to the context) if you keep the guidance —
otherwise delete these lines so the template doesn't mislead callers.

---

Duplicate comments:
In `@holmes/main.py`:
- Around line 609-611: result.result is optional and the code calls
result.result.replace(...) directly which can raise AttributeError when the LLM
returned None; update each console.print that renders the LLM output (the calls
that use console.print(Markdown(result.result.replace(...)))—and the similar
sites in the ticket rendering) to guard the value first by coalescing
result.result to a safe fallback (e.g., use (result.result or "") or a message
like "No response from model") before calling .replace(), then pass that
sanitized string into Markdown and console.print so rendering never dereferences
None.

---

Nitpick comments:
In `@holmes/main.py`:
- Around line 475-479: Replace the logging call inside the except block that
currently uses logging.error(..., exc_info=e) with logging.exception to properly
log the traceback in the except handler; locate the try/except around
source.fetch_issues() (the variable issues and the call source.fetch_issues())
and change the except block to call logging.exception("Failed to fetch issues
from alertmanager") and then return.

In `@holmes/plugins/prompts/_investigation_additions.jinja2`:
- Line 7: The prompt contains a stale directive telling the model to obey
triple-quoted extra instructions, but _investigate_issue() never provides such a
block; either remove that line from the _investigation_additions.jinja2 template
or update the caller that invokes _investigate_issue() so it forwards any
triple-quoted instruction payload into the investigation context; locate the
template entry that says "If the user provides you with extra instructions in a
triple quotes section..." and either delete it or replace it with a note that
callers must pass extra instructions via the _investigate_issue() payload, and
if you choose wiring, modify the caller to include that payload under the
investigation context key so _investigate_issue() receives it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: adec9ad4-d35a-475a-8f9f-dbcc35b1dc34

📥 Commits

Reviewing files that changed from the base of the PR and between aabe819 and 71ccc8d.

📒 Files selected for processing (4)
  • holmes/main.py
  • holmes/plugins/prompts/_investigation_additions.jinja2
  • holmes/plugins/prompts/generic_investigation.jinja2
  • tests/test_ai_safety_prompt.py
💤 Files with no reviewable changes (2)
  • tests/test_ai_safety_prompt.py
  • holmes/plugins/prompts/generic_investigation.jinja2

Comment thread holmes/main.py Outdated
Comment thread holmes/plugins/prompts/_investigation_additions.jinja2 Outdated
claude added 2 commits March 7, 2026 20:45
The ticket command was unconditionally writing results back to the source
system. All other investigate commands (jira, github, pagerduty, opsgenie)
already have --update flags. Add the same pattern here, defaulting to
read-only.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
Only included by the now-deleted generic_investigation.jinja2. No remaining
references in the codebase.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
arikalon1
arikalon1 previously approved these changes Mar 8, 2026
Keep dynamic test file collection from our branch (test_investigate.py
was removed as part of investigation endpoint removal). Master added
test_investigate.py to the static list, which no longer exists.

https://claude.ai/code/session_01GgDjg6RjYXmfDKSp2TCcxP
Signed-off-by: Claude <noreply@anthropic.com>
@aantn
aantn merged commit a66714e into master Mar 8, 2026
21 of 22 checks passed
@aantn
aantn deleted the claude/investigate-endpoint-audit-S5ufh branch March 8, 2026 09:09
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