Skip to content

Add SQL database toolset for read-only database queries - #1568

Merged
arikalon1 merged 16 commits into
masterfrom
claude/database-toolset-exploration-gOVFe
Feb 23, 2026
Merged

arikalon1 merged 16 commits into
masterfrom
claude/database-toolset-exploration-gOVFe

Conversation

@arikalon1

@arikalon1 arikalon1 commented Feb 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces a new Database/SQL toolset that enables Holmes to query SQL databases (PostgreSQL, MySQL, SQLite, SQL Server) with read-only access. The toolset provides three tools for exploring and querying databases safely.

Key Changes

New Database Toolset (holmes/plugins/toolsets/database/)

  • database.py: Core toolset implementation with:

    • DatabaseToolset: Main toolset class managing database connections
    • DatabaseConfig: Configuration class for connection URLs
    • DatabaseQuery: Tool to execute read-only SQL queries (SELECT, SHOW, DESCRIBE, EXPLAIN, WITH)
    • DatabaseListTables: Tool to list tables and views in a database
    • DatabaseDescribeTable: Tool to inspect table schema (columns, types, constraints, indexes)
    • _normalise_url(): Converts database URLs to use pure-Python drivers (pg8000, PyMySQL, pymssql) for better compatibility
    • _serialize_value(): Converts database values to JSON-safe types
    • Read-only validation with regex patterns to block write operations (INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, etc.)
    • Result limiting (max 200 rows per query)
  • instructions.jinja2: LLM instructions for using the database tools

Tests

  • tests/test_database_toolset.py: Comprehensive unit tests covering:

    • URL normalization for different database drivers
    • Value serialization for JSON compatibility
    • Configuration validation
    • Read-only enforcement (blocking INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, etc.)
    • Allowed operations (SELECT, SHOW, DESCRIBE, EXPLAIN, WITH)
    • End-to-end query execution with SQLite
  • tests/llm/fixtures/test_ask_holmes/217_database_sql_query/: Integration test case:

    • test_case.yaml: Test scenario that verifies Holmes can discover table structure and query data
    • toolsets.yaml: Toolset configuration for the test

Integration

  • Updated holmes/plugins/toolsets/__init__.py to register the new DatabaseToolset
  • Added holmes/plugins/toolsets/database/__init__.py module initialization

Notable Implementation Details

  • Security: Write operations are blocked at two levels:

    1. Regex pattern matching on SQL statements
    2. Validation before query execution
  • Driver Compatibility: Automatically rewrites connection URLs to use pure-Python drivers (no C extensions required), making the toolset work in containerized environments

  • Database Support: Works with PostgreSQL, MySQL/MariaDB, SQLite, and SQL Server via SQLAlchemy

  • Health Checks: Includes connection validation during prerequisite checks

  • Code Quality: Includes formatting improvements to several existing files (line length, import ordering)

https://claude.ai/code/session_01MDfhnnq4yosJYeU5hdFztJ

Summary by CodeRabbit

  • New Features

    • Added a SQL database toolset for read-only querying, table listing, and schema inspection against SQLAlchemy-compatible databases.
  • Bug Fixes

    • Fixed test skip reason wording and other minor test text fixes.
  • Documentation

    • Added usage guidance and best practices for the new database toolset.
  • Tests

    • Added comprehensive tests validating database behavior, URL normalization, serialization, and tool outputs.
  • Chores

    • Broad formatting and import cleanups across the codebase.

@netlify

netlify Bot commented Feb 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 1ad88b6
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/699c28ecc40e6a00084065ad
😎 Deploy Preview https://deploy-preview-1568--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 Feb 15, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

📜 Run @ f194c3b (#22300749260)

✅ Results of HolmesGPT evals

Automatically triggered by commit f194c3b on branch claude/database-toolset-exploration-gOVFe

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.6s 5 11 $0.2360
✅ 101_loki_historical_logs_pod_deleted 39.2s 5 10 $0.2576
✅ 111_pod_names_contain_service 43.4s 9 13 $0.2881
✅ 112_find_pvcs_by_uuid 26.3s 5 6 $0.2246
✅ 12_job_crashing 31.6s 6 10 $0.2381
✅ 176_network_policy_blocking_traffic_no_runbooks 45.3s 7 17 $0.2941
✅ 24_misconfigured_pvc 38.1s 6 15 $0.2696
✅ 43_current_datetime_from_prompt 4.9s 1 — $0.1121
✅ 61_exact_match_counting 12.1s 3 2 $0.1501
Total 30.2s avg 5.2 avg 10.5 avg $2.0704
📜 Run @ 0be49cd (#22299333162)

✅ Results of HolmesGPT evals

Automatically triggered by commit 0be49cd on branch claude/database-toolset-exploration-gOVFe

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 32.2s 5 11 $0.2394
✅ 101_loki_historical_logs_pod_deleted 44.1s 5 10 $0.2686
✅ 111_pod_names_contain_service 33.8s 5 12 $0.2374
✅ 112_find_pvcs_by_uuid 27.1s 4 6 $0.2361
✅ 12_job_crashing 33.6s 5 12 $0.2453
✅ 176_network_policy_blocking_traffic_no_runbooks 43.8s 7 17 $0.3058
✅ 24_misconfigured_pvc 31.1s 5 13 $0.2356
✅ 43_current_datetime_from_prompt 5.1s 1 — $0.0127
✅ 61_exact_match_counting 13.4s 3 2 $0.1516
Total 29.3s avg 4.4 avg 10.4 avg $1.9325
📜 Run @ 4ebf33f (#22068101706)

✅ Results of HolmesGPT evals

Automatically triggered by commit 4ebf33f on branch claude/database-toolset-exploration-gOVFe

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 35.9s 6 11 $0.2402
✅ 101_loki_historical_logs_pod_deleted 33.9s 4 8 $0.2194
✅ 111_pod_names_contain_service 34.3s 5 11 $0.2258
✅ 112_find_pvcs_by_uuid 40.4s 7 9 $0.2748
✅ 12_job_crashing 32.3s 5 11 $0.2380
✅ 176_network_policy_blocking_traffic_no_runbooks 48.5s 6 17 $0.2923
✅ 24_misconfigured_pvc 34.5s 5 14 $0.2347
✅ 43_current_datetime_from_prompt 5.7s 1 — $0.1086
✅ 61_exact_match_counting 17.6s 4 4 $0.1631
Total 31.5s avg 4.8 avg 10.6 avg $1.9969
📜 Run @ c9ab24e (#22039539098)

✅ Results of HolmesGPT evals

Automatically triggered by commit c9ab24e on branch claude/database-toolset-exploration-gOVFe

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 33.0s 5 11 $0.2349
✅ 101_loki_historical_logs_pod_deleted 67.4s 10 15 $0.3444
✅ 111_pod_names_contain_service 33.6s 5 11 $0.2300
✅ 112_find_pvcs_by_uuid 39.2s 7 11 $0.2947
✅ 12_job_crashing 31.4s 5 11 $0.2345
✅ 176_network_policy_blocking_traffic_no_runbooks 47.5s 7 16 $0.3157
✅ 24_misconfigured_pvc 32.7s 5 13 $0.2339
✅ 43_current_datetime_from_prompt 4.6s 1 — $0.1075
✅ 61_exact_match_counting 11.9s 3 2 $0.1452
Total 33.5s avg 5.3 avg 11.2 avg $2.1408
📜 Run @ 622a5b8 (#22039340532)

✅ Results of HolmesGPT evals

Automatically triggered by commit 622a5b8 on branch claude/database-toolset-exploration-gOVFe

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 33.7s 6 10 $0.2351
✅ 101_loki_historical_logs_pod_deleted 45.4s 6 11 $0.2646
✅ 111_pod_names_contain_service 34.9s 5 13 $0.2438
✅ 112_find_pvcs_by_uuid 32.7s 6 7 $0.2328
✅ 12_job_crashing 25.7s 4 8 $0.2025
✅ 176_network_policy_blocking_traffic_no_runbooks 48.2s 6 16 $0.3067
✅ 24_misconfigured_pvc 40.1s 7 16 $0.2715
✅ 43_current_datetime_from_prompt 4.7s 1 — $0.1073
✅ 61_exact_match_counting 16.9s 4 4 $0.1625
Total 31.4s avg 5.0 avg 10.6 avg $2.0269

✅ Results of HolmesGPT evals

Automatically triggered by commit 1ad88b6 on branch claude/database-toolset-exploration-gOVFe

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 37.2s 6 12 $0.2594
✅ 101_loki_historical_logs_pod_deleted 41.6s 5 10 $0.2510
✅ 111_pod_names_contain_service 32.7s 5 10 $0.2230
✅ 112_find_pvcs_by_uuid 32.7s 6 7 $0.2360
✅ 12_job_crashing 38.0s 6 13 $0.2752
✅ 176_network_policy_blocking_traffic_no_runbooks 38.1s 5 13 $0.2673
✅ 24_misconfigured_pvc 34.7s 6 13 $0.2423
✅ 43_current_datetime_from_prompt 5.6s 1 — $0.1122
✅ 61_exact_match_counting 12.4s 3 2 $0.1495
Total 30.3s avg 4.8 avg 10.0 avg $2.0159
📖 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/database-toolset-exploration-gOVFe -f markers=regression -f filter=

Option 1: Comment on this PR with /eval:

/eval
tags: regression

Or with more options (one per line):

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

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

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

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

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

Option 3: Add PR labels to include extra evals in automatic regression runs:

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

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

🏷️ Valid tags

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


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/database-toolset-exploration-gOVFe -f markers=regression -f filter=

@github-actions

github-actions Bot commented Feb 15, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker images ready for 089e5713 (built in 1m 16s)

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

Use these tags to pull the images for testing.

📋 Copy commands

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

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

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

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

@coderabbitai

coderabbitai Bot commented Feb 15, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new read-only DatabaseToolset (query, list tables, describe table) with URL normalization and health checks; includes unit and integration tests. Also applies many non-functional formatting/import cleanups across docs, tests, and existing toolsets.

Changes

Cohort / File(s) Summary
Database Toolset Implementation
holmes/plugins/toolsets/database/database.py, holmes/plugins/toolsets/database/__init__.py, holmes/plugins/toolsets/database/instructions.jinja2
New DatabaseToolset: URL normalization, SQLAlchemy engine usage, health check, read-only enforcement, result serialization, and three tools (DatabaseQuery, DatabaseListTables, DatabaseDescribeTable).
Toolset Registration
holmes/plugins/toolsets/__init__.py
Imported and registered DatabaseToolset() in Python toolset loader.
Pydantic Utilities
holmes/utils/pydantic_utils.py
Added build_config_example() and _extract_base_model_subclass() helpers for generating config examples.
Database Tests & Fixtures
tests/test_database_toolset.py, tests/llm/fixtures/test_ask_holmes/217_database_sql_query/test_case.yaml, tests/llm/fixtures/test_ask_holmes/217_database_sql_query/toolsets.yaml
New comprehensive unit tests for DatabaseToolset and new integration fixture/test case for SQL queries (Postgres URL normalization + setup/teardown).
Formatting / Docs
.claude/skills/create-eval/*, docs/development/evaluations/history/*, docs/data-sources/builtin-toolsets/splunk-mcp.md
Minor Markdown edits (bullets, trailing newlines); removed two Splunk MCP resource links.
Toolset Formatting Changes
holmes/plugins/toolsets/* (datadog/*, coralogix/utils.py, elasticsearch/elasticsearch.py, grafana/common.py, http/http_toolset.py, internet/internet.py, servicenow_tables/servicenow_tables.py)
Non-functional formatting/import reflows and minor signature formatting changes (no behavior change).
Utilities & Scripts Formatting
holmes/utils/holmes_sync_toolsets.py, scripts/cli_performance_benchmark.py
Import reordering, duplicate yaml import noted, and multiline formatting only.
Tests Cleanup & Reflow
tests/... (many test files and fixtures listed in diff)
Import hygiene, whitespace and multiline reflows, small test assertion formatting; no behavioral changes.
Miscellaneous Small Edits
holmes/core/scheduled_prompts/executor.py, holmes/utils/holmes_sync_toolsets.py, holmes/plugins/toolsets/coralogix/utils.py, etc.
Minor type alias reflow, duplicate import, and string formatting adjustments without logic changes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant DatabaseToolset
    participant Engine as SQLAlchemy_Engine
    participant Database

    Client->>DatabaseToolset: Request DatabaseQuery(sql, params)
    DatabaseToolset->>DatabaseToolset: Validate read-only SQL
    DatabaseToolset->>Engine: Build engine from normalized URL
    DatabaseToolset->>Engine: Execute query (LIMIT ≤200)
    Engine->>Database: Execute SELECT
    Database-->>Engine: Return rows & metadata
    Engine-->>DatabaseToolset: Rows + columns
    DatabaseToolset->>DatabaseToolset: Serialize values to JSON-safe types
    DatabaseToolset-->>Client: {columns, rows, row_count, truncated}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • moshemorad
  • Sheeproid
🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the primary feature being added: a SQL database toolset enabling read-only database queries.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

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

@github-actions

github-actions Bot commented Feb 15, 2026 •

Copy link
Copy Markdown
Contributor

🔬 CLI Performance Benchmark

🟡 Startup Time (no LLM)

Measures holmes version execution time (imports + initialization)

Metric PR Master Change
Cold Start 11.51s 10.53s +9.3%
Warm Mean 5.49s 4.60s +19.3%
Warm Min 5.37s 4.59s
Warm Max 5.60s 4.60s

🟡 Full CLI with LLM

Measures holmes ask execution time (OpenRouter + Haiku 4.5)

Metric PR Master Change
Cold Start 32.70s 36.01s -9.2%
Warm Mean 8.17s 7.38s +10.7%
Warm Min 8.10s 6.96s
Warm Max 8.28s 7.76s

PR: 089e5713 | Master: 1260df80 | Iterations: 5

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

Caution

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

⚠️ Outside diff range comments (1)
holmes/utils/pydantic_utils.py (1)

144-149: ⚠️ Potential issue | 🟡 Minor

Docstring step numbering is out of order.

Steps are listed as 1, 3, 2, 4, 5 instead of sequential 1–5.

📝 Suggested fix
-    Logic (in order):
-    1) Keys are derived from the field/variable name.
-    3) If available, use the first value from the field's examples array.
-    2) Otherwise, use default or default_factory to generate the example value.
-    4) If it's still None and the field type extends BaseModel, recursively build nested object example.
-    5) Otherwise, use "your_<variable_name>" (even for lists, dicts, primitive, and any other type).
+    Logic (in order):
+    1) Keys are derived from the field/variable name.
+    2) If available, use the first value from the field's examples array.
+    3) Otherwise, use default or default_factory to generate the example value.
+    4) If it's still None and the field type extends BaseModel, recursively build nested object example.
+    5) Otherwise, use "your_<variable_name>" (even for lists, dicts, primitive, and any other type).
🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/database/database.py`:
- Around line 26-35: The current prefix-only regex checks (_READONLY_PATTERN and
_WRITE_PATTERN) can be bypassed by writable CTEs; update the guard in the module
(referencing _READONLY_PATTERN and _WRITE_PATTERN) to either (A) scan the entire
SQL string for write keywords (apply _WRITE_PATTERN.search over the full query
body and also check for "WITH ... AS" blocks that contain write verbs) or (B,
preferred defense-in-depth) enforce a DB-level read-only transaction when
executing queries (e.g., set the connection/transaction to read-only via the DB
API or SQLAlchemy execution_options/SET TRANSACTION READ ONLY) so even queries
with embedded write statements cannot mutate data; implement one of these fixes
where queries are executed (the function that uses these patterns) and remove
reliance on prefix-only matching.
- Around line 302-336: The error handler in DatabaseListTables' _invoke does not
include the schema (and other params) in the returned error; update the except
block to include the schema and relevant params (e.g., schema =
params.get("schema") and include_views = params.get("include_views")) and embed
them in the StructuredToolResult.error message (or an additional field)
alongside the exception string so the error explicitly states which schema and
options were used when calling _toolset.database_config.connection_url via
sqlalchemy.create_engine and inspector calls.
- Around line 128-138: Move the inline "import sqlalchemy" statements out of the
functions and to the top of the module using a module-level optional import
pattern (try: import sqlalchemy except ImportError: sqlalchemy = None). Update
the functions that currently import sqlalchemy inline (the connection-check
routine that calls _normalise_url and engine.connect(), plus the other routines
at the places flagged) to first verify sqlalchemy is available and return/raise
a clear error if it's None; keep existing behavior otherwise. Ensure you
reference the module-level sqlalchemy symbol (not local imports) in calls like
sqlalchemy.create_engine, sqlalchemy.text and engine.dialect.name.

In
`@tests/llm/fixtures/test_ask_holmes/93_events_since_specific_date/test_case.yaml`:
- Line 13: In the test case YAML entry for the fixture (the skip_reason field),
fix the typo by changing the value from "this sometimes fails to to missing mock
errors" to "this sometimes fails due to missing mock errors" so the skip_reason
reads correctly; update the skip_reason string in
test_ask_holmes/93_events_since_specific_date/test_case.yaml accordingly.

In `@tests/test_database_toolset.py`:
- Around line 151-172: The test uses deprecated tempfile.mktemp to create
db_file; replace it by creating a tempfile.NamedTemporaryFile(delete=False) and
use its .name as the SQLite path (close the NamedTemporaryFile before opening
the DB so SQLite can access it), then proceed to create the engine and run the
queries as before; ensure the finally block still unlinks the temporary file.
Update references in this test around the db_file creation and keep
DatabaseConfig(connection_url=f"sqlite:///{db_file}") and
self.toolset.execute_query usage unchanged.
- Around line 108-110: The test test_config_requires_url is catching a bare
Exception which hides unexpected failures; update it to expect
pydantic.errors.ValidationError instead of Exception by importing
ValidationError from pydantic and using pytest.raises(ValidationError) when
instantiating DatabaseConfig() so the test only passes for the specific
required-field validation failure.
🧹 Nitpick comments (8)
tests/llm/fixtures/test_ask_holmes/212_large_configmap_needle/generate_configs.py (1)

177-200: Consider adding type hints to improve maintainability.

The functions random_hex, random_version, random_connection_string, generate_service_config, and main lack type hints. Adding them would improve code clarity and enable better static analysis. As per coding guidelines, type hints are required for Python files.

📝 Example type hints
def random_hex(rng: random.Random, length: int = 6) -> str:
    return "".join(rng.choice("0123456789abcdef") for _ in range(length))


def random_version(rng: random.Random) -> str:
    return f"{rng.randint(1, 12)}.{rng.randint(0, 30)}.{rng.randint(0, 99)}"


def random_connection_string(rng: random.Random, service_name: str) -> str:
    # ... existing implementation


def generate_service_config(
    rng: random.Random, name: str, connection_string: str | None = None
) -> str:
    # ... existing implementation


def main() -> None:
    # ... existing implementation

Also applies to: 202-308, 311-357

tests/llm/fixtures/test_ask_holmes/217_database_sql_query/test_case.yaml (1)

27-69: before_test setup looks solid, but URL normalization is duplicated.

The URL normalization logic (replacing postgresql:// → postgresql+pg8000://) is duplicated between this script and the main database.py toolset code. This is acceptable for test independence, but worth noting.

One minor concern: the before_test script doesn't handle the case where POSTGRES_URL already contains a +driver suffix (e.g., postgresql+psycopg2://...). If a CI environment provides such a URL, the normalization would be skipped and pg8000 wouldn't be available, causing a silent failure.

holmes/plugins/toolsets/database/instructions.jinja2 (1)

1-9: Consider mentioning the 200-row hard cap.

The DatabaseQuery tool enforces a _MAX_ROWS = 200 limit, but the instructions only say "Use LIMIT clauses" without mentioning this ceiling. If the LLM requests LIMIT 500, it will silently get 200 rows with truncated: true, which could confuse it. Adding a note like "Results are capped at 200 rows regardless of LIMIT" would help the LLM plan queries with appropriate aggregation.

tests/test_database_toolset.py (2)

91-100: Move repeated imports to the top of the file.

datetime, Decimal, sqlalchemy, tempfile, os, _READONLY_PATTERN, and _WRITE_PATTERN are imported inline in multiple test methods. The coding guidelines require imports at the top of the file. The _READONLY_PATTERN/_WRITE_PATTERN imports in particular are duplicated four times.

As per coding guidelines: "ALWAYS place Python imports at the top of the file, not inside functions or methods."

Proposed fix (top-of-file imports)
 import pytest
+import datetime
+import os
+import tempfile
+from decimal import Decimal
+
+import sqlalchemy
 
 from holmes.plugins.toolsets.database.database import (
     DatabaseConfig,
     DatabaseToolset,
+    _READONLY_PATTERN,
+    _WRITE_PATTERN,
     _normalise_url,
     _serialize_value,
 )

Also applies to: 139-139, 151-152, 205-208, 214-217, 223-226, 232-235


253-275: Accessing tools by index is fragile.

toolset.tools[0], toolset.tools[1], toolset.tools[2] will break silently if the tool registration order changes. Consider looking up tools by name instead.

Suggested approach
     def test_query_one_liner(self):
         toolset = DatabaseToolset()
-        query_tool = toolset.tools[0]  # DatabaseQuery
+        query_tool = next(t for t in toolset.tools if t.name == "database_query")
         result = query_tool.get_parameterized_one_liner(
             {"sql": "SELECT * FROM users WHERE active = true LIMIT 10"}
         )
holmes/plugins/toolsets/database/database.py (3)

165-187: Engine created and disposed on every query invocation.

sqlalchemy.create_engine() is designed to be a long-lived, pooled resource. Creating and disposing it on every call to execute_query (and similarly in DatabaseListTables._invoke and DatabaseDescribeTable._invoke) defeats connection pooling and adds unnecessary overhead per query.

Consider creating the engine once (e.g., in _perform_health_check or lazily on first use) and reusing it across invocations, disposing only on toolset teardown.

Sketch: lazy engine with reuse
 class DatabaseToolset(Toolset):
+    _engine: Optional[Any] = None
+
+    def _get_engine(self):
+        import sqlalchemy
+        if self._engine is None:
+            url = _normalise_url(self.database_config.connection_url)
+            self._engine = sqlalchemy.create_engine(url, pool_pre_ping=True)
+        return self._engine
+
     def execute_query(self, sql: str, limit: Optional[int] = None) -> Dict[str, Any]:
         import sqlalchemy
         # ... validation ...
-        url = _normalise_url(self.database_config.connection_url)
-        engine = sqlalchemy.create_engine(url)
-        try:
-            with engine.connect() as conn:
-                # ...
-        finally:
-            engine.dispose()
+        engine = self._get_engine()
+        with engine.connect() as conn:
+            # ...

53-66: URL normalization edge case: schemes with + that partially match a prefix.

The iteration scheme.startswith(prefix + "+") means scheme "mssql+pyodbc" matches prefix "mssql" and gets rewritten to "mssql+pymssql", which may not be desired if a user intentionally chose pyodbc. This is generally the intended behavior per the PR description (preferring pure-Python drivers), but it silently overrides an explicit user choice. Consider logging a warning when overriding a user-specified driver.

Suggested: log when overriding an explicit driver
     for prefix, replacement in _DRIVER_MAP.items():
         if scheme == prefix or scheme.startswith(prefix + "+"):
-            # Only replace if the user hasn't already picked the right driver
             if scheme != replacement:
+                logger.info(
+                    "Rewriting database URL scheme from %s to %s for pure-Python driver compatibility",
+                    scheme, replacement,
+                )
                 return raw_url.replace(scheme, replacement, 1)
             return raw_url

190-201: _serialize_value: dict and list values may contain non-JSON-safe nested values.

If a database returns a JSON/JSONB column, it could contain nested datetime, Decimal, or bytes values inside dicts/lists. The current passthrough on Lines 198-199 skips recursive serialization. Consider whether nested serialization is needed, or if this is acceptable for the expected data shapes.

Comment thread holmes/plugins/toolsets/database/database.py
Comment thread holmes/plugins/toolsets/database/database.py Outdated
Comment thread tests/llm/fixtures/test_ask_holmes/93_events_since_specific_date/test_case.yaml Outdated
Comment thread tests/test_database_toolset.py
Comment thread tests/test_database_toolset.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: 1

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/database/database.py`:
- Around line 327-359: The DatabaseListTables._invoke and
DatabaseDescribeTable._invoke methods call sqlalchemy.create_engine /
sqlalchemy.inspect directly without checking whether the optional sqlalchemy
import succeeded; add the same guard used in execute_query (check if sqlalchemy
is None) at the start of each _invoke and raise a clear RuntimeError like the
one in execute_query explaining that SQLAlchemy is required (include context
such as the tool name or params), so users get a descriptive error instead of an
AttributeError.
🧹 Nitpick comments (6)
holmes/plugins/toolsets/database/database.py (4)

42-47: _WRITE_ANYWHERE_PATTERN will false-positive on SQL keywords inside string literals.

A legitimate query like SELECT * FROM events WHERE action = 'DELETE' will be rejected because \bDELETE\b matches inside the string literal. This also applies to UPDATE, DROP, etc. in column values.

This is hard to solve perfectly without a SQL parser, but worth documenting as a known limitation or considering a simple heuristic (e.g., strip quoted strings before matching).


54-62: "mysql+mysqldb" entry in _DRIVER_MAP is unreachable dead code.

The "mysql" entry matches first due to scheme.startswith("mysql+"), so "mysql+mysqldb" is never reached in the loop. The test test_mysql_mysqldb_rewritten passes, but via the "mysql" key. The entry is harmless but misleading — consider removing it or adding a comment.


185-212: Consider reusing the SQLAlchemy engine instead of creating one per query.

create_engine is called on every execute_query, DatabaseListTables._invoke, and DatabaseDescribeTable._invoke call. SQLAlchemy engines are designed to be long-lived and manage their own connection pools. Creating and disposing an engine per call adds overhead (DNS resolution, connection setup, SSL handshake).

Consider creating the engine once (e.g., lazily on first use) and storing it on the toolset, then disposing in a teardown/cleanup method.


215-226: _serialize_value doesn't recursively serialize nested structures.

dict and list values are passed through as-is (Line 223-224), but they may contain non-JSON-serializable nested values (e.g., Decimal, datetime). This is a minor edge case since most DB drivers return simple types in JSON/JSONB columns, but PostgreSQL JSONB columns can contain Decimal values in some drivers.

tests/test_database_toolset.py (2)

253-275: Tool access by index is fragile.

toolset.tools[0], tools[1], tools[2] will silently test the wrong tool if the order changes in DatabaseToolset.__init__. Consider looking up by name:

Proposed approach
-        query_tool = toolset.tools[0]  # DatabaseQuery
+        query_tool = next(t for t in toolset.tools if t.name == "database_query")

Same for the other two tests.


99-108: Minor: datetime and Decimal imports are inside test methods.

Per coding guidelines, Python imports should be at the top of the file. These could be moved to the top alongside the other imports, though this is a common test pattern.

As per coding guidelines: "ALWAYS place Python imports at the top of the file, not inside functions or methods."

Comment thread holmes/plugins/toolsets/database/database.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 `@tests/test_database_toolset.py`:
- Around line 100-109: Tests test_datetime_to_str and test_decimal_to_str
currently import datetime and Decimal inside the test bodies; move "from
datetime import datetime" and "from decimal import Decimal" to the module-level
imports at the top of the file and remove the inline imports from the functions
so the tests (and references to _serialize_value) use the top-level imports
instead.
🧹 Nitpick comments (3)
tests/test_database_toolset.py (3)

11-11: Remove unnecessary noqa directive.

The # noqa: E402 comment is flagged as unused by Ruff because the E402 rule (module level import not at top of file) is not enabled in your configuration. While this pattern is correct for imports after importorskip, the directive can be safely removed.

♻️ Proposed fix
-from holmes.plugins.toolsets.database.database import (  # noqa: E402
+from holmes.plugins.toolsets.database.database import (
     DatabaseConfig,
     DatabaseToolset,

139-252: Add test coverage for database error handling with detailed messages.

The tests comprehensively verify write-operation blocking, but there's no test for how the toolset handles database errors from valid read queries (e.g., syntax errors, missing tables, invalid column names). Based on learnings, all toolsets must return detailed error messages from underlying APIs, including the exact query executed and full error response to enable LLM self-correction.

Consider adding a test case like:

def test_database_error_returns_detailed_message(self):
    """Verify that database errors include the query and full error details."""
    with pytest.raises(Exception) as exc_info:
        self.toolset.execute_query("SELECT nonexistent_column FROM nonexistent_table")
    
    error_msg = str(exc_info.value)
    # Verify error includes the actual query
    assert "SELECT nonexistent_column FROM nonexistent_table" in error_msg
    # Verify error includes database-specific details
    assert any(keyword in error_msg.lower() for keyword in ["table", "column", "not found", "does not exist"])

Based on learnings: All toolsets must return detailed error messages from underlying APIs to enable LLM self-correction, including exact query/command executed and full API error response.


254-276: Consider accessing tools by name instead of index.

The tests access tools by numeric index (tools[0], tools[1], tools[2]), which is brittle if tool order changes. Consider finding tools by name for more robust tests.

♻️ Suggested improvement
     def test_query_one_liner(self):
         toolset = DatabaseToolset()
-        query_tool = toolset.tools[0]  # DatabaseQuery
+        query_tool = next(t for t in toolset.tools if t.name == "database_query")
         result = query_tool.get_parameterized_one_liner(
             {"sql": "SELECT * FROM users WHERE active = true LIMIT 10"}
         )
         assert "Database" in result
         assert "SELECT" in result

     def test_list_tables_one_liner(self):
         toolset = DatabaseToolset()
-        list_tool = toolset.tools[1]  # DatabaseListTables
+        list_tool = next(t for t in toolset.tools if t.name == "database_list_tables")
         result = list_tool.get_parameterized_one_liner({"schema": "public"})
         assert "Database" in result
         assert "public" in result

     def test_describe_one_liner(self):
         toolset = DatabaseToolset()
-        desc_tool = toolset.tools[2]  # DatabaseDescribeTable
+        desc_tool = next(t for t in toolset.tools if t.name == "database_describe_table")
         result = desc_tool.get_parameterized_one_liner({"table_name": "orders"})
         assert "Database" in result
         assert "orders" in result

Comment thread tests/test_database_toolset.py
@arikalon1
arikalon1 force-pushed the claude/database-toolset-exploration-gOVFe branch from d42e785 to be52c55 Compare February 15, 2026 16:15

@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/database/database.py`:
- Around line 42-47: The current _WRITE_ANYWHERE_PATTERN can match write
keywords inside SQL string literals (e.g., "WHERE action = 'DELETE'"); update
the code by (1) adding a concise comment above _WRITE_ANYWHERE_PATTERN
documenting this known limitation and advising that a real SQL parser is the
robust fix, and (2) implement a lightweight preprocessing step (e.g., a helper
like strip_sql_string_literals or sanitize_query_literals) and use it before
applying _WRITE_ANYWHERE_PATTERN so quoted literals are removed/replaced first
to avoid false positives; reference the _WRITE_ANYWHERE_PATTERN symbol and any
query-checking function that uses it so you replace the raw regex check with the
sanitized-query check.
🧹 Nitpick comments (6)
tests/llm/fixtures/test_ask_holmes/217_database_sql_query/test_case.yaml (1)

11-15: User prompt directly names the target table — consider testing discovery more.

The prompt explicitly tells the LLM to "look at the holmes_eval_217 table," which bypasses schema discovery. Per coding guidelines, eval tests should "test discovery, not recognition." Consider a prompt like: "Find the verification code stored in the database" and assert that Holmes discovers the table via list_tables + describe_table on its own. That said, this is acceptable for an initial test since the primary goal is validating the database toolset end-to-end.

tests/test_database_toolset.py (2)

11-11: Remove unused noqa directive.

Ruff flags # noqa: E402 as unnecessary here. Since pytest.importorskip on line 9 is not itself a regular import statement, the E402 rule doesn't apply to the imports on line 11.

-from holmes.plugins.toolsets.database.database import (  # noqa: E402
+from holmes.plugins.toolsets.database.database import (

254-276: Tool access by positional index is fragile.

toolset.tools[0], [1], [2] will silently break if tool order changes in DatabaseToolset.__init__. Consider looking up tools by name instead.

Suggested approach
-    def test_query_one_liner(self):
-        toolset = DatabaseToolset()
-        query_tool = toolset.tools[0]  # DatabaseQuery
+    def test_query_one_liner(self):
+        toolset = DatabaseToolset()
+        query_tool = next(t for t in toolset.tools if t.name == "database_query")

Apply the same pattern for the other two tests.

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

65-78: Dead entry in _DRIVER_MAP: "mysql+mysqldb" is never reached.

When _normalise_url receives "mysql+mysqldb://...", the "mysql" entry matches first via scheme.startswith("mysql+"), producing the correct result. The explicit "mysql+mysqldb" entry at line 58 is therefore unreachable. This isn't a bug (behavior is correct), but it's misleading — the entry suggests it's doing something when it isn't.

Consider either removing the dead entry or reordering to check more-specific prefixes first (longer keys before shorter ones).


185-212: A new SQLAlchemy engine is created on every execute_query call.

Each invocation of execute_query calls create_engine() and dispose(), which means a full connection setup/teardown per query with no connection pooling benefit. For a toolset that an LLM might call multiple times in a single investigation, this adds unnecessary latency.

Consider creating and caching the engine once (e.g., during health check or on first use) and reusing it across calls, disposing only on toolset teardown.


191-194: Consider logging the suppressed exception for debuggability.

The bare except Exception: pass when setting READ ONLY transaction mode silently swallows errors. A debug-level log would help troubleshoot connection issues without noise in production.

Suggested fix
                 try:
                     conn.execute(sqlalchemy.text("SET TRANSACTION READ ONLY"))
                 except Exception:
-                    pass  # Not all dialects support this; regex check is primary guard
+                    logger.debug("SET TRANSACTION READ ONLY not supported by this dialect, skipping")

Comment thread holmes/plugins/toolsets/database/database.py
Adds a new Python toolset that provides read-only SQL access via SQLAlchemy.
Supports PostgreSQL, MySQL/MariaDB, SQLite, and SQL Server using pure-Python
drivers (pg8000, PyMySQL, pymssql). Write operations are blocked by regex
validation. Includes 3 tools: database_query, database_list_tables, and
database_describe_table.

- 40 unit tests covering URL normalisation, read-only validation, serialization
- Eval test (217) using Supabase PostgreSQL with verification code pattern
- Toolset config uses DATABASE_URL env var

https://claude.ai/code/session_01H8VBEN3oLGYE7CRvSi2zvC
Signed-off-by: Claude <noreply@anthropic.com>
Use a database-specific env var name to allow separate evals for
different database backends in the future.

https://claude.ai/code/session_01H8VBEN3oLGYE7CRvSi2zvC
Signed-off-by: Claude <noreply@anthropic.com>
- Block writable CTEs (WITH ... DELETE/INSERT/UPDATE) via _WRITE_ANYWHERE_PATTERN
- Add defense-in-depth with SET TRANSACTION READ ONLY at DB level
- Move sqlalchemy import to module level with try/except for optional dep
- Include schema parameter in DatabaseListTables error messages
- Use ValidationError instead of bare Exception in test
- Replace deprecated tempfile.mktemp with NamedTemporaryFile
- Fix typo in test_case.yaml: "to to" → "due to"
- Add 4 new tests for writable CTE detection

https://claude.ai/code/session_01MDfhnnq4yosJYeU5hdFztJ
Signed-off-by: Claude <noreply@anthropic.com>
Use pytest.importorskip("sqlalchemy") to gracefully skip the entire
test module in CI where sqlalchemy is an optional dependency.

https://claude.ai/code/session_01MDfhnnq4yosJYeU5hdFztJ
Signed-off-by: Claude <noreply@anthropic.com>
@arikalon1
arikalon1 force-pushed the claude/database-toolset-exploration-gOVFe branch from 3b64206 to 559e761 Compare February 15, 2026 16:30
@arikalon1

Copy link
Copy Markdown
Collaborator Author

/eval
markers: database

Adds pytestmark = pytest.mark.database so all 44 tests can be
run with: pytest -m database

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

Copy link
Copy Markdown
Contributor

@arikalon1 Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/database-toolset-exploration-gOVFe
Model opus-4.5
Markers database
Iterations 1
Duration 2m 23s
Workflow View logs | Rerun

Results of HolmesGPT evals

  • ask_holmes: 1/5 test cases were successful, 0 regressions, 3 skipped, 1 setup failures
Status Test case Time Turns Tools Cost
✅ 104a_postgres_root_issue 40.0s 5 13 $0.2735
➖ 104b_postgres_missing_index_pgstat — — — —
➖ 104c_postgres_minimal_missing_index — — — —
➖ 105_redis_wrong_data_structure — — — —
🚧 217_database_sql_query — — — —
Total 40.0s avg 5.0 avg 13.0 avg $0.2735
📖 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/database-toolset-exploration-gOVFe -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, fast, frontend, grafana-dashboard, hard, integration, kafka, kubernetes, leaked-information, logs, loki, medium, metrics, network, newrelic, no-cicd, numerical, one-test, port-forward, prometheus, question-answer, regression, runbooks, slackbot, storage, toolset-limitation, traces, transparency


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/database-toolset-exploration-gOVFe -f markers=regression -f filter=

Replace database/question-answer/fast/easy tags with single
db-connectors tag on eval 217 and unit tests.

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

Copy link
Copy Markdown
Collaborator Author

/eval
markers: db-connectors

@github-actions

Copy link
Copy Markdown
Contributor

@arikalon1 Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/database-toolset-exploration-gOVFe
Model opus-4.5
Markers db-connectors
Iterations 1
Duration 44s
Workflow View logs | Rerun

Results of HolmesGPT evals

  • ask_holmes: 0/1 test cases were successful, 0 regressions, 1 setup failures
Status Test case Time Turns Tools Cost
🚧 217_database_sql_query — — — —
Total — avg — avg — avg —
📖 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/database-toolset-exploration-gOVFe -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, db-connectors, easy, elasticsearch, embeds, fast, frontend, grafana-dashboard, hard, integration, kafka, kubernetes, leaked-information, logs, loki, medium, metrics, network, newrelic, no-cicd, numerical, one-test, port-forward, prometheus, question-answer, regression, runbooks, slackbot, storage, toolset-limitation, traces, transparency


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/database-toolset-exploration-gOVFe -f markers=regression -f filter=

The secret was added to GitHub but not passed to the test runner
environment, causing the database toolset eval to fail.

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

Copy link
Copy Markdown
Collaborator Author

/eval
markers: db-connectors

@github-actions

Copy link
Copy Markdown
Contributor

@arikalon1 Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/database-toolset-exploration-gOVFe
Model opus-4.5
Markers db-connectors
Iterations 1
Duration 45s
Workflow View logs | Rerun

Results of HolmesGPT evals

  • ask_holmes: 0/1 test cases were successful, 0 regressions, 1 setup failures
Status Test case Time Turns Tools Cost
🚧 217_database_sql_query — — — —
Total — avg — avg — avg —
📖 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/database-toolset-exploration-gOVFe -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, db-connectors, easy, elasticsearch, embeds, fast, frontend, grafana-dashboard, hard, integration, kafka, kubernetes, leaked-information, logs, loki, medium, metrics, network, newrelic, no-cicd, numerical, one-test, port-forward, prometheus, question-answer, regression, runbooks, slackbot, storage, toolset-limitation, traces, transparency


Commands: /eval · /rerun · /list

CLI: gh workflow run eval-regression.yaml --repo HolmesGPT/holmesgpt --ref claude/database-toolset-exploration-gOVFe -f markers=regression -f filter=

Signed-off-by: Arik Alon <alon.arik@gmail.com>
Signed-off-by: Arik Alon <alon.arik@gmail.com>
Comment thread holmes/plugins/toolsets/database/database.py Outdated
Comment thread holmes/plugins/toolsets/database/database.py
Comment thread holmes/plugins/toolsets/database/database.py
Comment thread holmes/plugins/toolsets/database/database.py
Comment thread holmes/plugins/toolsets/database/database.py Outdated
Comment thread holmes/plugins/toolsets/database/database.py Outdated
Comment thread holmes/plugins/toolsets/database/database.py Outdated
Comment thread holmes/plugins/toolsets/database/database.py

@Avi-Robusta Avi-Robusta left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey,
some small changes

Signed-off-by: Arik Alon <alon.arik@gmail.com>
Signed-off-by: Arik Alon <alon.arik@gmail.com>
Signed-off-by: Arik Alon <alon.arik@gmail.com>
Signed-off-by: Arik Alon <alon.arik@gmail.com>
Avi-Robusta
Avi-Robusta previously approved these changes Feb 23, 2026
Avi-Robusta
Avi-Robusta previously approved these changes Feb 23, 2026
Signed-off-by: Arik Alon <alon.arik@gmail.com>
@arikalon1
arikalon1 merged commit 30ee64e into master Feb 23, 2026
21 of 22 checks passed
@arikalon1
arikalon1 deleted the claude/database-toolset-exploration-gOVFe branch February 23, 2026 10:23
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.

4 participants