Skip to content

feat: report json schema of toolset config to db - #1327

Merged
naomi-robusta merged 38 commits into
masterfrom
claude/document-holmesgpt-data-reporting-axz0d
Jan 25, 2026
Merged

naomi-robusta merged 38 commits into
masterfrom
claude/document-holmesgpt-data-reporting-axz0d

Conversation

@aantn

@aantn aantn commented Jan 5, 2026 •

Copy link
Copy Markdown
Collaborator

Wraps installation_instructions as JSON containing:

  • instructions: The markdown installation instruction
  • config_schema: JSON Schema for toolset configuration
  • example_config: Example configuration values

This enables the frontend to render a form-based UI for toolset configuration while maintaining backwards compatibility.

Changes:

  • Add config_class ClassVar to Toolset base class
  • Add get_config_schema() method to Toolset
  • Update holmes_sync_toolsets to wrap instructions as JSON
  • Add config_class to RabbitMQ, Coralogix, Prometheus, OpenSearch toolsets

Summary by CodeRabbit

  • New Features

    • Toolsets now expose unified, typed configuration classes with consolidated config example/schema access.
  • Documentation

    • Many configs include richer field descriptions, examples and clearer defaults for generated schemas.
  • Refactor

    • Legacy example-config helpers removed; configuration surface unified around Pydantic models and automated example generation.
  • Tests

    • New unit test for config example generation; existing tests updated to align with the new config API.

✏️ Tip: You can customize this high-level summary in your review settings.

Wraps installation_instructions as JSON containing:
- instructions: The markdown installation instructions
- config_schema: JSON Schema for toolset configuration
- example_config: Example configuration values

This enables the frontend to render a form-based UI for toolset
configuration while maintaining backwards compatibility.

Changes:
- Add config_class ClassVar to Toolset base class
- Add get_config_schema() method to Toolset
- Update holmes_sync_toolsets to wrap instructions as JSON
- Add config_class to RabbitMQ, Coralogix, Prometheus, OpenSearch toolsets
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jan 5, 2026 •

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

@coderabbitai

coderabbitai Bot commented Jan 5, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a typed config surface to toolsets: introduces config_classes: ClassVar[List[Type[BaseModel]]], get_config_example() and get_config_schema(); removes many per-tool get_example_config() methods; enriches Pydantic models with Field(...) metadata; adds build_config_example() and tests; updates sync/rendering to use new API.

Changes

Cohort / File(s) Summary
Core API
holmes/core/tools.py
Add config_classes: ClassVar[List[Type[BaseModel]]], get_config_example() and get_config_schema(); remove legacy get_example_config() and installation_instructions; add ClassVar, Type, and build_config_example imports.
Config example generator & tests
holmes/utils/pydantic_utils.py, tests/utils/test_pydantic_utils.py
Add build_config_example() and helper _extract_base_model_subclass(); unit test verifying example generation (defaults, default_factory, examples, nested models, placeholders).
Toolset sync / rendering & tests
holmes/utils/holmes_sync_toolsets.py, tests/test_holmes_sync_toolsets.py
Replace get_example_config() usage with get_config_example(); assign default installation_instructions when missing using the new example; update tests to the new flow.
Bulk toolset metadata & registration
holmes/plugins/toolsets/... (many files)
Replace many plain model attributes with pydantic.Field(...) (descriptions, examples, default_factory), add config_classes on many toolsets, and remove per-tool get_example_config() methods.
Specific model enrichments
prometheus/*, kafka.py, grafana/*, elasticsearch/*, rabbitmq/*, datadog/*, mcp/*, internet.py, servicenow_tables.py, atlas_mongodb/*, azure_sql/*, newrelic/*, coralogix/*, etc.
Convert fields to Field(...) with descriptions/examples, add config_classes declarations, and where applicable update config parsing/usage (e.g., InternetBaseToolset stores parsed config).
API serialization / read-only metadata
holmes/plugins/toolsets/rabbitmq/api.py
Mark internal/read-only fields with Field(..., exclude=True, json_schema_extra={"readOnly": True}) and add richer metadata for JSON Schema generation.
Tests adjustments
tests/plugins/...
Remove/update tests that asserted per-tool get_example_config() outputs; add/adjust tests to validate schema/example behavior.
PR template
.github/PULL_REQUEST_TEMPLATE/toolset.md
Replace checklist item about get_example_config with requirement for config_classes and Field annotations.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Toolset as Toolset
participant Model as PydanticModel
participant Utils as build_config_example
Client->>Toolset: request get_config_schema()
Toolset->>Toolset: read config_classes
Toolset->>Model: select config class
Toolset->>Utils: call build_config_example(Model)
Utils-->>Toolset: return example dict
Toolset-->>Client: return schema + example

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • moshemorad
  • arikalon1
  • nherment
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.65% 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 PR title 'feat: report json schema of toolset config to db' directly aligns with the main objective: exposing toolset configuration schema for database reporting and frontend form rendering.

✏️ 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: 0

🧹 Nitpick comments (2)
tests/test_holmes_sync_toolsets.py (1)

107-111: LGTM! Test correctly validates the JSON wrapping feature.

The test properly verifies that installation_instructions is now wrapped in JSON format with the expected structure. The assertions correctly expect None values for config_schema and example_config since SampleToolset doesn't define a config_class.

Consider adding a test case with a toolset that defines config_class to verify non-None schema handling, though this may be covered elsewhere.

holmes/utils/holmes_sync_toolsets.py (1)

84-92: LGTM! Integration logic correctly wraps instructions with schema.

The code properly extracts config schema and example configuration from the toolset, then wraps them with installation instructions.

Line 91 can be simplified:

🔎 Minor simplification
         wrapped_instructions = wrap_installation_instructions_with_schema(
             instructions=toolset.installation_instructions,
             config_schema=config_schema,
-            example_config=example_config if example_config else None,
+            example_config=example_config or None,
         )

Or even just example_config=example_config since the function accepts Optional.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 661ab7c and 7a2f9a0.

📒 Files selected for processing (7)
  • holmes/core/tools.py
  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/utils/holmes_sync_toolsets.py
  • tests/test_holmes_sync_toolsets.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Use Ruff for formatting and linting with configuration in pyproject.toml
Type hints required (mypy configuration in pyproject.toml)
Pre-commit hooks enforce quality checks on Python files

Files:

  • tests/test_holmes_sync_toolsets.py
  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/core/tools.py
  • holmes/utils/holmes_sync_toolsets.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests should match source structure under tests/
Live execution is now enabled by default to ensure tests match real-world behavior

Files:

  • tests/test_holmes_sync_toolsets.py
holmes/plugins/toolsets/**/*.{py,yaml}

📄 CodeRabbit inference engine (CLAUDE.md)

holmes/plugins/toolsets/**/*.{py,yaml}: All toolsets MUST return detailed error messages from underlying APIs to enable LLM self-correction, including exact query/command executed, time ranges/parameters/filters used, and full API error response (status code and message)
For 'no data' responses in toolsets, specify what was searched and where
Never return unbounded data from APIs - always include filter parameters on tools that query collections

Files:

  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
holmes/plugins/toolsets/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

holmes/plugins/toolsets/**/*.py: Use requests library for HTTP calls in Python toolsets, not specialized client libraries like opensearchpy
Implement simple Pydantic config class with validation for Python toolsets
Include health check in prerequisites_callable() method for Python toolsets
Each tool in Python toolsets should be a thin wrapper around a single API endpoint
Use JsonFilterMixin for client-side filtering when server-side filtering is not possible, adding max_depth and jq parameters
Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields
Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields
Use @model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings
Bash toolset validates commands for safety

Files:

  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields
📚 Learning: 2026-01-05T11:14:20.209Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.209Z
Learning: New toolsets require integration tests

Applied to files:

  • tests/test_holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.208Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields

Applied to files:

  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/core/tools.py
📚 Learning: 2026-01-05T11:14:20.208Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets

Applied to files:

  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/opensearch/opensearch.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/core/tools.py
📚 Learning: 2026-01-05T11:14:20.208Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields

Applied to files:

  • holmes/plugins/toolsets/coralogix/toolset_coralogix.py
  • holmes/plugins/toolsets/prometheus/prometheus.py
  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/core/tools.py
  • holmes/utils/holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.208Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.208Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings

Applied to files:

  • holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py
  • holmes/core/tools.py
🧬 Code graph analysis (2)
holmes/plugins/toolsets/coralogix/toolset_coralogix.py (1)
holmes/plugins/toolsets/coralogix/utils.py (1)
  • CoralogixConfig (27-31)
holmes/utils/holmes_sync_toolsets.py (1)
holmes/core/tools.py (5)
  • get_config_schema (770-777)
  • get_example_config (767-768)
  • get_example_config (805-806)
  • get_example_config (838-839)
  • ToolsetDBModel (842-852)
🪛 Ruff (0.14.10)
holmes/utils/holmes_sync_toolsets.py

29-29: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (12)
holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py (1)

3-3: LGTM! Clean type annotation for config class.

The addition of ClassVar[Type[RabbitMQConfig]] properly declares the configuration class at the type level, enabling the get_config_schema() method introduced in the base Toolset class to generate JSON schemas for frontend form rendering.

Also applies to: 132-133

holmes/plugins/toolsets/opensearch/opensearch.py (1)

2-2: LGTM! Consistent with project-wide pattern.

The config_class attribute follows the same pattern as other toolsets in this PR, properly annotated with ClassVar[Type[OpenSearchConfig]] to expose the configuration schema.

Also applies to: 194-194

holmes/plugins/toolsets/coralogix/toolset_coralogix.py (1)

3-3: LGTM! Clean implementation.

The configuration class binding is correctly implemented with proper type annotations, aligning with the standardized approach across all toolsets in this PR.

Also applies to: 184-185

holmes/core/tools.py (3)

15-15: LGTM! Necessary imports for class variable annotations.

The ClassVar and Type imports support the new configuration class binding pattern.

Also applies to: 21-21


543-543: LGTM! Well-designed class variable for backward compatibility.

The config_class class variable is optional (defaulting to None), which allows existing toolsets without configuration classes to continue working without modification.


770-778: LGTM! Safe schema retrieval method.

The get_config_schema() method properly checks for the existence of config_class before attempting to generate the JSON schema, preventing AttributeError for toolsets that don't define a configuration class.

holmes/plugins/toolsets/prometheus/prometheus.py (1)

6-6: Verify schema completeness for AMPConfig.

