Skip to content

Confluence improvements - #1381

Open
aantn wants to merge 22 commits into
masterfrom
claude/confluence-page-eval-By6aw
Open

aantn wants to merge 22 commits into
masterfrom
claude/confluence-page-eval-By6aw

Conversation

@aantn

@aantn aantn commented Jan 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Confluence integration: list spaces, list pages, and fetch page content with cloud/on‑prem support, multiple auth modes, health checks, and structured results.
  • Documentation

    • Clarified that expected_output is evaluator-visible with usage notes and YAML examples, including before_test/verification example.
  • Tests

    • End-to-end Confluence page-fetch test (setup/teardown) and new pytest marker/toolset configuration.
  • Chores

    • Allow passthrough of Atlassian Cloud endpoints; added OpenRouter API key handling.

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

- Add new eval test 195_confluence_page_fetch that:
  - Creates a Confluence page with unique verification code in before_test
  - Tests that LLM can read the page ID file and fetch page contents
  - Deletes the test page in after_test
- Add Atlassian Cloud passthrough to conftest.py for HTTP mocking
- Add confluence marker to pyproject.toml

Required environment variables:
- CONFLUENCE_BASE_URL
- CONFLUENCE_USER
- CONFLUENCE_API_KEY
- CONFLUENCE_SPACE_KEY

Signed-off-by: Claude <noreply@anthropic.com>
Confluence toolset changes:
- Add list_confluence_spaces tool to discover available spaces
- Add get_confluence_space_pages tool to list pages in a space
- Switch from Basic Auth to Bearer token authentication
- Remove CONFLUENCE_USER requirement (only API key needed now)
- Add configurable SSL verification via verify_ssl config option
- Add explicit parameters with descriptions for all tools

Eval test improvements:
- Simplify test to use expected_output for secret verification
- The LLM discovers the page using the new space/pages tools
- Verification code in expected_output is invisible to LLM

Documentation:
- Add CLAUDE.md section explaining expected_output invisibility pattern
- Document how to use verification codes for hallucination-proof tests

Signed-off-by: Claude <noreply@anthropic.com>
Set default CONFLUENCE_BASE_URL and CONFLUENCE_SPACE_KEY via test_env_vars
so they're available to both before_test scripts and the toolset:
- CONFLUENCE_BASE_URL: https://robusta-llm-test.atlassian.net
- CONFLUENCE_SPACE_KEY: MFS

Only CONFLUENCE_API_KEY is required from the user (no default for secrets).

Signed-off-by: Claude <noreply@anthropic.com>
Use bash defaults for CONFLUENCE_BASE_URL and CONFLUENCE_SPACE_KEY in
before_test/after_test scripts. Note: The toolset still requires these
env vars to be set since it uses shell variable expansion in curl commands.

Required env vars:
- CONFLUENCE_API_KEY (required, no default)
- CONFLUENCE_BASE_URL (required for toolset, defaults in scripts)

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
Replace YAML/curl-based toolset with Python implementation:
- ConfluenceConfig with url, api_key, verify_ssl settings
- Bearer token authentication
- Health check on prerequisite validation
- Three tools:
  - list_confluence_spaces: List all spaces
  - get_confluence_space_pages: Get pages in a space (with title filter)
  - fetch_confluence_page: Fetch page by ID

Update eval toolsets.yaml to use config with env var references.

Signed-off-by: Claude <noreply@anthropic.com>
The Python toolset now supports two authentication methods:

1. Basic Auth (for Atlassian Cloud):
   - Set username + api_key
   - Uses -u username:api_key format

2. Bearer Token (for self-hosted/PAT):
   - Set only api_key (no username)
   - Uses Authorization: Bearer header

Update eval to use Basic Auth with CONFLUENCE_USERNAME env var.

Signed-off-by: Claude <noreply@anthropic.com>
- Import and register ConfluenceToolset in load_python_toolsets
- Update 195_confluence_page_fetch eval to disable K8s toolsets
- Test passes successfully with verification code discovery

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

netlify Bot commented Jan 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 67c52ba
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6970e53f496d7900085f9af6
😎 Deploy Preview https://deploy-preview-1381--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.

@linux-foundation-easycla

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

Copy link
Copy Markdown

CLA Not Signed

@github-actions

github-actions Bot commented Jan 19, 2026 •

Copy link
Copy Markdown
Contributor

📂 Previous Runs