The changes correctly add the config_class attribute pointing to PrometheusConfig. However, the runtime config attribute (line 1496) can be either PrometheusConfig or AMPConfig (which extends PrometheusConfig with AWS-specific fields like aws_region, aws_access_key, etc.).

Since get_config_schema() will return the schema for PrometheusConfig only, AWS-specific fields won't appear in the generated schema. This may be intentional if AMPConfig is considered an advanced/alternate configuration path, but please confirm this won't cause issues for users trying to configure AWS Managed Prometheus through a form-based UI.

Consider whether AMPConfig should have its own schema exposure mechanism, or if the base PrometheusConfig schema is sufficient for the intended use case.

Also applies to: 1495-1495

tests/test_holmes_sync_toolsets.py (1)

1-1: LGTM!

The json import is correctly placed at the top of the file and is necessary for parsing the JSON-wrapped installation instructions in the updated test.

holmes/utils/holmes_sync_toolsets.py (4)

1-1: LGTM!

The import additions are correctly placed and necessary for the new JSON wrapping functionality and type hints.

Also applies to: 4-4


25-29: LGTM!

The custom JSON serializer follows a common pattern and appropriately handles non-serializable objects by falling back to their string representation. This is suitable for config schemas and example configurations.

The static analysis hint (TRY003) appears to be a false positive—the error message is concise and appropriate.


32-54: LGTM! Well-documented wrapping function.

The function correctly wraps installation instructions with configuration schema and example. The docstring clearly explains the structure and backwards compatibility approach for frontend parsing.


96-96: LGTM! Correct handling of wrapped instructions.

The code properly excludes the original installation_instructions from the model dump and replaces it with the JSON-wrapped version. This ensures the database receives the enhanced format containing instructions, config schema, and example configuration.

Also applies to: 101-101

claude added 2 commits January 5, 2026 21:14
Remove example_config from installation_instructions wrapper and rely
solely on JSON Schema with Field annotations for examples.

Changes:
- Remove example_config parameter from wrap_installation_instructions_with_schema
- Add Field(examples=[...], description="...") to config models:
  - RabbitMQClusterConfig, RabbitMQConfig
  - PrometheusConfig
  - CoralogixConfig, CoralogixLabelsConfig
  - GrafanaConfig, GrafanaTempoConfig, GrafanaTempoLabelsConfig
- Update test assertions

Frontend can now extract examples from config_schema["properties"]["field"]["examples"]
Resolved conflicts in:
- prometheus/prometheus.py: Added Field annotations to new field names
- rabbitmq/api.py: Merged imports
- rabbitmq/toolset_rabbitmq.py: Merged imports with config_class support
@netlify

netlify Bot commented Jan 5, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 0cf6fdf
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6976491ccc44300008201298
😎 Deploy Preview https://deploy-preview-1327--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 Jan 5, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

📜 Run @ 8685098 (#21334361701)

✅ Results of HolmesGPT evals

Automatically triggered by commit 8685098 on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost
✅ 09_crashpod 29.4s ↑14% 5 11 $0.2300
✅ 101_loki_historical_logs_pod_deleted 43.4s ↓15% 7 11 $0.2757
✅ 111_pod_names_contain_service 22.1s ±0% 4 7 $0.1784
✅ 12_job_crashing 33.2s ±0% 5 12 $0.2420
✅ 162_get_runbooks 35.1s ±0% 6 11 $0.2611
✅ 176_network_policy_blocking_traffic_no_runbooks 43.2s ±0% 7 17 $0.3077
✅ 24_misconfigured_pvc 29.4s ↓11% 5 12 $0.2177
✅ 43_current_datetime_from_prompt 3.8s ±0% 1 — $0.1019
✅ 61_exact_match_counting 15.4s ±0% 4 4 $0.1593
Total 28.3s avg 4.9 avg 10.6 avg $1.9737

Time/Cost columns show % change vs historical average (↑slower/costlier, ↓faster/cheaper). Changes under 10% shown as ±0%.

Historical Comparison Details

Filter: excluding branch 'claude/document-holmesgpt-data-reporting-axz0d'

Status: Success - 20 test/model combinations loaded

Experiments compared (30):

Comparison indicators:

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

✅ Results of HolmesGPT evals

Automatically triggered by commit 28a1606 on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost
✅ 09_crashpod 28.2s ±0% 5 11 $0.2313
✅ 101_loki_historical_logs_pod_deleted 50.2s ±0% 7 14 $0.3185
✅ 111_pod_names_contain_service 21.5s ±0% 4 7 $0.1779
✅ 12_job_crashing 38.7s ↑16% 6 15 $0.2843
✅ 162_get_runbooks 36.5s ±0% 6 11 $0.2684
✅ 176_network_policy_blocking_traffic_no_runbooks 41.2s ±0% 6 17 $0.2796
✅ 24_misconfigured_pvc 33.1s ±0% 6 13 $0.2347
✅ 43_current_datetime_from_prompt 4.1s ↑11% 1 — $0.1019
✅ 61_exact_match_counting 16.0s ±0% 4 4 $0.1592
Total 29.9s avg 5.0 avg 11.5 avg $2.0560

Time/Cost columns show % change vs historical average (↑slower/costlier, ↓faster/cheaper). Changes under 10% shown as ±0%.

Historical Comparison Details

Filter: excluding branch 'claude/document-holmesgpt-data-reporting-axz0d'

Status: Success - 20 test/model combinations loaded

Experiments compared (30):

Comparison indicators:

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

✅ Results of HolmesGPT evals

Automatically triggered by commit b1a82bd on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost
✅ 09_crashpod 30.9s ↑20% 5 11 $0.2267
✅ 101_loki_historical_logs_pod_deleted 44.7s ↓12% 7 11 $0.2749
✅ 111_pod_names_contain_service 22.7s ±0% 4 7 $0.1769
✅ 12_job_crashing 32.1s ±0% 5 12 $0.2383
✅ 162_get_runbooks 38.5s ±0% 6 11 $0.2670
✅ 176_network_policy_blocking_traffic_no_runbooks 44.9s ±0% 6 17 $0.2745
✅ 24_misconfigured_pvc 29.4s ↓11% 5 12 $0.2208
✅ 43_current_datetime_from_prompt 3.7s ±0% 1 — $0.1019
✅ 61_exact_match_counting 16.0s ±0% 4 4 $0.1593
Total 29.2s avg 4.8 avg 10.6 avg $1.9402

Time/Cost columns show % change vs historical average (↑slower/costlier, ↓faster/cheaper). Changes under 10% shown as ±0%.

Historical Comparison Details

Filter: excluding branch 'claude/document-holmesgpt-data-reporting-axz0d'

Status: Success - 20 test/model combinations loaded

Experiments compared (30):

Comparison indicators:

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

✅ Results of HolmesGPT evals

Automatically triggered by commit f165ff0 on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

⚠️ No eval report was generated.

📜 Run @ 052a1ae (#21333242854)

✅ Results of HolmesGPT evals

Automatically triggered by commit 052a1ae on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

⚠️ No eval report was generated.


✅ Results of HolmesGPT evals

Automatically triggered by commit 0cf6fdf on branch claude/document-holmesgpt-data-reporting-axz0d

View workflow logs

Results of HolmesGPT evals

  • ask_holmes: 9/9 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost
✅ 09_crashpod 27.8s ±0% 5 10 $0.2264
✅ 101_loki_historical_logs_pod_deleted 63.9s ↑25% 9 18 $0.3773
✅ 111_pod_names_contain_service 22.7s ±0% 4 7 $0.1780
✅ 12_job_crashing 33.1s ±0% 5 12 $0.2471
✅ 162_get_runbooks 39.3s ±0% 6 12 $0.2712
✅ 176_network_policy_blocking_traffic_no_runbooks 37.5s ±0% 6 14 $0.2713
✅ 24_misconfigured_pvc 34.2s ±0% 6 13 $0.2409
✅ 43_current_datetime_from_prompt 3.7s ±0% 1 — $0.1019
✅ 61_exact_match_counting 15.9s ±0% 4 4 $0.1592
Total 30.9s avg 5.1 avg 11.2 avg $2.0732

Time/Cost columns show % change vs historical average (↑slower/costlier, ↓faster/cheaper). Changes under 10% shown as ±0%.

Historical Comparison Details

Filter: excluding branch 'claude/document-holmesgpt-data-reporting-axz0d'

Status: Success - 20 test/model combinations loaded

Experiments compared (30):

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/document-holmesgpt-data-reporting-axz0d -f markers=regression -f filter=

Option 1: Comment on this PR with /eval:

/eval
markers: regression

Or with more options (one per line):

/eval
model: gpt-4o
markers: regression
filter: 09_crashpod
iterations: 5

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

/eval
branch: master
markers: regression
Option Description
model Model(s) to test (default: same as automatic runs)
markers Pytest 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"

🏷️ Valid markers

benchmark, chain-of-causation, compaction, confluence, context_window, coralogix, counting, database, datadog, datetime, easy, elasticsearch, embeds, 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/document-holmesgpt-data-reporting-axz0d -f markers=regression -f filter=

@github-actions

github-actions Bot commented Jan 5, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker image ready for 6c93cf9 (built in 59s)

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

Use this tag to pull the image 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:6c93cf9
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/holmes:6c93cf9 me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:6c93cf9
docker push me-west1-docker.pkg.dev/robusta-development/development/holmes-dev:6c93cf9

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:6c93cf9

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:6c93cf9

- Remove get_example_config() method from Toolset base class and all implementations
- Update installation instructions template to not include example configs
- Frontend will now generate config examples from the JSON Schema instead
- Update PR template to reference config_class for schema generation
- Remove all test methods that tested get_example_config()

Signed-off-by: Claude <noreply@anthropic.com>
Resolved conflicts:
- tools.py: Added restricted_tools/approval_required_tools, removed get_example_config
- grafana/common.py: Added verify_ssl field with Field annotation
- grafana/base_grafana_toolset.py: Removed get_example_config
- newrelic.py: Removed get_example_config
- prometheus.py: Added AzurePrometheusConfig to union, kept config_class
- rabbitmq/api.py: Merged Field annotations with verify_ssl rename and model_validator

Signed-off-by: Claude <noreply@anthropic.com>
- Remove default_toolset_installation_guide.jinja2 template
- Frontend can now generate all instructions from config schema + toolset metadata
- Simplify holmes_sync_toolsets.py by removing render function
- Update test to use toolset.installation_instructions directly

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

- Remove installation_instructions field from Toolset and ToolsetYamlFromConfig
- Send config_schema JSON directly in installation_instructions DB field
- Remove wrapper function - schema is now the direct value
- Frontend can parse JSON schema directly for form generation

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

🤖 Fix all issues with AI agents
In `@holmes/utils/holmes_sync_toolsets.py`:
- Around line 54-60: The code treats an empty but valid config schema as missing
by using a truthy check; change the conditional that builds config_schema_json
to explicitly check for None (use config_schema is not None) so that empty
structures returned by toolset.get_config_schema() are preserved and still
JSON-serialized via json.dumps(..., default=_json_serializer); update references
around config_schema, config_schema_json and toolset.get_config_schema()
accordingly.
🧹 Nitpick comments (1)
holmes/utils/holmes_sync_toolsets.py (1)

22-26: Tighten serializer fallback to avoid masking schema issues.
Every object has __str__, so this will stringify any non‑serializable object and can hide real schema problems. Consider limiting to known safe types (e.g., datetime/Enum/Path) and raising otherwise.

♻️ Proposed refactor
+from datetime import date, datetime
+from enum import Enum
+from pathlib import Path
+
 def _json_serializer(obj: Any) -> Any:
     """Custom JSON serializer for objects not serializable by default json code."""
-    if hasattr(obj, "__str__"):
-        return str(obj)
+    if isinstance(obj, (datetime, date, Enum, Path)):
+        return str(obj)
     raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 228feea and 7da62ff.

📒 Files selected for processing (3)
  • holmes/core/tools.py
  • holmes/utils/holmes_sync_toolsets.py
  • tests/test_holmes_sync_toolsets.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Use Ruff for formatting and linting with configuration in pyproject.toml
Type hints required (mypy configuration in pyproject.toml)
Pre-commit hooks enforce quality checks on Python files

Files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
  • holmes/utils/holmes_sync_toolsets.py
**/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: Use semantic, descriptive names for variables, functions, and components
Write clear, concise comments that explain 'why' rather than 'what'

Files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
  • holmes/utils/holmes_sync_toolsets.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests should match source structure under tests/
Live execution is now enabled by default to ensure tests match real-world behavior

Files:

  • tests/test_holmes_sync_toolsets.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to tests/llm/fixtures/**/*.yaml : Toolset config in eval tests must go under config field: toolsets.toolset_name.enabled.config
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields

Applied to files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
  • holmes/utils/holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields

Applied to files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
  • holmes/utils/holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets

Applied to files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
  • holmes/utils/holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings

Applied to files:

  • holmes/core/tools.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to tests/llm/fixtures/**/*.yaml : Custom toolsets for eval tests: Create separate toolsets.yaml file, never put toolset config in test_case.yaml

Applied to files:

  • holmes/core/tools.py
  • tests/test_holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to tests/llm/fixtures/**/*.yaml : Toolset config in eval tests must go under config field: toolsets.toolset_name.enabled.config

Applied to files:

  • holmes/core/tools.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use JsonFilterMixin for client-side filtering when server-side filtering is not possible, adding max_depth and jq parameters

Applied to files:

  • holmes/utils/holmes_sync_toolsets.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.{py,yaml} : All toolsets MUST return detailed error messages from underlying APIs to enable LLM self-correction, including exact query/command executed, time ranges/parameters/filters used, and full API error response (status code and message)

Applied to files:

  • holmes/utils/holmes_sync_toolsets.py
🧬 Code graph analysis (1)
holmes/utils/holmes_sync_toolsets.py (1)
holmes/core/tools.py (2)
  • get_config_schema (826-833)
  • ToolsetDBModel (894-904)
🪛 Ruff (0.14.11)
holmes/utils/holmes_sync_toolsets.py

26-26: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: llm_evals
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)
  • GitHub Check: build (3.10)
🔇 Additional comments (3)
tests/test_holmes_sync_toolsets.py (2)

45-46: LGTM — minimal SampleToolset stub is fine.


92-115: LGTM — test now validates null schema path.

holmes/core/tools.py (1)

593-833: LGTM — config_class + schema accessor fit the new schema-driven flow.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread holmes/utils/holmes_sync_toolsets.py Outdated
Add config_class ClassVar and Field annotations with descriptions
to all toolsets for JSON Schema generation and frontend form support.

Toolsets updated:
- newrelic: NewrelicConfig with Field annotations
- kafka: KafkaConfig with Field annotations
- git: GitHubConfig with Field annotations
- datadog/logs: DatadogLogsConfig (already had annotations)
- datadog/metrics: DatadogMetricsConfig (already had annotations)
- datadog/traces: DatadogTracesConfig (already had annotations)
- datadog/general: DatadogGeneralConfig (already had annotations)
- bash: BashExecutorConfig with Field annotations
- atlas_mongodb: MongoDBConfig with Field annotations
- azure_sql: AzureSQLConfig with Field annotations

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: 4

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/bash/common/config.py`:
- Around line 16-19: The Field for allowed_images currently uses a mutable
default (Field(default=[])) which can cause shared-state bugs; update the Field
for allowed_images to use default_factory=list instead (keeping the type
list[KubectlImageConfig] and the description intact) so each model instance gets
a fresh list; locate the allowed_images Field definition in the file and mirror
the pattern used elsewhere in this module (e.g., the other Field that already
uses default_factory=list).

In `@holmes/plugins/toolsets/datadog/toolset_datadog_logs.py`:
- Around line 66-67: Update the Pydantic config classes to add Field metadata so
schema generation and frontend forms get useful labels and examples: in
DatadogBaseConfig add Field(...) with description and example for dd_api_key,
dd_app_key, and site_api_url; in DatadogLogsConfig add Field(...) with
description and example for indexes (e.g., list of index names), storage_tiers
(e.g., list or mapping of tier names), compact_logs (boolean description), and
default_limit (integer description and example). Keep the existing types and
defaults, import Field from pydantic if not already imported, and attach the
description and example parameters to each field declaration (e.g., dd_api_key:
str = Field(..., description="...", example="...")).
- Line 28: Replace the incorrect import of ClassVar and Type from
holmes.core.tools with imports from the typing module: update the import
statement that currently reads "from holmes.core.tools import ClassVar, Type" to
import ClassVar and Type from typing (consolidate with existing typing imports
on the module), ensuring any references to ClassVar and Type in this file (e.g.,
class/type annotations) remain unchanged.

In `@holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py`:
- Around line 9-20: The file imports typing constructs ClassVar and Type from
holmes.core.tools which causes ImportError; update the import statements so
ClassVar and Type are imported from the typing module (e.g., add "from typing
import ClassVar, Type" and remove ClassVar and Type from the holmes.core.tools
import list), and ensure any annotations in this module (e.g., on classes or
functions referencing ClassVar or Type) continue to use those names from the
typing import.
🧹 Nitpick comments (5)
holmes/plugins/toolsets/kafka.py (1)

42-67: Well-structured config with good Field metadata.

The Field() annotations provide clear descriptions for JSON Schema generation. The kafka_broker field includes an example which is useful for the frontend form rendering.

Consider adding examples to the name field as well, since it's a required field and an example would help users understand expected values:

♻️ Optional enhancement
 class KafkaClusterConfig(BaseModel):
-    name: str = Field(description="Name identifier for this Kafka cluster")
+    name: str = Field(
+        description="Name identifier for this Kafka cluster",
+        examples=["production-kafka", "staging-kafka"],
+    )
holmes/plugins/toolsets/azure_sql/azure_base_toolset.py (1)

25-38: Add a validator to prevent partial service‑principal configs.
Fail fast if only one or two of the SP fields are provided.

♻️ Proposed refactor
-from pydantic import BaseModel, ConfigDict, Field
+from pydantic import BaseModel, ConfigDict, Field, model_validator
@@
 class AzureSQLConfig(BaseModel):
     database: AzureSQLDatabaseConfig = Field(
         description="Azure SQL database connection details",
     )
@@
     client_secret: Optional[str] = Field(
         default=None,
         description="Azure AD client secret (required for service principal auth)",
     )
+
+    `@model_validator`(mode="after")
+    def _validate_service_principal(self):
+        creds = (self.tenant_id, self.client_id, self.client_secret)
+        if any(creds) and not all(creds):
+            raise ValueError(
+                "tenant_id, client_id, and client_secret must all be set for service principal auth"
+            )
+        return self
holmes/plugins/toolsets/bash/bash_toolset.py (1)

14-26: Import ClassVar and Type from typing, not from intermediate modules.

While holmes.core.tools currently re-exports these, importing directly from the typing module follows best practices by using the canonical source. This also reduces unnecessary coupling between modules.

♻️ Proposed change
-from typing import Any, Dict, Optional
+from typing import Any, ClassVar, Dict, Optional, Type
@@
-from holmes.core.tools import (
-    ApprovalRequirement,
-    CallablePrerequisite,
-    ClassVar,
-    StructuredToolResult,
-    StructuredToolResultStatus,
-    Tool,
-    ToolInvokeContext,
-    ToolParameter,
-    Toolset,
-    ToolsetTag,
-    Type,
-)
+from holmes.core.tools import (
+    ApprovalRequirement,
+    CallablePrerequisite,
+    StructuredToolResult,
+    StructuredToolResultStatus,
+    Tool,
+    ToolInvokeContext,
+    ToolParameter,
+    Toolset,
+    ToolsetTag,
+)
holmes/plugins/toolsets/git.py (1)

4-20: Import ClassVar and Type from typing instead of holmes.core.tools.

While holmes.core.tools does make these available in its namespace, importing them directly from typing is cleaner, more explicit, and reduces fragile reliance on module internals.

♻️ Suggested change
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type

 import requests  # type: ignore
 from pydantic import BaseModel, Field
 
 from holmes.core.tools import (
     CallablePrerequisite,
-    ClassVar,
     StructuredToolResult,
     StructuredToolResultStatus,
     Tool,
     ToolInvokeContext,
     ToolParameter,
     Toolset,
     ToolsetTag,
-    Type,
 )
holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py (1)

7-23: Import ClassVar and Type directly from the typing module instead of holmes.core.tools.

ClassVar and Type are standard library types that holmes.core.tools re-exports implicitly (without explicit __all__ declaration). Importing directly from typing avoids unnecessary coupling and follows Python best practices of importing from canonical sources.

♻️ Proposed refactor
-from typing import Any, Dict, List, Tuple
+from typing import Any, ClassVar, Dict, List, Tuple, Type
...
-from holmes.core.tools import (
-    CallablePrerequisite,
-    ClassVar,
-    StructuredToolResult,
-    StructuredToolResultStatus,
-    Tool,
-    ToolInvokeContext,
-    ToolParameter,
-    Toolset,
-    ToolsetTag,
-    Type,
-)
+from holmes.core.tools import (
+    CallablePrerequisite,
+    StructuredToolResult,
+    StructuredToolResultStatus,
+    Tool,
+    ToolInvokeContext,
+    ToolParameter,
+    Toolset,
+    ToolsetTag,
+)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7da62ff and a4ce7d1.

📒 Files selected for processing (11)
  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_general.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/kafka.py
  • holmes/plugins/toolsets/newrelic/newrelic.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • holmes/plugins/toolsets/datadog/toolset_datadog_general.py
  • holmes/plugins/toolsets/newrelic/newrelic.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always place Python imports at the top of the file, not inside functions or methods
Use Ruff for formatting and linting with configuration in pyproject.toml
Type hints required (mypy configuration in pyproject.toml)
Pre-commit hooks enforce quality checks on Python files

Files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
holmes/plugins/toolsets/**/*.{py,yaml}

📄 CodeRabbit inference engine (CLAUDE.md)

holmes/plugins/toolsets/**/*.{py,yaml}: All toolsets MUST return detailed error messages from underlying APIs to enable LLM self-correction, including exact query/command executed, time ranges/parameters/filters used, and full API error response (status code and message)
For 'no data' responses in toolsets, specify what was searched and where
Never return unbounded data from APIs - always include filter parameters on tools that query collections

Files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
holmes/plugins/toolsets/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

holmes/plugins/toolsets/**/*.py: Use requests library for HTTP calls in Python toolsets, not specialized client libraries like opensearchpy
Implement simple Pydantic config class with validation for Python toolsets
Include health check in prerequisites_callable() method for Python toolsets
Each tool in Python toolsets should be a thin wrapper around a single API endpoint
Use JsonFilterMixin for client-side filtering when server-side filtering is not possible, adding max_depth and jq parameters
Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields
Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields
Use @model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings
Bash toolset validates commands for safety

Files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
**/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: Use semantic, descriptive names for variables, functions, and components
Write clear, concise comments that explain 'why' rather than 'what'

Files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Maintain backwards compatibility in toolset config using Pydantic's extra='allow' when renaming config fields

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Only define current field names in toolset config schema with extra='allow' to avoid polluting model_dump() output with deprecated fields

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Implement simple Pydantic config class with validation for Python toolsets

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_logs.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Each tool in Python toolsets should be a thin wrapper around a single API endpoint

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/git.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.{py,yaml} : Never return unbounded data from APIs - always include filter parameters on tools that query collections

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use requests library for HTTP calls in Python toolsets, not specialized client libraries like opensearchpy

Applied to files:

  • holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py
  • holmes/plugins/toolsets/git.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Use model_validator(mode='after') in toolset config to map old field names to new names and log deprecation warnings

Applied to files:

  • holmes/plugins/toolsets/azure_sql/azure_base_toolset.py
  • holmes/plugins/toolsets/bash/common/config.py
  • holmes/plugins/toolsets/kafka.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Include health check in prerequisites_callable() method for Python toolsets

Applied to files:

  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.{py,yaml} : All toolsets MUST return detailed error messages from underlying APIs to enable LLM self-correction, including exact query/command executed, time ranges/parameters/filters used, and full API error response (status code and message)

Applied to files:

  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/*.py : Bash toolset validates commands for safety

Applied to files:

  • holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
  • holmes/plugins/toolsets/bash/bash_toolset.py
📚 Learning: 2026-01-05T11:14:20.222Z
Learnt from: CR
Repo: HolmesGPT/holmesgpt PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T11:14:20.222Z
Learning: Applies to holmes/plugins/toolsets/**/kubernetes*.py : RBAC permissions are respected for Kubernetes access

Applied to files:

  • holmes/plugins/toolsets/bash/common/config.py
🧬 Code graph analysis (6)
holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py (2)
holmes/core/tools.py (5)
  • StructuredToolResult (83-109)
  • StructuredToolResultStatus (56-80)
  • Tool (169-418)
  • ToolInvokeContext (158-166)
  • ToolParameter (149-155)
holmes/plugins/toolsets/utils.py (1)
  • toolset_name_for_one_liner (232-236)
holmes/plugins/toolsets/azure_sql/azure_base_toolset.py (1)
holmes/core/tools.py (2)
  • Tool (169-418)
  • Toolset (590-850)
holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py (2)
holmes/core/tools.py (5)
  • StructuredToolResult (83-109)
  • StructuredToolResultStatus (56-80)
  • Tool (169-418)
  • ToolParameter (149-155)
  • Toolset (590-850)
holmes/plugins/toolsets/datadog/datadog_models.py (1)
  • DatadogMetricsConfig (28-31)
holmes/plugins/toolsets/bash/bash_toolset.py (2)
holmes/core/tools.py (3)
  • Tool (169-418)
  • Toolset (590-850)
  • ToolsetTag (137-140)
holmes/plugins/toolsets/bash/common/config.py (1)
  • BashExecutorConfig (22-26)
holmes/plugins/toolsets/datadog/toolset_datadog_logs.py (1)
holmes/plugins/toolsets/datadog/datadog_models.py (1)
  • DatadogLogsConfig (40-50)
holmes/plugins/toolsets/kafka.py (2)
holmes/core/tools.py (7)
  • CallablePrerequisite (577-578)
  • StructuredToolResult (83-109)
  • StructuredToolResultStatus (56-80)
  • Tool (169-418)
  • ToolParameter (149-155)
  • Toolset (590-850)
  • ToolsetTag (137-140)
holmes/plugins/toolsets/utils.py (2)
  • get_param_or_raise (225-229)
  • toolset_name_for_one_liner (232-236)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.12)
  • GitHub Check: llm_evals
🔇 Additional comments (12)
holmes/plugins/toolsets/datadog/toolset_datadog_traces.py (2)

12-23: LGTM on imports.

The ClassVar and Type imports from holmes.core.tools are correctly added to support the new config_class class variable declaration pattern.


49-54: LGTM on config_class declaration.

The config_class class variable correctly references DatadogTracesConfig, enabling JSON Schema generation for frontend form rendering. This aligns with the broader PR pattern of replacing get_example_config() methods with the class-level config_class hook.

holmes/plugins/toolsets/kafka.py (3)

24-37: LGTM on imports.

The imports for Field, ClassVar, and Type are correctly added to support the enhanced config model definitions and config_class declaration.


70-73: LGTM on KafkaConfig field definition.

The Field() annotation with description is appropriate for the kafka_clusters list.


587-592: LGTM on config_class declaration.

The config_class class variable correctly references KafkaConfig, enabling JSON Schema generation. The placement before model_config and instance attributes is appropriate.

holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py (1)

659-661: config_class hook looks good.
This aligns the toolset with the schema-driven config flow.

holmes/plugins/toolsets/azure_sql/azure_base_toolset.py (3)

3-5: LGTM for the schema-related imports.


10-21: Nice schema metadata for DB fields.


42-44: Good addition of the config schema hook.

holmes/plugins/toolsets/git.py (1)

24-44: No changes needed. The GitHubConfig does not have deprecated or renamed fields, so adding ConfigDict(extra="allow") is unnecessary. The codebase pattern (RabbitMQ, Prometheus) shows that extra="allow" is used specifically when maintaining backwards compatibility during field renames, paired with a @model_validator(mode="after") to map old names to new ones. Without field migrations, adding extra="allow" alone would pollute model_dump() output per the project learnings.

Likely an incorrect or invalid review comment.

holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py (2)

28-37: Schema descriptions look good.

Field descriptions should improve the generated config schema for form rendering.

Please verify that the schema generation path (e.g., Toolset.get_config_schema()) includes Pydantic Field metadata so these descriptions appear in the JSON sent to the DB.


42-42: config_class hook is wired correctly.

This cleanly exposes the config model for schema reporting.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread holmes/plugins/toolsets/bash/common/config.py Outdated
Comment thread holmes/plugins/toolsets/datadog/toolset_datadog_logs.py Outdated
Comment thread holmes/plugins/toolsets/datadog/toolset_datadog_logs.py Outdated
Comment thread holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
@CLAassistant

CLAassistant commented Jan 20, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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

🤖 Fix all issues with AI agents
In `@holmes/utils/holmes_sync_toolsets.py`:
- Around line 54-70: The code currently sets installation_instructions to only
the JSON schema from toolset.get_config_schema(), dropping the original markdown
instructions and example config; change the block that builds ToolsetDBModel so
installation_instructions is a JSON payload that contains the original
instructions plus the schema and example config (e.g., {"instructions":
toolset.installation_instructions, "schema": config_schema, "example_config":
toolset.example_config} serialized via json.dumps with _json_serializer) instead
of just config_schema_json; update references around ToolsetDBModel(...) and its
model_dump() usage so the frontend receives the combined JSON object under
installation_instructions.
🧹 Nitpick comments (1)
holmes/utils/holmes_sync_toolsets.py (1)

22-27: __str__ guard is redundant and hides unexpected types.
Every object has __str__, so the TypeError branch is unreachable. Either simplify to return str(obj) or whitelist supported types to avoid silently stringifying unexpected objects.

♻️ Suggested simplification (no behavior change)
 def _json_serializer(obj: Any) -> Any:
     """Custom JSON serializer for objects not serializable by default json code."""
-    if hasattr(obj, "__str__"):
-        return str(obj)
-    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
+    return str(obj)

Comment thread holmes/utils/holmes_sync_toolsets.py Outdated
@naomi-robusta
naomi-robusta force-pushed the claude/document-holmesgpt-data-reporting-axz0d branch from 18bf829 to a2f08fe Compare January 20, 2026 12:46

@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

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/kafka.py`:
- Around line 42-84: The Pydantic models KafkaClusterConfig and KafkaConfig
currently use the default extra behavior which drops unknown keys; update both
models to allow unknown keys by adding a Pydantic config to each model that sets
extra="allow" (i.e., add an inner Config class or the appropriate pydantic v2
model_config mapping depending on your pydantic version) so KafkaClusterConfig
and KafkaConfig retain unknown fields for backwards compatibility.
♻️ Duplicate comments (4)
holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py (1)

9-20: Import ClassVar and Type from typing module instead of holmes.core.tools.

The past review flagged this as addressed, but the current code still imports ClassVar (line 11) and Type (line 19) from holmes.core.tools. These are standard typing constructs that should be imported from the typing module. This creates unnecessary indirection and is inconsistent with other toolsets in the codebase.

#!/bin/bash
# Verify the actual import pattern in the file
rg -n "from typing import" holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py
rg -n "ClassVar|Type" holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py | head -10
Suggested fix
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, ClassVar, Dict, Optional, Tuple, Type

 from pydantic import AnyUrl

 from holmes.core.tools import (
     CallablePrerequisite,
-    ClassVar,
     StructuredToolResult,
     StructuredToolResultStatus,
     Tool,
     ToolInvokeContext,
     ToolParameter,
     Toolset,
     ToolsetTag,
-    Type,
 )
holmes/plugins/toolsets/datadog/toolset_datadog_logs.py (1)

28-28: Import ClassVar and Type from typing and consolidate with existing typing imports.

Two issues with this import:

  1. ClassVar and Type should be imported from the typing module, not holmes.core.tools
  2. Per coding guidelines, imports should be at the top of the file — consolidate with the existing typing imports on line 4
Suggested fix
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, ClassVar, Dict, Optional, Tuple, Type

And remove line 28:

-from holmes.core.tools import ClassVar, Type
holmes/utils/holmes_sync_toolsets.py (2)

54-70: Installation instructions are dropped; wrap schema with instructions and example config.

Per the PR objectives, installation_instructions should be a JSON object containing instructions, config_schema, and example_config for frontend form rendering while maintaining backwards compatibility. The current implementation only sends the raw config schema, dropping the original markdown instructions entirely.

🐛 Proposed fix (preserve instructions + schema)
         # Get config schema for frontend form generation
         config_schema = toolset.get_config_schema()
-        config_schema_json = (
-            json.dumps(config_schema, default=_json_serializer)
-            if config_schema
-            else None
-        )
+        
+        # Build installation payload with instructions, schema, and example config
+        installation_payload = None
+        if hasattr(toolset, 'installation_instructions') or config_schema is not None:
+            installation_payload = {
+                "instructions": getattr(toolset, 'installation_instructions', None),
+                "config_schema": config_schema,
+                "example_config": getattr(toolset, 'get_example_config', lambda: None)(),
+            }
+        
+        installation_json = (
+            json.dumps(installation_payload, default=_json_serializer)
+            if installation_payload is not None
+            else None
+        )

         db_toolsets.append(
             ToolsetDBModel(
                 **toolset.model_dump(exclude_none=True),
                 toolset_name=toolset.name,
                 cluster_id=config.cluster_name,
                 account_id=dal.account_id,
                 updated_at=updated_at,
-                installation_instructions=config_schema_json,
+                installation_instructions=installation_json,
             ).model_dump()
         )

56-60: Don't treat empty schema as "missing".

The truthy check if config_schema will drop valid but empty schemas (e.g., {}). Use an explicit is not None check to preserve intent. This was flagged in a prior review and marked as addressed, but the code still shows the truthy check.

🐛 Proposed fix
         config_schema_json = (
             json.dumps(config_schema, default=_json_serializer)
-            if config_schema
+            if config_schema is not None
             else None
         )
🧹 Nitpick comments (3)
holmes/plugins/toolsets/elasticsearch/elasticsearch.py (1)

22-63: Well-structured config with rich metadata for schema generation.

The Field definitions with descriptions and examples are well-implemented and will generate useful JSON Schema for the frontend UI. The env variable templating pattern ({{ env.ELASTICSEARCH_API_KEY }}) in the examples is a good practice for sensitive configuration.

Consider adding extra='allow' for future backwards compatibility.

Based on learnings, toolset configs should use extra='allow' to maintain backwards compatibility when fields are renamed in the future. This prevents validation errors if users have old config fields.

♻️ Optional: Add extra='allow' for future-proofing
 class ElasticsearchConfig(BaseModel):
     """Configuration for Elasticsearch/OpenSearch API access.
     ...
     """
+    model_config = ConfigDict(extra='allow')
 
     url: str = Field(
holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py (1)

28-40: Consider adding extra="allow" for consistency and future backwards compatibility.

RabbitMQClusterConfig in api.py uses ConfigDict(extra="allow") to support deprecated field handling, but RabbitMQConfig here does not. While there are no deprecated fields at this level currently, adding it would maintain consistency with the nested config and prepare for potential future field renames.

Based on learnings, extra='allow' is the standard pattern for maintaining backwards compatibility in toolset configs.

♻️ Suggested change
+from pydantic import BaseModel, ConfigDict, Field
-from pydantic import BaseModel, Field

 class RabbitMQConfig(BaseModel):
+    model_config = ConfigDict(extra="allow")
+
     clusters: List[RabbitMQClusterConfig] = Field(
holmes/plugins/toolsets/prometheus/prometheus.py (1)

1760-1760: Consider exposing AMP/Azure fields in the generated schema.

config_class points to PrometheusConfig, but the toolset accepts AMPConfig and AzurePrometheusConfig based on input. If the frontend form relies solely on this schema, AWS/Azure-specific fields won’t be discoverable. Consider a union/wrapper schema or explicitly documenting that those modes require manual JSON config. Please verify the intended UX for AMP/Azure users.

Comment thread holmes/plugins/toolsets/kafka.py

@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

🤖 Fix all issues with AI agents
In `@holmes/utils/pydantic_utils.py`:
- Around line 62-115: The function build_config_example currently reads examples
before defaults, but the docstring requires defaults/default_factory be applied
first; modify build_config_example so that for each field (iterate
model_cls.model_fields and use field_info), you first check default
(field_info.default != PydanticUndefined) and then default_factory
(field_info.default_factory) and set example_value from those (catch exceptions
from default_factory and fall back), only after those checks consult examples
(field_info.examples or field_info.json_schema_extra.get("examples")) and take
the first example if present, then proceed with nested model handling via
annotation -> _extract_base_model_subclass and final fallback to
f"your_{field_name}", preserving existing exclude handling and BaseModel
instance behavior.
♻️ Duplicate comments (2)
holmes/plugins/toolsets/bash/common/config.py (1)

18-22: Prefer default_factory=list for mutable defaults (re-flag).

Line 18 uses a mutable default list; use default_factory=list to avoid shared-state pitfalls.

♻️ Proposed fix
     allowed_images: list[KubectlImageConfig] = Field(
-        default=[],
+        default_factory=list,
         description="List of allowed container images for kubectl run",
         examples=[[build_config_example(KubectlImageConfig)]]
     )
Pydantic v2 Field default_factory mutable defaults best practices
holmes/utils/holmes_sync_toolsets.py (1)

54-77: Installation payload drops instructions and schema (regression vs PR objective).

installation_instructions is now only the example JSON. This drops the existing markdown instructions and the config schema, and config_schema_json is computed but unused. Also, the truthy checks will discard valid empty schemas/examples. Please wrap instructions + schema + example into one JSON payload and serialize that (use explicit is not None checks).

🐛 Proposed fix (wrap instructions + schema + example)
-        config_schema = toolset.get_config_schema()
-        config_schema_json = (
-            json.dumps(config_schema, default=_json_serializer)
-            if config_schema
-            else None
-        )
-
-        config_example = toolset.get_config_example()
-        config_example_json = (
-            json.dumps(config_example, default=_json_serializer)
-            if config_example
-            else None
-        )
+        config_schema = toolset.get_config_schema()
+        config_example = toolset.get_config_example()
+        instructions = getattr(toolset, "installation_instructions", None)
+
+        installation_payload = (
+            {
+                "instructions": instructions,
+                "config_schema": config_schema,
+                "example_config": config_example,
+            }
+            if instructions is not None
+            or config_schema is not None
+            or config_example is not None
+            else None
+        )
+        installation_instructions_json = (
+            json.dumps(installation_payload, default=_json_serializer)
+            if installation_payload is not None
+            else None
+        )
@@
-                installation_instructions=config_example_json,
+                installation_instructions=installation_instructions_json,
🧹 Nitpick comments (2)
holmes/utils/pydantic_utils.py (2)

13-17: Narrow the exception to ImportError to avoid masking real failures.

Catching Exception here can hide genuine import-time errors inside pydantic_core. Prefer ImportError for the compatibility shim.

♻️ Proposed refinement
-try:
-    # pydantic v2
-    from pydantic_core import PydanticUndefined  # type: ignore
-except Exception:  # pragma: no cover
-    PydanticUndefined = object()  # type: ignore
+try:
+    # pydantic v2
+    from pydantic_core import PydanticUndefined  # type: ignore
+except ImportError:  # pragma: no cover
+    PydanticUndefined = object()  # type: ignore

117-146: Consider handling PEP 604 unions (A | None) in BaseModel extraction.

get_origin() returns types.UnionType for A | B in newer Python versions, so Optional-style annotations using | may be skipped.

♻️ Suggested enhancement
+from types import UnionType
@@
-    if origin is Union:
+    if origin in (Union, UnionType):
         args = [a for a in get_args(annotation) if a is not type(None)]  # noqa: E721
         if len(args) == 1:
             return _extract_base_model_subclass(args[0])
         return None

Comment thread holmes/utils/pydantic_utils.py

@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: 5

🤖 Fix all issues with AI agents
In `@tests/plugins/toolsets/atlas_mongodb/test_mongodb_atlas.py`:
- Around line 5-10: The test function
test_build_config_example_mongodb_atlas_config is missing an explicit return
type annotation; update its signature to include "-> None" (e.g., def
test_build_config_example_mongodb_atlas_config() -> None:) to satisfy the
project's type-hinting requirement while keeping the body unchanged; reference
symbols: test_build_config_example_mongodb_atlas_config, build_config_example,
MongoDBConfig.

In `@tests/plugins/toolsets/bash/test_bash.py`:
- Around line 5-15: The test function
test_build_config_example_bash_executor_config is missing an explicit return
type; update its signature to include "-> None" to satisfy the project's typing
requirements. Locate the function definition for
test_build_config_example_bash_executor_config and add the return type
annotation while keeping the body unchanged; ensure any imports or test
frameworks remain unaffected.

In `@tests/plugins/toolsets/prometheus/test_prometheus.py`:
- Around line 52-72: The test expects an Authorization header example but
build_config_example applies the dataclass default/default_factory (headers
default_factory=dict -> {}), so update the test to assert example["headers"] ==
{} (or otherwise remove the Authorization expectation) instead of the current
Authorization value; locate the assertion in
test_build_config_example_prometheus_config referencing PrometheusConfig and the
"headers" key and change it to match the helper's current behavior.
- Around line 1-2: The test currently imports PrometheusConfig from the external
library; update the import to use the toolset's own PrometheusConfig class
(holmes.plugins.toolsets.prometheus.prometheus.PrometheusConfig) so the test
validates the toolset schema and Field metadata; keep using build_config_example
from holmes.utils.pydantic_utils and ensure any references to PrometheusConfig
in the test (e.g., in test functions) now refer to the toolset class name
PrometheusConfig.

In `@tests/plugins/toolsets/rabbitmq/test_rabbitmq.py`:
- Around line 13-16: The test currently asserts a hardcoded password literal
("holmes_password") which triggers Ruff S105; to suppress this, introduce a
local constant like TEST_RABBITMQ_PASSWORD = "holmes_password" with a trailing
comment `# noqa: S105` (or alternatively append `  # noqa: S105` to the password
assertion) and then use that constant in the assertion (cluster0["password"] ==
TEST_RABBITMQ_PASSWORD) so the linter warning is silenced while keeping the test
semantics; update references in tests/plugins/toolsets/rabbitmq/test_rabbitmq.py
around the cluster0 assertions accordingly.
🧹 Nitpick comments (3)
tests/plugins/toolsets/datadog/test_toolset_datadog_general.py (1)

180-189: LGTM!

The test correctly validates the build_config_example output for DatadogGeneralConfig, including the general-specific fields max_response_size and allow_custom_endpoints.

Minor observation: This test is a standalone function outside TestDatadogGeneralToolset, whereas similar tests in other files (e.g., test_datadog_metrics.py) are placed inside their respective test classes. Consider moving it inside the class for consistency, though this is purely stylistic.

tests/plugins/toolsets/datadog/logs/test_datadog_logs_utils.py (1)

1-3: Imports should follow standard ordering: stdlib first, then third-party, then local.

The pytest import should be placed before the holmes package imports.

Suggested fix
+import pytest
+
 from holmes.plugins.toolsets.datadog.datadog_models import DataDogStorageTier, DatadogLogsConfig
 from holmes.utils.pydantic_utils import build_config_example
-import pytest
tests/plugins/toolsets/grafana/test_grafana.py (1)

1-10: Consider reordering imports to follow PEP 8 conventions.

Local holmes.* imports appear before the third-party pytest import. PEP 8 recommends: standard library → third-party → local application imports.

Suggested import order
-from holmes.plugins.toolsets.grafana.common import GrafanaConfig, GrafanaTempoConfig
-from holmes.utils.pydantic_utils import build_config_example
 import pytest
 
 from holmes.core.tools import ToolsetStatusEnum
+from holmes.plugins.toolsets.grafana.common import GrafanaConfig, GrafanaTempoConfig
 from holmes.plugins.toolsets.grafana.loki.toolset_grafana_loki import (
     GrafanaLokiToolset,
 )
 from holmes.plugins.toolsets.grafana.toolset_grafana import GrafanaDashboardConfig, GrafanaToolset
+from holmes.utils.pydantic_utils import build_config_example
 from tests.plugins.toolsets.grafana.conftest import check_service_running

Note: If Ruff is configured in pre-commit hooks, this should be auto-fixed.

Comment thread tests/plugins/toolsets/atlas_mongodb/test_mongodb_atlas.py Outdated
Comment thread tests/plugins/toolsets/bash/test_bash.py Outdated
Comment thread tests/plugins/toolsets/prometheus/test_prometheus.py Outdated
Comment thread tests/plugins/toolsets/prometheus/test_prometheus.py Outdated
Comment thread tests/plugins/toolsets/rabbitmq/test_rabbitmq.py Outdated
@naomi-robusta

Copy link
Copy Markdown
Collaborator

examples

{
  "AzureSQLConfig": {
    "client_id": "{{ env.AZURE_CLIENT_ID }}",
    "client_secret": "{{ env.AZURE_CLIENT_SECRET }}",
    "database": {
      "database_name": "mydatabase",
      "resource_group": "my-resource-group",
      "server_name": "myserver",
      "subscription_id": "12345678-1234-1234-1234-123456789012"
    },
    "tenant_id": "{{ env.AZURE_TENANT_ID }}"
  },
  "BashExecutorConfig": {
    "kubectl": {
      "allowed_images": [
        {
          "allowed_commands": [
            "ping",
            "curl",
            "wget"
          ],
          "image": "busybox:1.36"
        }
      ]
    }
  },
  "CoralogixConfig": {
    "api_key": "cxuw_xxxxxxxxxxxx",
    "domain": "eu2.coralogix.com",
    "labels": {
      "log_message": "logRecord.body",
      "namespace": "resource.attributes.k8s.namespace.name",
      "pod": "resource.attributes.k8s.pod.name",
      "timestamp": "logRecord.attributes.time"
    },
    "team_hostname": "my-team"
  },
  "DatadogGeneralConfig": {
    "allow_custom_endpoints": false,
    "dd_api_key": "<your_datadog_api_key>",
    "dd_app_key": "<your_datadog_app_key>",
    "max_response_size": 10485760,
    "request_timeout": 60,
    "site_api_url": "https://api.datadoghq.com"
  },
  "DatadogLogsConfig": {
    "compact_logs": true,
    "dd_api_key": "<your_datadog_api_key>",
    "dd_app_key": "<your_datadog_app_key>",
    "default_limit": 100,
    "indexes": [
      "*"
    ],
    "request_timeout": 60,
    "site_api_url": "https://api.datadoghq.com",
    "storage_tiers": [
      "indexes"
    ]
  },
  "DatadogMetricsConfig": {
    "dd_api_key": "<your_datadog_api_key>",
    "dd_app_key": "<your_datadog_app_key>",
    "default_limit": 100,
    "request_timeout": 60,
    "site_api_url": "https://api.datadoghq.com"
  },
  "DatadogTracesConfig": {
    "dd_api_key": "<your_datadog_api_key>",
    "dd_app_key": "<your_datadog_app_key>",
    "indexes": [
      "*"
    ],
    "request_timeout": 60,
    "site_api_url": "https://api.datadoghq.com"
  },
  "ElasticsearchConfig": {
    "api_key": "{{ env.ELASTICSEARCH_API_KEY }}",
    "password": "your_password",
    "timeout": 10,
    "url": "https://your-cluster.es.cloud.io",
    "username": "your_username",
    "verify_ssl": true
  },
  "GitHubConfig": {
    "git_branch": "main",
    "git_credentials": "your_git_credentials",
    "git_repo": "robusta-dev/holmesgpt",
    "git_url": "https://api.github.com"
  },
  "GrafanaConfig": {
    "api_key": "YOUR API KEY",
    "external_url": "your_external_url",
    "grafana_datasource_uid": "loki",
    "headers": {
      "Authorization": "Bearer YOUR_API_KEY"
    },
    "url": "YOUR GRAFANA URL",
    "verify_ssl": true
  },
  "GrafanaDashboardConfig": {
    "api_key": "YOUR API KEY",
    "external_url": "your_external_url",
    "grafana_datasource_uid": "loki",
    "headers": {
      "Authorization": "Bearer YOUR_API_KEY"
    },
    "url": "YOUR GRAFANA URL",
    "verify_ssl": true
  },
  "GrafanaTempoConfig": {
    "api_key": "YOUR API KEY",
    "external_url": "your_external_url",
    "grafana_datasource_uid": "loki",
    "headers": {
      "Authorization": "Bearer YOUR_API_KEY"
    },
    "labels": {
      "deployment": "k8s.deployment.name",
      "namespace": "k8s.namespace.name",
      "node": "k8s.node.name",
      "pod": "k8s.pod.name",
      "service": "service.name"
    },
    "url": "YOUR GRAFANA URL",
    "verify_ssl": true
  },
  "KafkaConfig": {
    "kafka_clusters": [
      {
        "kafka_broker": "broker1.example.com:9092,broker2.example.com:9092",
        "kafka_client_id": "holmes-kafka-client",
        "kafka_password": "{{ env.KAFKA_PASSWORD }}",
        "kafka_sasl_mechanism": "PLAIN",
        "kafka_security_protocol": "SASL_SSL",
        "kafka_username": "{{ env.KAFKA_USERNAME }}",
        "name": "us-west-kafka"
      }
    ]
  },
  "MongoDBConfig": {
    "private_key": "your_private_key",
    "project_id": "your_project_id",
    "public_key": "your_public_key"
  },
  "NewrelicConfig": {
    "enable_multi_account": false,
    "is_eu_datacenter": false,
    "nr_account_id": "1234567",
    "nr_api_key": "NRAK-XXXXXXXXXXXXXXXXXXXXXXXXXX"
  },
  "PrometheusConfig": {
    "additional_labels": {
      "cluster": "prod",
      "namespace": "default"
    },
    "discover_metrics_from_last_hours": 1,
    "headers": {
      "Authorization": "Basic <base64_encoded_credentials>"
    },
    "metadata_timeout_seconds_default": 20,
    "metadata_timeout_seconds_hard_max": 60,
    "prometheus_url": "http://prometheus-server.monitoring.svc.cluster.local:9090",
    "query_response_size_limit_pct": 10,
    "query_timeout_seconds_default": 20,
    "query_timeout_seconds_hard_max": 180,
    "rules_cache_duration_seconds": 1800,
    "tool_calls_return_data": true,
    "verify_ssl": true
  },
  "RabbitMQConfig": {
    "clusters": [
      {
        "id": "rabbitmq",
        "management_url": "http://<your-rabbitmq-server-or-service>:15672",
        "password": "holmes_password",
        "request_timeout_seconds": 30,
        "username": "holmes_user",
        "verify_ssl": true
      }
    ]
  },
  "ServiceNowTablesConfig": {
    "api_key": "now_1234567890abcdef",
    "api_key_header": "x-sn-apikey",
    "instance_url": "https://your-instance.service-now.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

🤖 Fix all issues with AI agents
In `@tests/plugins/toolsets/grafana/test_grafana.py`:
- Around line 1-8: Remove the unused imports GrafanaTempoConfig and
GrafanaDashboardConfig from the test file import block; specifically delete the
references to GrafanaTempoConfig (from holmes.plugins.toolsets.grafana.common)
and GrafanaDashboardConfig (from
holmes.plugins.toolsets.grafana.toolset_grafana) so only the required imports
(pytest, ToolsetStatusEnum, GrafanaLokiToolset, GrafanaToolset) remain.
🧹 Nitpick comments (1)
tests/plugins/toolsets/grafana/test_grafana_common.py (1)

1-7: Consider reordering imports to follow PEP8 conventions.

The pytest import (third-party) is placed between application imports. Standard convention groups imports as: standard library → third-party → local application.

Suggested import order
 from typing import Dict, Optional
 
+import pytest
+
 from holmes.plugins.toolsets.grafana.toolset_grafana import GrafanaDashboardConfig
 from holmes.utils.pydantic_utils import build_config_example
-import pytest
-
 from holmes.plugins.toolsets.grafana.common import GrafanaConfig, GrafanaTempoConfig, build_headers

Comment thread tests/plugins/toolsets/grafana/test_grafana.py Outdated
DrFaust92 and others added 6 commits January 21, 2026 13:44
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Deployment image pull policy is now configurable via chart values
instead of being hard-coded. It defaults to "IfNotPresent", allowing
operators to change the pull behavior at deploy/upgrade time without
code changes.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: drfaust92 <ilia.lazebnik@gmail.com>
Fix failing windows builds with flag to support longer file names


Signed-off-by: Roi Glinik <groi.tech@gmail.com>
…change (#1375)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Removed multiple outdated test fixtures for New Relic NRQL query
operations, including queries for crashpod logs, Prometheus logs, and
related infrastructure data
* Added test fixture for TCP connectivity checks against Prometheus
server

* **Chores**
* Updated CI/CD workflow configuration to streamline Windows build
process

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Roi Glinik <groi.tech@gmail.com>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Added troubleshooting for SSL certificate verification errors with
GitHub Enterprise, plus step‑by‑step deployment examples and updated
host examples to use https://.

* **New Features**
* Support for supplying a custom CA certificate to GitHub MCP
deployments across deployment methods.
* Option to bypass SSL certificate verification for self‑signed/internal
CA setups.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Arik Alon <alon.arik@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Naomi Caren <naomi@robusta.dev>
Signed-off-by: Naomi Caren <naomi@robusta.dev>
Comment thread holmes/utils/pydantic_utils.py
Comment thread holmes/utils/pydantic_utils.py
Comment thread holmes/plugins/toolsets/datadog/datadog_api.py
Comment thread holmes/utils/pydantic_utils.py

@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

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/internet/internet.py`:
- Around line 194-196: The current expression accesses
self.toolset.config.additional_headers without ensuring config exists, causing
AttributeError when prerequisites_callable left self.config as None; update the
logic to first get the config (e.g., config = self.toolset.config or
self.config) and then derive additional_headers using a safe check like "if
config and config.additional_headers else {}" so you only access
additional_headers when config is not None; adjust references in the same scope
(look for additional_headers, self.toolset.config, and prerequisites_callable)
accordingly.
🧹 Nitpick comments (3)
holmes/plugins/toolsets/azure_sql/azure_base_toolset.py (1)

32-46: Consider adding cross-field validation for service principal auth fields.

The three auth fields (tenant_id, client_id, client_secret) are individually optional but described as required together for service principal auth. A model_validator could enforce that either all three are provided or none are, catching misconfigurations early.

♻️ Suggested enhancement
+from pydantic import model_validator

 class AzureSQLConfig(BaseModel):
     database: AzureSQLDatabaseConfig = Field(
         description="Azure SQL database connection details",
     )
     tenant_id: Optional[str] = Field(
         default=None,
         description="Azure AD tenant ID (required for service principal auth)",
         examples=["{{ env.AZURE_TENANT_ID }}"],
     )
     client_id: Optional[str] = Field(
         default=None,
         description="Azure AD client/application ID (required for service principal auth)",
         examples=["{{ env.AZURE_CLIENT_ID }}"],
     )
     client_secret: Optional[str] = Field(
         default=None,
         description="Azure AD client secret (required for service principal auth)",
         examples=["{{ env.AZURE_CLIENT_SECRET }}"],
     )
+
+    `@model_validator`(mode="after")
+    def validate_service_principal_auth(self) -> "AzureSQLConfig":
+        auth_fields = [self.tenant_id, self.client_id, self.client_secret]
+        provided = [f for f in auth_fields if f is not None]
+        if provided and len(provided) != 3:
+            raise ValueError(
+                "Service principal auth requires all of: tenant_id, client_id, client_secret"
+            )
+        return self
holmes/plugins/toolsets/internet/internet.py (2)

224-233: Missing blank line between class definitions.

PEP 8 recommends two blank lines between top-level class definitions for readability.

Suggested fix
         {"Authorization": "Bearer <token>"},
         ]
     )
+
+
 class InternetBaseToolset(Toolset):

261-265: Consider always initializing config to avoid None state.

When config is falsy, returning early leaves self.config as None. A more robust approach is to always initialize with defaults.

Proposed fix
     def prerequisites_callable(self, config: Dict[str, Any]) -> Tuple[bool, str]:
-        if not config:
-            return True, ""
-        self.config = InternetBaseToolsetConfig(**config)
+        self.config = InternetBaseToolsetConfig(**(config or {}))
         return True, ""

This approach ensures self.config is always a valid InternetBaseToolsetConfig instance with defaults applied, eliminating the need for None checks elsewhere.

Comment thread holmes/plugins/toolsets/internet/internet.py

@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: 3

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/datadog/toolset_datadog_traces.py`:
- Around line 12-23: The import list currently pulls ClassVar and Type from
holmes.core.tools; change it to import ClassVar and Type directly from the
typing module and remove them from the holmes.core.tools import list—keep all
other symbols (CallablePrerequisite, StructuredToolResult,
StructuredToolResultStatus, Tool, ToolInvokeContext, ToolParameter, Toolset,
ToolsetTag) imported from holmes.core.tools so references to ClassVar and Type
used elsewhere in this file (e.g., any annotations) come from typing instead of
holmes.core.tools.

In `@holmes/plugins/toolsets/kafka.py`:
- Around line 28-36: The imports currently pull ClassVar and Type from
holmes.core.tools; update the import statements so ClassVar and Type are
imported from the standard typing module instead (e.g., add "from typing import
ClassVar, Type") and remove ClassVar and Type from the holmes.core.tools import
list (which contains StructuredToolResult, Tool, Toolset, etc.) so only the
typing constructs come from typing and the tool classes remain imported from
holmes.core.tools.

In `@holmes/plugins/toolsets/mcp/toolset_mcp.py`:
- Around line 98-115: prerequisites_callable currently defaults to SSE when mode
is missing, which makes dicts that include stdio-specific keys like "command"
validate against MCPConfig (requiring url) and fail; update
prerequisites_callable to infer and set the correct mode before instantiating
the config: if the incoming config dict contains "command" or "args" treat it as
StdioMCPConfig (set mode=MCPMode.STDIO) and if it contains "url" treat it as
SseMCPConfig (set mode=MCPMode.SSE), then instantiate the appropriate class (or
alternatively make the mode Field required on MCPConfig/StdioMCPConfig so
missing mode cannot occur); reference MCPMode, MCPConfig, StdioMCPConfig and the
prerequisites_callable function when applying the change.
♻️ Duplicate comments (5)
holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py (1)

9-20: Import ClassVar/Type from typing (ruff RUF012).

Same issue as previously noted: holmes.core.tools indirection can keep lint warnings.

🛠️ Suggested fix
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, ClassVar, Dict, Optional, Tuple, Type

 from holmes.core.tools import (
     CallablePrerequisite,
-    ClassVar,
     StructuredToolResult,
     StructuredToolResultStatus,
     Tool,
     ToolInvokeContext,
     ToolParameter,
     Toolset,
     ToolsetTag,
-    Type,
 )
holmes/plugins/toolsets/datadog/toolset_datadog_general.py (1)

10-21: Same ClassVar/Type import issue as in bash_toolset.py.

Please import ClassVar and Type from typing here as well to avoid potential import errors and Ruff RUF012.

Also applies to: 203-204

holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py (1)

10-43: Same ClassVar/Type import issue as in bash_toolset.py.

Please import ClassVar and Type from typing to avoid potential import errors and Ruff RUF012.

holmes/plugins/toolsets/internet/internet.py (1)

194-196: Potential AttributeError when config is None.

When prerequisites_callable receives an empty config (lines 264-265), self.config remains None. Accessing self.toolset.config.additional_headers will raise AttributeError.

Proposed fix
         additional_headers = (
-            self.toolset.config.additional_headers if self.toolset.config.additional_headers else {}
+            self.toolset.config.additional_headers if self.toolset.config and self.toolset.config.additional_headers else {}
         )
holmes/plugins/toolsets/kafka.py (1)

43-86: Add extra="allow" to config models for backwards compatibility.

Based on learnings, toolset config models should use extra="allow" to maintain backwards compatibility when fields are renamed or deprecated in future versions. This allows unknown keys to be preserved rather than silently dropped.

♻️ Recommended change
 class KafkaClusterConfig(BaseModel):
+    model_config = ConfigDict(extra="allow")
+
     name: str = Field(
 class KafkaConfig(BaseModel):
+    model_config = ConfigDict(extra="allow")
+
     kafka_clusters: List[KafkaClusterConfig] = Field(
🧹 Nitpick comments (3)
holmes/plugins/toolsets/bash/bash_toolset.py (1)

14-26: Consider importing ClassVar and Type from typing module for consistency with Python conventions.

While ClassVar and Type are currently re-exported from holmes.core.tools (lines 17, 23), the standard practice in Python is to import these typing constructs directly from the typing module. This improves code clarity and aligns with type-checking best practices.

Suggested fix
-from typing import Any, Dict, Optional
+from typing import Any, ClassVar, Dict, Optional, Type
 ...
-from holmes.core.tools import (
-    ApprovalRequirement,
-    CallablePrerequisite,
-    ClassVar,
-    StructuredToolResult,
-    StructuredToolResultStatus,
-    Tool,
-    ToolInvokeContext,
-    ToolParameter,
-    Toolset,
-    ToolsetTag,
-    Type,
-)
+from holmes.core.tools import (
+    ApprovalRequirement,
+    CallablePrerequisite,
+    StructuredToolResult,
+    StructuredToolResultStatus,
+    Tool,
+    ToolInvokeContext,
+    ToolParameter,
+    Toolset,
+    ToolsetTag,
+)

Also applies to: line 234

holmes/plugins/toolsets/elasticsearch/elasticsearch.py (1)

70-70: Consider using List from typing for consistency.

The config_classes attribute uses lowercase list[Type[...]] syntax. While this is valid in Python 3.9+, other toolsets and the base Toolset class use List[Type[BaseModel]] from typing. Consider aligning for consistency across the codebase.

♻️ Suggested change for consistency
-    config_classes: ClassVar[list[Type[ElasticsearchConfig]]] = [ElasticsearchConfig]
+    config_classes: ClassVar[List[Type[ElasticsearchConfig]]] = [ElasticsearchConfig]
holmes/plugins/toolsets/internet/internet.py (1)

233-238: Minor: Missing blank line between class definitions.

There's no blank line between InternetBaseToolsetConfig and InternetBaseToolset class definitions. PEP 8 recommends two blank lines between top-level class definitions for readability.

♻️ Add blank line
         ],
     )
+
+
 class InternetBaseToolset(Toolset):

Comment thread holmes/plugins/toolsets/datadog/toolset_datadog_traces.py
Comment thread holmes/plugins/toolsets/kafka.py
Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
holmes/plugins/toolsets/bash/bash_toolset.py (1)

43-87: Critical: Unresolved merge conflict markers will break the file.

The file contains unresolved merge conflict markers (<<<<<<< HEAD, =======, >>>>>>> master) that will cause Python syntax errors. The static analysis tool also flagged this with "Expected a statement" errors.

This must be resolved before the PR can be merged. Based on the context, it appears the HEAD side had no content for the docstring Args section, while master includes the full docstring. You likely want to keep the master version:

🐛 Fix: Remove merge conflict markers
     """
     Convert a BashResult to a StructuredToolResult.

-<<<<<<< HEAD
-=======
     Args:
         result: The BashResult from execute_bash_command
         cmd: The original command (for error messages)
         timeout: The timeout value (for error messages)
         params: Parameters to include in the result

     Returns:
         StructuredToolResult suitable for the tool response
     """
     if result.timed_out:
         # ... rest of function
->>>>>>> master
🧹 Nitpick comments (2)
holmes/core/tools.py (1)

836-856: Inconsistent handling of multiple config classes.

get_config_schema() returns schemas for all config classes, but get_config_example() only returns the example for the first config class. Given the commit message mentions "support multiple config classes per toolset," consider returning examples for all config classes for consistency:

♻️ Suggested refactor for consistent multi-config handling
     def get_config_example(self) -> Optional[Dict[str, Any]]:
-        """Returns a JSON-serializable example object for the toolset's configuration.
+        """Returns JSON-serializable example objects for the toolset's configuration.
 
-        Returns the example of the first config class (if any), otherwise returns None.
+        Returns a dict of { config_class_name: example } (if any), otherwise returns None.
         """
         if self.config_classes:
-            return build_config_example(self.config_classes[0])
+            return {
+                config_cls.__name__: build_config_example(config_cls)
+                for config_cls in self.config_classes
+            }
         return None
-        

Alternatively, if only returning the first example is intentional, update the docstring to explain the rationale.

holmes/plugins/toolsets/bash/bash_toolset.py (1)

12-24: Consider importing ClassVar and Type from typing directly.

Importing ClassVar and Type from holmes.core.tools works (since they're imported at module level there), but it's non-idiomatic. These typing constructs should be imported from their canonical source:

♻️ Suggested change
 import logging
 import os
-from typing import Any, Dict, Optional
+from typing import Any, ClassVar, Dict, Optional, Type

 from holmes.core.tools import (
     ApprovalRequirement,
     CallablePrerequisite,
-    ClassVar,
     StructuredToolResult,
     StructuredToolResultStatus,
     Tool,
     ToolInvokeContext,
     ToolParameter,
     Toolset,
     ToolsetTag,
-    Type,
 )

naomi-robusta
naomi-robusta previously approved these changes Jan 25, 2026

@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

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/internet/internet.py`:
- Around line 263-265: prerequisites_callable currently instantiates
InternetBaseToolsetConfig(**config) without handling pydantic.ValidationError
which can crash initialization; wrap that construction in a try/except that
catches pydantic.ValidationError (or Exception if ValidationError is not
imported), and on exception set self.config to None (or leave unchanged), and
return (False, "<clear validation error message>") where the message includes
the validation error details; update the function InternetBaseToolsetConfig
usage in prerequisites_callable to return (True, "") only when config validation
succeeds.

In `@tests/test_holmes_sync_toolsets.py`:
- Line 1: Remove the unused import from tests/test_holmes_sync_toolsets.py by
deleting the line that imports the json module (the "import json" statement)
since it's not referenced elsewhere in the file; ensure no other code depends on
json before removing.
♻️ Duplicate comments (1)
holmes/plugins/toolsets/internet/internet.py (1)

194-196: Guard against None config when deriving headers.

If _invoke is called before prerequisites_callable, self.toolset.config can be None, which raises AttributeError. Add a safe guard.

🐛 Proposed fix
-        additional_headers = (
-            self.toolset.config.additional_headers if self.toolset.config.additional_headers else {}
-        )
+        config = self.toolset.config
+        additional_headers = (
+            config.additional_headers if config and config.additional_headers else {}
+        )
🧹 Nitpick comments (1)
tests/test_holmes_sync_toolsets.py (1)

92-113: Test assertions don't match test name and comment.

The test is named test_sync_toolsets_with_config_schema and the comment states "should have null schema", but the assertion only verifies installation_instructions is not None. Consider parsing the JSON structure and verifying the config_schema field is actually null for toolsets without config_classes.

Proposed enhancement to verify config_schema
     mock_dal.sync_toolsets.assert_called_once()
     toolset_data = mock_dal.sync_toolsets.call_args[0][0][0]
-    assert toolset_data["installation_instructions"] is not None
+    assert toolset_data["installation_instructions"] is not None
+    
+    # Verify the config_schema is null for toolsets without config_classes
+    instructions_json = json.loads(toolset_data["installation_instructions"])
+    assert instructions_json.get("config_schema") is None

Note: If you apply this fix, the json import on line 1 would then be needed.

Comment thread holmes/plugins/toolsets/internet/internet.py
Comment thread tests/test_holmes_sync_toolsets.py Outdated

@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

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/internet/internet.py`:
- Around line 263-267: In prerequisites_callable, don't catch a broad Exception
when instantiating InternetBaseToolsetConfig; import and catch
pydantic.ValidationError specifically and preserve other exceptions to surface;
update the except clause in prerequisites_callable to except ValidationError as
e and return the same failure message (using e) so only validation failures are
handled while other errors propagate.

In `@holmes/utils/holmes_sync_toolsets.py`:
- Around line 71-73: The current check skips empty example configs because it
tests truthiness of example_config; change the guard around
toolset.get_config_example() to explicitly test for None (e.g., use "if
example_config is not None:") so empty mappings like {} are preserved and still
assigned via context["example_config"] = yaml.dump(example_config); update the
code path referencing example_config, toolset.get_config_example(), and
context["example_config"] accordingly.
♻️ Duplicate comments (2)
holmes/utils/holmes_sync_toolsets.py (1)

48-58: Wrap installation_instructions as structured JSON (instructions + schema + example).
Right now this block still stores only the rendered markdown, so the frontend can’t reliably parse config_schema / example_config as promised by the PR objectives.

🐛 Suggested direction
+import json
+
+def _json_serializer(obj: Any) -> Any:
+    if hasattr(obj, "isoformat"):
+        return obj.isoformat()
+    raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
...
-        if not toolset.installation_instructions:
-            instructions = render_default_installation_instructions_for_toolset(toolset)
-            toolset.installation_instructions = instructions
+        if not toolset.installation_instructions:
+            toolset.installation_instructions = render_default_installation_instructions_for_toolset(toolset)
+
+        payload = {
+            "instructions": toolset.installation_instructions,
+            "config_schema": toolset.get_config_schema(),
+            "example_config": toolset.get_config_example(),
+        }
+        toolset.installation_instructions = json.dumps(payload, default=_json_serializer)
holmes/plugins/toolsets/internet/internet.py (1)

194-196: Guard against self.toolset.config being None.

Line 194 currently dereferences self.toolset.config.additional_headers without a null check; this can raise AttributeError if prerequisites haven’t run or failed.

🐛 Suggested fix
-        additional_headers = (
-            self.toolset.config.additional_headers if self.toolset.config.additional_headers else {}
-        )
+        config = self.toolset.config
+        additional_headers = (
+            config.additional_headers if (config and config.additional_headers) else {}
+        )
🧹 Nitpick comments (1)
tests/test_holmes_sync_toolsets.py (1)

90-112: Strengthen test to validate JSON payload shape.
The test name says “with_config_schema” but it only checks non-null instructions. Consider parsing the payload and asserting the presence (and nullness) of config_schema/example_config to lock in the frontend contract.

Comment thread holmes/plugins/toolsets/internet/internet.py
Comment thread holmes/utils/holmes_sync_toolsets.py

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
holmes/plugins/toolsets/internet/notion.py (1)

51-54: Update header access to the new internet_config field.

InternetBaseToolset no longer exposes additional_headers, so this will raise AttributeError at runtime. Use internet_config and guard for None.

🐛 Suggested fix
-        additional_headers = (
-            self.toolset.additional_headers if self.toolset.additional_headers else {}
-        )
+        internet_config = self.toolset.internet_config
+        additional_headers = (
+            internet_config.additional_headers
+            if internet_config and internet_config.additional_headers
+            else {}
+        )
♻️ Duplicate comments (2)
holmes/plugins/toolsets/internet/internet.py (2)

194-196: Guard against internet_config being None before accessing headers.

If prerequisites fail or aren’t called, self.toolset.internet_config can be None, which will raise AttributeError here. Safer fallback avoids runtime failures.

🐛 Suggested fix
-        additional_headers = (
-            self.toolset.internet_config.additional_headers if self.toolset.internet_config.additional_headers else {}
-        )
+        internet_config = self.toolset.internet_config
+        additional_headers = (
+            internet_config.additional_headers
+            if internet_config and internet_config.additional_headers
+            else {}
+        )

264-268: Catch ValidationError instead of broad Exception.

BaseModel validation failures raise pydantic.ValidationError; catching Exception masks unexpected bugs and triggers Ruff BLE001.

🧩 Suggested fix
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field, ValidationError
@@
-        try:
-            self.internet_config = InternetBaseToolsetConfig(**(config or {}))
-        except Exception as e:
-            return False, f"Failed to parse config: {e}"
+        try:
+            self.internet_config = InternetBaseToolsetConfig(**(config or {}))
+        except ValidationError as e:
+            return False, f"Failed to parse config: {e}"
In Pydantic v2, does BaseModel(...) raise pydantic.ValidationError for invalid input, and is it recommended to catch ValidationError specifically instead of Exception?

@naomi-robusta
naomi-robusta merged commit 453c501 into master Jan 25, 2026
16 of 17 checks passed
@naomi-robusta
naomi-robusta deleted the claude/document-holmesgpt-data-reporting-axz0d branch January 25, 2026 18:42
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.

7 participants