📜 Run @ 974da40 (#21149948617)

✅ Results of HolmesGPT evals

Automatically triggered by commit 974da40 on branch claude/confluence-page-eval-By6aw

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 34.4s ±0% 6 14 $0.1721
✅ 101_loki_historical_logs_pod_deleted 55.2s ±0% 8 17 $0.2278
✅ 111_pod_names_contain_service 34.5s ↓20% 6 14 $0.1659
✅ 12_job_crashing 49.7s ±0% 9 18 $0.2137
✅ 162_get_runbooks 40.5s ↓22% 6 16 $0.1937
✅ 176_network_policy_blocking_traffic_no_runbooks 40.5s ±0% 7 15 $0.1892
✅ 24_misconfigured_pvc 42.4s ±0% 8 19 $0.1972
✅ 43_current_datetime_from_prompt 3.3s ±0% 1 — $0.0619
✅ 61_exact_match_counting 15.2s ↑13% 4 5 $0.1051
Total 35.1s avg 6.1 avg 14.8 avg $1.5266

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/confluence-page-eval-By6aw'

Status: Success - 11 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 @ fb675df (#21149257179)

✅ Results of HolmesGPT evals

Automatically triggered by commit fb675df on branch claude/confluence-page-eval-By6aw

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 38.4s ±0% 7 14 $0.1806
✅ 101_loki_historical_logs_pod_deleted 50.7s ↓12% 8 15 $0.2025
✅ 111_pod_names_contain_service 34.8s ↓19% 6 15 $0.1807
✅ 12_job_crashing 47.8s ±0% 8 18 $0.2135
✅ 162_get_runbooks 49.3s ±0% 8 17 $0.2315
✅ 176_network_policy_blocking_traffic_no_runbooks 36.8s ↓13% 6 14 $0.1768
✅ 24_misconfigured_pvc 40.9s ±0% 8 18 $0.1918
✅ 43_current_datetime_from_prompt 3.5s ±0% 1 — $0.0619
✅ 61_exact_match_counting 11.3s ↓16% 3 3 $0.0858
Total 34.8s avg 6.1 avg 14.2 avg $1.5249

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/confluence-page-eval-By6aw'

Status: Success - 11 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 @ a970171 (#21149130639)

✅ Results of HolmesGPT evals

Automatically triggered by commit a970171 on branch claude/confluence-page-eval-By6aw

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 36.2s ±0% 7 14 $0.1800
✅ 101_loki_historical_logs_pod_deleted 42.8s ↓24% 6 13 $0.1798
✅ 111_pod_names_contain_service 46.8s ±0% 9 20 $0.2323
✅ 12_job_crashing 42.3s ↓18% 7 17 $0.1971
✅ 162_get_runbooks 48.8s ±0% 7 20 $0.2321
✅ 176_network_policy_blocking_traffic_no_runbooks 42.5s ±0% 8 16 $0.2069
✅ 24_misconfigured_pvc 35.6s ±0% 7 16 $0.1757
✅ 43_current_datetime_from_prompt 3.4s ±0% 1 — $0.0619
✅ 61_exact_match_counting 10.4s ↓21% 3 3 $0.0858
Total 34.3s avg 6.1 avg 14.9 avg $1.5517

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/confluence-page-eval-By6aw'

Status: Success - 10 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 @ 2e3285f (#21148827383)

✅ Results of HolmesGPT evals

Automatically triggered by commit 2e3285f on branch claude/confluence-page-eval-By6aw

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 36.6s ±0% 7 14 $0.1826
✅ 101_loki_historical_logs_pod_deleted 61.6s ↑10% 9 22 $0.2520
✅ 111_pod_names_contain_service 45.1s ±0% 8 18 $0.2107
✅ 12_job_crashing 41.7s ↓20% 8 16 $0.1987
✅ 162_get_runbooks 48.4s ±0% 8 17 $0.2258
✅ 176_network_policy_blocking_traffic_no_runbooks 45.5s ±0% 8 17 $0.2049
✅ 24_misconfigured_pvc 39.7s ±0% 8 18 $0.1890
✅ 43_current_datetime_from_prompt 3.3s ±0% 1 — $0.0619
✅ 61_exact_match_counting 10.4s ↓22% 3 3 $0.0858
Total 36.9s avg 6.7 avg 15.6 avg $1.6114

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/confluence-page-eval-By6aw'

Status: Success - 10 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 @ 2e9377c (#21148560035)

✅ Results of HolmesGPT evals

Automatically triggered by commit 2e9377c on branch claude/confluence-page-eval-By6aw

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.1s ±0% 6 14 $0.1720
✅ 101_loki_historical_logs_pod_deleted 40.8s ↓29% 6 12 $0.1796
✅ 111_pod_names_contain_service 42.1s ±0% 8 17 $0.2032
✅ 12_job_crashing 52.8s ±0% 9 22 $0.2503
✅ 162_get_runbooks 48.2s ±0% 8 16 $0.2312
✅ 176_network_policy_blocking_traffic_no_runbooks 42.8s ±0% 8 16 $0.1998
✅ 24_misconfigured_pvc 36.7s ±0% 7 19 $0.1809
✅ 43_current_datetime_from_prompt 3.1s ↓12% 1 — $0.0619
✅ 61_exact_match_counting 10.2s ↓23% 3 3 $0.0858
Total 34.4s avg 6.2 avg 14.9 avg $1.5647

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/confluence-page-eval-By6aw'

Status: Success - 10 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)

✅ Results of HolmesGPT evals

Automatically triggered by commit 67c52ba on branch claude/confluence-page-eval-By6aw

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.7s ↑11% 7 15 $0.1878
✅ 101_loki_historical_logs_pod_deleted 47.0s ↓21% 7 14 $0.2002
✅ 111_pod_names_contain_service 46.5s ↑17% 8 17 $0.1960
✅ 12_job_crashing 48.1s ±0% 9 18 $0.2114
✅ 162_get_runbooks 53.2s ↑12% 8 18 $0.2389
✅ 176_network_policy_blocking_traffic_no_runbooks 43.6s ↑18% 7 16 $0.1863
✅ 24_misconfigured_pvc 36.7s ±0% 7 16 $0.1721
✅ 43_current_datetime_from_prompt 3.6s ±0% 1 — $0.0619
✅ 61_exact_match_counting 10.8s ±0% 3 3 $0.0858
Total 36.4s avg 6.3 avg 14.6 avg $1.5403

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/confluence-page-eval-By6aw'

Status: Success - 28 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/confluence-page-eval-By6aw -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, 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/confluence-page-eval-By6aw -f markers=regression -f filter=

@github-actions

github-actions Bot commented Jan 19, 2026 •

Copy link
Copy Markdown
Contributor

✅ Docker image ready for b1a2c7c (built in 5m 11s)

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

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

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

@coderabbitai

coderabbitai Bot commented Jan 19, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@aantn has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds a new Confluence Python toolset (client, auth, health checks, three tools), registers and exports it, removes the YAML toolset config, adds E2E Confluence test fixtures and passthroughs, extends OpenRouter support in classifiers, and updates CLAUDE.md and pytest markers.

Changes

Cohort / File(s) Change Summary
Documentation & Test Marker
CLAUDE.md, pyproject.toml
Documented expected_output visibility and example; added confluence pytest marker.
Toolset Implementation
holmes/plugins/toolsets/confluence/confluence.py
New Confluence client and toolset: ConfluenceConfig, ConfluenceToolset (auth, headers, base URL, cloud_id discovery, health check, _make_request), BaseConfluenceTool, and tools ListConfluenceSpaces, GetConfluenceSpacePages, FetchConfluencePage.
Toolset Registration & Package Export
holmes/plugins/toolsets/__init__.py, holmes/plugins/toolsets/confluence/__init__.py, holmes/plugins/toolsets/confluence.yaml
Imported and instantiated ConfluenceToolset in Python toolset registry; exported ConfluenceToolset from package; removed YAML-based Confluence toolset config.
Tests: Fixtures & Network
conftest.py, tests/llm/fixtures/test_ask_holmes/195_confluence_page_fetch/*, tests/llm/fixtures/test_ask_holmes/195_confluence_page_fetch/toolsets.yaml
Added passthrough rules for Atlassian endpoints; new E2E fixture test_case.yaml that creates a verification Confluence page in before_test and deletes it in after_test; added Confluence-specific toolsets.yaml.
LLM Classifier Runtime
tests/llm/utils/classifiers.py
Added OPENROUTER_API_KEY/OPENROUTER_API_BASE support, model prefix stripping for openrouter/, and API-key presence validation.
Minor Formatting/Logging
holmes/core/tool_calling_llm.py, holmes/core/tools.py, server.py
Small docstring/formatting/logging reflow edits without behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    participant LLM as LLM / Holmes
    participant Tool as Confluence Tool
    participant Toolset as ConfluenceToolset
    participant API as Confluence REST API

    LLM->>Tool: _invoke(params, context)
    Tool->>Toolset: _make_request(method, endpoint, params)
    Toolset->>Toolset: _get_headers() / _get_auth()
    Toolset->>API: HTTP request (auth, timeout, verify_ssl)
    alt 200 OK
        API-->>Toolset: JSON response
        Toolset-->>Tool: StructuredToolResult(success, data)
        Tool-->>LLM: StructuredToolResult(success)
    else HTTP/auth/timeout/error
        API-->>Toolset: error / no response
        Toolset-->>Tool: StructuredToolResult(error with context)
        Tool-->>LLM: StructuredToolResult(error)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • moshemorad
  • arikalon1
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Confluence improvements' is vague and generic, using non-descriptive language that doesn't convey specific information about what was actually changed. Consider a more specific title such as 'Add Confluence toolset with page fetching and listing capabilities' or 'Implement Python-based Confluence integration with cloud and self-hosted support' to better reflect the substantial implementation work.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

@aantn

aantn commented Jan 19, 2026

Copy link
Copy Markdown
Collaborator Author

/eval
markers: confluence

@github-actions

Copy link
Copy Markdown
Contributor

@aantn Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/confluence-page-eval-By6aw
Model bedrock/eu.anthropic.claude-sonnet-4-5-20250929-v1:0
Markers confluence
Iterations 1
Duration 1m 0s
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
🚧 195_confluence_page_fetch — — — —
Total — avg — avg — avg —

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

Historical Comparison Details

Filter: excluding branch 'master'

Status: Success - 10 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/confluence-page-eval-By6aw -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, 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/confluence-page-eval-By6aw -f markers=regression -f filter=

@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/confluence/confluence.py`:
- Around line 267-279: The linter flags unused arguments (ARG002); fix by
renaming unused parameters with a leading underscore: in the Confluence plugin
class rename the unused "context" parameter in _invoke to "_context" and rename
any other unused "params" or "context" parameters (for example in
get_parameterized_one_liner and the other similar methods flagged) to
"_params"/"_context" respectively, or alternatively add a per-argument noqa for
ARG002 if you prefer; ensure call sites are unaffected (only rename parameters
in the function signatures where the argument is not used).
- Around line 68-95: The catch-all Exception handlers in prerequisites_callable
and _perform_health_check should be narrowed and their error formatting updated
to satisfy Ruff: replace the broad except Exception in prerequisites_callable
with the specific validation errors thrown by ConfluenceConfig (e.g.,
pydantic.error_wrappers.ValidationError and TypeError) and update its message to
use an explicit f-string conversion like {e!r}; similarly, in
_perform_health_check replace the final except Exception with
requests.RequestException (or another specific requests exception class) and
change f"Confluence health check failed: {str(e)}" to f"Confluence health check
failed: {e!r}" so you avoid broad Exception catches and eliminate str(e) in
f-strings while keeping the existing logic in prerequisites_callable and
_perform_health_check.
- Around line 267-331: The list endpoints should return a NO_DATA
StructuredToolResult with the search context when they yield no items: update
the _invoke implementations (the top-level list spaces _invoke and
GetConfluenceSpacePages._invoke) to call the existing request, inspect the
response for an empty result set (e.g., no "results" or empty list), and if
empty return a StructuredToolResult with status NO_DATA and a message describing
the endpoint and the applied filters/query_params (include endpoint string and
relevant params such as "limit", "start", "type" or "space_key"/"title");
otherwise return the normal successful result from _make_request. Ensure the
message explicitly names the endpoint and the filter values so callers know what
was searched.
- Around line 180-234: In _make_request, update the error branches that build
StructuredToolResult (especially the requests.exceptions.HTTPError handler, and
also Timeout/ConnectionError/Exception branches) to include the full request
context (pass both params and query_params and body contents into the
StructuredToolResult.params or a new context field) and include the full API
error payload and HTTP status (use e.response.text and e.response.status_code
without truncation and include any parsed json) in the error string (or
StructuredToolResult.error payload) so the tool returns exact invocation +
query/body + full response text; ensure you reference the existing
StructuredToolResult and StructuredToolResultStatus constructors used in
_make_request to carry the extra fields.
🧹 Nitpick comments (3)
tests/llm/fixtures/test_ask_holmes/195_confluence_page_fetch/test_case.yaml (3)

74-76: Use jq for robust JSON parsing.

The current grep/cut approach for extracting PAGE_ID is fragile and may fail if the JSON response contains multiple "id" fields or has different formatting. Use jq for reliable JSON extraction.

♻️ Suggested fix
-  # Extract the page ID from the response
-  PAGE_ID=$(echo "$RESPONSE" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
+  # Extract the page ID from the response
+  PAGE_ID=$(echo "$RESPONSE" | jq -r '.id')

83-84: Consider using test-specific temp file path for parallel test safety.

Using /tmp/confluence_test_page_id could cause conflicts if multiple Confluence tests run in parallel. Consider using a test-specific path.

♻️ Suggested fix
-  # Save the page ID for cleanup
-  echo "$PAGE_ID" > /tmp/confluence_test_page_id
+  # Save the page ID for cleanup (test-specific path)
+  echo "$PAGE_ID" > /tmp/confluence_test_page_id_195

And update after_test correspondingly:

-  if [ -f /tmp/confluence_test_page_id ]; then
-    PAGE_ID=$(cat /tmp/confluence_test_page_id)
+  if [ -f /tmp/confluence_test_page_id_195 ]; then
+    PAGE_ID=$(cat /tmp/confluence_test_page_id_195)
...
-    rm -f /tmp/confluence_test_page_id
+    rm -f /tmp/confluence_test_page_id_195

1-13: Consider adding runbooks: {} and include_tool_calls: true.

Per the coding guidelines, eval tests should add runbooks: {} for an empty runbook catalog. Additionally, while the verification code is specific, adding include_tool_calls: true provides extra assurance that the Confluence tools were actually called.

♻️ Suggested additions
user_prompt: |
  Find and fetch the contents of the test page titled "HolmesGPT Eval Test Page" in Confluence.
  Tell me the verification code found in the page content.

expected_output:
  - "Must report the verification code: HOLMES-EVAL-7x9k2m4p"

include_tool_calls: true

runbooks: {}

tags:
  - confluence
  - question-answer
  - medium

setup_timeout: 120

Comment thread holmes/plugins/toolsets/confluence/confluence.py
Comment thread holmes/plugins/toolsets/confluence/confluence.py
Comment thread holmes/plugins/toolsets/confluence/confluence.py Outdated
Comment thread holmes/plugins/toolsets/confluence/confluence.py Outdated
LiteLLM handles the openrouter/ prefix automatically, but the classifier
uses the OpenAI client directly. Added prefix stripping and default
base URL so CLASSIFIER_MODEL=openrouter/openai/gpt-4.1 works correctly
with just OPENROUTER_API_KEY set.

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

Caution

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

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

44-61: Misleading error message in else branch.

The else branch at lines 58-61 is reachable when AZURE_API_KEY is set but AZURE_API_BASE is not. In this case, the error message "No API key found" is misleading because an API key is set — the actual problem is the missing AZURE_API_BASE.

Proposed fix
         elif OPENROUTER_API_KEY:
             client_api_key = OPENROUTER_API_KEY
             client_base_url = OPENROUTER_API_BASE or "https://openrouter.ai/api/v1"
         else:
-            raise ValueError(
-                "No API key found (AZURE_API_KEY, OPENAI_API_KEY, or OPENROUTER_API_KEY)"
-            )
+            # This branch is reached when AZURE_API_KEY is set but AZURE_API_BASE is not
+            raise ValueError(
+                "AZURE_API_KEY is set but AZURE_API_BASE is missing. "
+                "Either set AZURE_API_BASE or use OPENAI_API_KEY/OPENROUTER_API_KEY instead."
+            )

@aantn

aantn commented Jan 19, 2026

Copy link
Copy Markdown
Collaborator Author

/eval
markers: confluence

@github-actions

Copy link
Copy Markdown
Contributor

@aantn Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/confluence-page-eval-By6aw
Model bedrock/eu.anthropic.claude-sonnet-4-5-20250929-v1:0
Markers confluence
Iterations 1
Duration 1m 9s
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
🚧 195_confluence_page_fetch — — — —
Total — avg — avg — avg —

Historical comparison unavailable: No experiments found (excluding branch 'master')

Historical Comparison Details

Filter: excluding branch 'master'

Status: No experiments found (excluding branch 'master')

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/confluence-page-eval-By6aw -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, 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/confluence-page-eval-By6aw -f markers=regression -f filter=

- Fix ARG002 linting: rename unused context params to _context
- Add NO_DATA status for empty list results with search context
- Include full request context in error responses (endpoint, query_params,
  body, HTTP status, response text, parsed JSON)
- Better error messages with endpoint and filter details

Signed-off-by: Claude <noreply@anthropic.com>
Keep original parameter names (context, params) for API consistency
and add noqa: ARG002 comments to suppress linting warnings.

Signed-off-by: Claude <noreply@anthropic.com>
- Auto-discover cloud_id using public /_edge/tenant_info endpoint
- Use API gateway (api.atlassian.com) for Atlassian Cloud instances
- Support on-premise Confluence with direct URL fallback
- Update eval test to use API gateway for page creation/deletion
- Add helpful error messages for service account permission issues

The tenant_info endpoint works without authentication, which solves
the chicken-and-egg problem for service accounts that can only access
the API gateway but need cloud_id to construct the gateway URL.

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
Service accounts use Bearer auth (no username), while personal tokens
use Basic Auth (with username). Updated docstring to document both
authentication methods clearly.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/confluence/confluence.py`:
- Around line 98-122: In _discover_cloud_id, replace the broad "except
Exception" with explicit exception handling for requests.RequestException (for
network/HTTP issues) and ValueError (for response.json() parse errors), and
update the logging call to use structured logging (e.g., logging.debug("Could
not auto-discover cloud_id", exc_info=e) or logging.debug("Could not
auto-discover cloud_id: %s", e)) so errors aren't masked and are logged
consistently; keep the rest of the request/response logic and return None on
handled failures.
- Around line 295-303: The try/except around parsing e.response.json() uses a
bare except which hides errors; change it to catch ValueError (or
json.JSONDecodeError) specifically and log the parse failure instead of silently
passing—update the block where error_json is set (variable error_json and the
call e.response.json()) to use except ValueError as parse_err:
logger.debug/exception("Failed to parse error response JSON",
exc_info=parse_err) (or the module's configured logger) so callers still get
detailed error context.
♻️ Duplicate comments (1)
holmes/plugins/toolsets/confluence/confluence.py (1)

270-342: Include full request context in error payloads, not just data.

The error field still omits query/body filters; LLMs may miss search context unless they inspect data. Include the exact invocation + filters in the error payload (or params) for all error branches.

🔧 Proposed fix
         request_context = {
             "endpoint": endpoint,
             "method": method,
             "query_params": query_params or {},
             "body": body,
             "params": params,
         }
+        params_payload = {"tool_params": params, "request_context": request_context}
@@
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=error_msg,
+                error=f"{error_msg} | request_context={request_context}",
                 data=error_detail,
-                params=params,
+                params=params_payload,
             )
@@
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Confluence request timed out for endpoint '{endpoint}' (timeout: {timeout or self._toolset.confluence_config.timeout}s)",
+                error=f"Confluence request timed out for endpoint '{endpoint}' (timeout: {timeout or self._toolset.confluence_config.timeout}s) | request_context={request_context}",
                 data={"request_context": request_context},
-                params=params,
+                params=params_payload,
             )
@@
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Failed to connect to Confluence at {self._toolset.confluence_config.url}: {str(e)}",
+                error=f"Failed to connect to Confluence at {self._toolset.confluence_config.url}: {e!s} | request_context={request_context}",
                 data={"request_context": request_context, "connection_error": str(e)},
-                params=params,
+                params=params_payload,
             )
@@
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Unexpected error querying Confluence endpoint '{endpoint}': {type(e).__name__}: {str(e)}",
+                error=f"Unexpected error querying Confluence endpoint '{endpoint}': {type(e).__name__}: {e!s} | request_context={request_context}",
                 data={
                     "request_context": request_context,
                     "exception_type": type(e).__name__,
                 },
-                params=params,
+                params=params_payload,
             )
As per coding guidelines, error messages should include the exact query/filters and full API error context.

Comment on lines +98 to +122
def _discover_cloud_id(self) -> Optional[str]:
"""Discover cloud_id from Atlassian Cloud using the tenant_info endpoint.

The /_edge/tenant_info endpoint is a public endpoint that returns the cloud_id
without requiring authentication, which works for both personal tokens and
service account scoped tokens.
"""
try:
# Use the public tenant_info endpoint - no auth required
url = f"{self.confluence_config.url.rstrip('/')}/_edge/tenant_info"
response = requests.get(
url,
headers={"Accept": "application/json"},
timeout=self.confluence_config.timeout,
verify=self.confluence_config.verify_ssl,
)
response.raise_for_status()
data = response.json()

cloud_id = data.get("cloudId")
if cloud_id:
return cloud_id
except Exception as e:
logging.debug(f"Could not auto-discover cloud_id: {e}")
return None

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the target file
fd "confluence.py" --type f

Repository: HolmesGPT/holmesgpt

Length of output: 111


🏁 Script executed:

# Check the Ruff configuration
cat pyproject.toml | head -100

Repository: HolmesGPT/holmesgpt

Length of output: 2389


🏁 Script executed:

# Get more details about the confluence.py file and its full context
wc -l holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 115


🏁 Script executed:

# Read the target lines and surrounding context
sed -n '90,130p' holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 1854


🏁 Script executed:

# Check for Ruff configuration in pyproject.toml
grep -A 50 "\[tool.ruff" pyproject.toml

Repository: HolmesGPT/holmesgpt

Length of output: 45


🏁 Script executed:

# Search for other exception handling patterns in the codebase to understand conventions
rg "except.*RequestException" holmes/plugins/toolsets --type py -A 2 | head -50

Repository: HolmesGPT/holmesgpt

Length of output: 3952


🏁 Script executed:

# Check what Ruff BLE001 actually is and verify it's a real rule
python3 << 'EOF'
# BLE001 is from flake8-blind-except, now part of Ruff
# It flags catching bare Exception
print("BLE001: Do not catch blind exception: Exception")
print("This is a real Ruff rule for flagging overly broad exception handling")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 149


🏁 Script executed:

# Examine the full confluence.py to understand error handling patterns
cat holmes/plugins/toolsets/confluence/confluence.py | head -50

Repository: HolmesGPT/holmesgpt

Length of output: 1393


🏁 Script executed:

# Check what json.JSONDecodeError is and whether it's a ValueError
python3 << 'EOF'
import json
import requests

# Check exception hierarchy
print("json.JSONDecodeError bases:", json.JSONDecodeError.__bases__)
print("Is JSONDecodeError a ValueError?", issubclass(json.JSONDecodeError, ValueError))

# Check what requests.get() can raise
print("\nrequests.RequestException is base for:")
print("- ConnectionError")
print("- HTTPError") 
print("- URLRequired")
print("- TooManyRedirects")
print("- ConnectTimeout")
print("- ReadTimeout")
print("- Timeout")

# response.json() raises json.JSONDecodeError which IS a ValueError
print("\nresponse.json() raises: json.JSONDecodeError (subclass of ValueError)")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 149


🏁 Script executed:

# Check the full error handling in this file to see if there are other patterns
rg "except|logging\." holmes/plugins/toolsets/confluence/confluence.py -B 1 -A 3

Repository: HolmesGPT/holmesgpt

Length of output: 2902


🏁 Script executed:

# Check what the toolset guidelines say about error context - search for "detailed error message" pattern
rg "detailed error" holmes/plugins/toolsets -A 3 | head -30

Repository: HolmesGPT/holmesgpt

Length of output: 434


🏁 Script executed:

# Check the imports in confluence.py to verify requests library usage
head -20 holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 505


🏁 Script executed:

# Verify the specific exception types that would be raised
python3 << 'EOF'
import json

# Verify exception hierarchy for response.json()
try:
    # This is what response.json() does internally
    json.loads("invalid json")
except Exception as e:
    print(f"Exception type from json.loads: {type(e).__name__}")
    print(f"Is it a ValueError? {isinstance(e, ValueError)}")
    print(f"MRO: {type(e).__mro__}")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 267


🏁 Script executed:

# Check what exceptions the requests library actually raises
python3 << 'EOF'
from requests import RequestException
from requests.exceptions import HTTPError, ConnectionError, Timeout

print("RequestException hierarchy:")
print(f"HTTPError is subclass of RequestException: {issubclass(HTTPError, RequestException)}")
print(f"ConnectionError is subclass of RequestException: {issubclass(ConnectionError, RequestException)}")
print(f"Timeout is subclass of RequestException: {issubclass(Timeout, RequestException)}")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 238


Narrow cloud_id discovery exception handling (Ruff BLE001).

Catching bare Exception violates Ruff BLE001 and can mask unexpected errors. Catch the specific exceptions that requests.get() and response.json() actually raise: requests.RequestException (for HTTP errors) and ValueError (for JSON parsing failures). Also use structured logging format.

🔧 Proposed fix
-        except Exception as e:
-            logging.debug(f"Could not auto-discover cloud_id: {e}")
+        except (requests.RequestException, ValueError) as e:
+            logging.debug("Could not auto-discover cloud_id: %s", e)
🧰 Tools
🪛 Ruff (0.14.13)

120-120: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@holmes/plugins/toolsets/confluence/confluence.py` around lines 98 - 122, In
_discover_cloud_id, replace the broad "except Exception" with explicit exception
handling for requests.RequestException (for network/HTTP issues) and ValueError
(for response.json() parse errors), and update the logging call to use
structured logging (e.g., logging.debug("Could not auto-discover cloud_id",
exc_info=e) or logging.debug("Could not auto-discover cloud_id: %s", e)) so
errors aren't masked and are logged consistently; keep the rest of the
request/response logic and return None on handled failures.

Comment on lines +295 to +303
# Include full error context: status code, response text, and parsed JSON if available
status_code = e.response.status_code
response_text = e.response.text
error_json = None
try:
error_json = e.response.json()
except Exception:
pass

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and the specific lines mentioned
head -n 310 holmes/plugins/toolsets/confluence/confluence.py | tail -n 50

Repository: HolmesGPT/holmesgpt

Length of output: 1828


🏁 Script executed:

# Check if logging is imported in the file
grep -n "import logging" holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 79


🏁 Script executed:

# Check the full context around lines 295-303
sed -n '280,310p' holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 1217


🏁 Script executed:

# Check pyproject.toml for Ruff configuration
cat pyproject.toml | grep -A 50 "\[tool.ruff"

Repository: HolmesGPT/holmesgpt

Length of output: 45


🏁 Script executed:

# Check if pyproject.toml exists and view it
cat pyproject.toml 2>/dev/null | head -n 200

Repository: HolmesGPT/holmesgpt

Length of output: 6355


🏁 Script executed:

# Also check for ruff.toml
cat ruff.toml 2>/dev/null || echo "No ruff.toml found"

Repository: HolmesGPT/holmesgpt

Length of output: 969


🏁 Script executed:

# Let's look for Ruff configuration more broadly
find . -name "pyproject.toml" -o -name "ruff.toml" | head -5

Repository: HolmesGPT/holmesgpt

Length of output: 91


🏁 Script executed:

# Check if pre-commit hooks are configured
cat .pre-commit-config.yaml 2>/dev/null | grep -A 10 ruff || echo "No pre-commit ruff config found"

Repository: HolmesGPT/holmesgpt

Length of output: 493


🏁 Script executed:

# Let's verify the requests library's json() method behavior
python3 << 'EOF'
import json
# Check what exceptions json() can raise
from requests import Response

# Create a sample response with invalid JSON
response_obj = Response()
response_obj._content = b'not json'
response_obj.status_code = 200

try:
    response_obj.json()
except Exception as e:
    print(f"Exception type: {type(e).__name__}")
    print(f"Is it a ValueError? {isinstance(e, ValueError)}")
    print(f"Exception module: {type(e).__module__}")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 149


🏁 Script executed:

# Check if logging is used elsewhere in the confluence.py file for error logging
grep -n "logging\." holmes/plugins/toolsets/confluence/confluence.py | head -20

Repository: HolmesGPT/holmesgpt

Length of output: 222


🏁 Script executed:

# Verify that the BLE (blind-except) rule is indeed active and not ignored
rg "S110|BLE001|BLE002" ruff.toml

Repository: HolmesGPT/holmesgpt

Length of output: 45


🏁 Script executed:

# Check what the BLE rule actually covers in flake8-bandit/ruff
cat ruff.toml | grep -B5 -A5 "BLE"

Repository: HolmesGPT/holmesgpt

Length of output: 424


🏁 Script executed:

# Let's check the requests library documentation for json() method exceptions
# by looking at how it's used elsewhere in the codebase
rg "\.json\(\)" holmes/plugins/toolsets/ -A2 -B2 | head -50

Repository: HolmesGPT/holmesgpt

Length of output: 3289


🏁 Script executed:

# Verify the exact Ruff rule code S110 vs BLE001
rg "except.*:" holmes/plugins/toolsets/confluence/confluence.py | head -10

Repository: HolmesGPT/holmesgpt

Length of output: 495


🏁 Script executed:

# Verify which Ruff rule code actually corresponds to bare except clauses
rg "BLE001|S110" . --type py | head -5

Repository: HolmesGPT/holmesgpt

Length of output: 166


🏁 Script executed:

# Check the requests library behavior - when does json() raise ValueError vs other exceptions
python3 << 'EOF'
# Looking at requests source, response.json() calls json.loads()
# which raises json.JSONDecodeError (subclass of ValueError)
print("JSONDecodeError is a ValueError subclass: True")
print("Rule BLE001: catches bare except / except Exception")
print("Rule S110: is not related to bare except clauses")
EOF

Repository: HolmesGPT/holmesgpt

Length of output: 210


🏁 Script executed:

# Verify the actual line numbers and context one more time to ensure accuracy
sed -n '295,305p' holmes/plugins/toolsets/confluence/confluence.py

Repository: HolmesGPT/holmesgpt

Length of output: 394


Narrow exception type and log parse failures (Ruff BLE001).

Bare except Exception: pass violates the blind-except rule (BLE001, not S110) and hides diagnostics needed for error recovery. Per coding guidelines, toolsets must return detailed error context. Narrow to ValueError (json.JSONDecodeError is a ValueError subclass) and log the failure.

🔧 Proposed fix
-            try:
-                error_json = e.response.json()
-            except Exception:
-                pass
+            try:
+                error_json = e.response.json()
+            except ValueError as exc:
+                logging.debug("Failed to parse Confluence error JSON: %s", exc)
🧰 Tools
🪛 Ruff (0.14.13)

301-302: try-except-pass detected, consider logging the exception

(S110)


301-301: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@holmes/plugins/toolsets/confluence/confluence.py` around lines 295 - 303, The
try/except around parsing e.response.json() uses a bare except which hides
errors; change it to catch ValueError (or json.JSONDecodeError) specifically and
log the parse failure instead of silently passing—update the block where
error_json is set (variable error_json and the call e.response.json()) to use
except ValueError as parse_err: logger.debug/exception("Failed to parse error
response JSON", exc_info=parse_err) (or the module's configured logger) so
callers still get detailed error context.

- Add api.atlassian.com to HTTP passthru list in conftest.py
  (required for Atlassian Cloud API gateway calls)
- Fix shell script in test_case.yaml to properly handle Bearer auth
  for service accounts (previously used broken variable escaping)
- Update toolsets.yaml to support service accounts by not requiring
  username (omitting username triggers Bearer auth)

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

aantn commented Jan 19, 2026

Copy link
Copy Markdown
Collaborator Author

/eval
markers: confluence

@github-actions

Copy link
Copy Markdown
Contributor

@aantn Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/confluence-page-eval-By6aw
Model bedrock/eu.anthropic.claude-sonnet-4-5-20250929-v1:0
Markers confluence
Iterations 1
Duration 1m 2s
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
🚧 195_confluence_page_fetch — — — —
Total — avg — avg — avg —

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

Historical Comparison Details

Filter: excluding branch 'master'

Status: Success - 10 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/confluence-page-eval-By6aw -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, 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/confluence-page-eval-By6aw -f markers=regression -f filter=

@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/llm/fixtures/test_ask_holmes/195_confluence_page_fetch/test_case.yaml`:
- Around line 1-13: The test fixture is missing the required top-level runbooks
field; add an explicit runbooks: {} entry to the YAML in the test case so the
eval schema is satisfied (i.e., update the
test_ask_holmes/195_confluence_page_fetch/test_case.yaml to include runbooks: {}
at the top level alongside user_prompt, expected_output, tags, and
setup_timeout).
♻️ Duplicate comments (3)
holmes/plugins/toolsets/confluence/confluence.py (3)

74-91: Replace broad Exception catches in prerequisite & health checks.

These blocks still violate Ruff BLE001/RUF010 and mask unexpected failures. Narrow the exception types and use explicit conversion flags.

🔧 Proposed fix
-        except Exception as e:
-            return False, f"Failed to validate Confluence configuration: {str(e)}"
+        except (ValueError, TypeError, requests.RequestException) as e:
+            return False, f"Failed to validate Confluence configuration: {e!s}"
@@
-        except Exception as e:
-            return False, f"Confluence health check failed: {str(e)}"
+        except (requests.RequestException, ValueError) as e:
+            return False, f"Confluence health check failed: {e!s}"

Also applies to: 119-150


100-116: Narrow _discover_cloud_id exception handling and avoid f-string str(e).

Catching Exception here is flagged by Ruff and hides request/JSON errors.

🔧 Proposed fix
-        except Exception as e:
-            logging.debug(f"Could not auto-discover cloud_id: {e}")
+        except (requests.RequestException, ValueError) as e:
+            logging.debug("Could not auto-discover cloud_id: %s", e)

289-337: Avoid silent JSON parse failures and broad exception catch in tool errors.

The bare except and pass hide parse failures and still trigger Ruff BLE001/S110/RUF010.

🔧 Proposed fix
-            except Exception:
-                pass
+            except ValueError as exc:
+                logging.debug("Failed to parse Confluence error JSON: %s", exc)
@@
-        except requests.exceptions.ConnectionError as e:
+        except requests.exceptions.ConnectionError as e:
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Failed to connect to Confluence at {self._toolset.confluence_config.url}: {str(e)}",
-                data={"request_context": request_context, "connection_error": str(e)},
+                error=f"Failed to connect to Confluence at {self._toolset.confluence_config.url}: {e!s}",
+                data={"request_context": request_context, "connection_error": f"{e!s}"},
                 params=params,
             )
-        except Exception as e:
+        except (requests.RequestException, ValueError) as e:
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
-                error=f"Unexpected error querying Confluence endpoint '{endpoint}': {type(e).__name__}: {str(e)}",
+                error=f"Unexpected error querying Confluence endpoint '{endpoint}': {type(e).__name__}: {e!s}",
                 data={
                     "request_context": request_context,
                     "exception_type": type(e).__name__,
                 },
                 params=params,
             )

Comment on lines +1 to +13
user_prompt: |
Find and fetch the contents of the test page titled "HolmesGPT Eval Test Page" in Confluence.
Tell me the verification code found in the page content.

expected_output:
- "Must report the verification code: HOLMES-EVAL-7x9k2m4p"

tags:
- confluence
- question-answer
- medium

setup_timeout: 120

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.

⚠️ Potential issue | 🟠 Major

Add the required runbooks field to the eval test case.

This fixture should include runbooks: {} even when empty to satisfy the eval schema. As per coding guidelines, add an explicit runbooks entry.

🔧 Proposed fix
 tags:
   - confluence
   - question-answer
   - medium
+runbooks: {}
 
 setup_timeout: 120
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
user_prompt: |
Find and fetch the contents of the test page titled "HolmesGPT Eval Test Page" in Confluence.
Tell me the verification code found in the page content.
expected_output:
- "Must report the verification code: HOLMES-EVAL-7x9k2m4p"
tags:
- confluence
- question-answer
- medium
setup_timeout: 120
user_prompt: |
Find and fetch the contents of the test page titled "HolmesGPT Eval Test Page" in Confluence.
Tell me the verification code found in the page content.
expected_output:
- "Must report the verification code: HOLMES-EVAL-7x9k2m4p"
tags:
- confluence
- question-answer
- medium
runbooks: {}
setup_timeout: 120
🤖 Prompt for AI Agents
In `@tests/llm/fixtures/test_ask_holmes/195_confluence_page_fetch/test_case.yaml`
around lines 1 - 13, The test fixture is missing the required top-level runbooks
field; add an explicit runbooks: {} entry to the YAML in the test case so the
eval schema is satisfied (i.e., update the
test_ask_holmes/195_confluence_page_fetch/test_case.yaml to include runbooks: {}
at the top level alongside user_prompt, expected_output, tags, and
setup_timeout).

claude and others added 2 commits January 19, 2026 19:19
Simplifies CI/CD setup by reducing required env vars from 3 to 2:
- CONFLUENCE_BASE_URL
- CONFLUENCE_API_KEY

The space key 'MFS' is now hardcoded since we use a dedicated test instance.

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

aantn commented Jan 19, 2026

Copy link
Copy Markdown
Collaborator Author

/eval
markers: confluence

@github-actions

Copy link
Copy Markdown
Contributor

@aantn Your eval run has finished. ✅ Completed successfully


🧪 Manual Eval Results

Parameter Value
Triggered via /eval comment
Branch claude/confluence-page-eval-By6aw
Model bedrock/eu.anthropic.claude-sonnet-4-5-20250929-v1:0
Markers confluence
Iterations 1
Duration 1m 21s
Workflow View logs | Rerun

Results of HolmesGPT evals

  • ask_holmes: 1/1 test cases were successful, 0 regressions
Status Test case Time Turns Tools Cost
✅ 195_confluence_page_fetch 21.3s 5 8 $0.1301
Total 21.3s avg 5.0 avg 8.0 avg $0.1301

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

Historical Comparison Details

Filter: excluding branch 'master'

Status: Success - 9 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/confluence-page-eval-By6aw -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, 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/confluence-page-eval-By6aw -f markers=regression -f filter=

Atlassian Cloud uses /wiki/rest/api/... while self-hosted Confluence
(Server/Data Center) uses /rest/api/... without the /wiki prefix.

Changes:
- Add _get_api_path_prefix() to determine correct API path
- Update all tools to use dynamic API prefix
- Update docstring with self-hosted configuration example

Signed-off-by: Claude <noreply@anthropic.com>
- Remove redundant test 195_confluence_page_fetch (covered by 208)
- Update tests 208, 209, 210 toolsets.yaml with Python toolset config
  (url and api_key instead of relying on env vars directly)

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants