Skip to content

feat: add Telegram and data platform integrations - #2319

Open
Dirouk wants to merge 3 commits into
HolmesGPT:masterfrom
Dirouk:feature/telegram-trino-flink-airflow
Open

feat: add Telegram and data platform integrations#2319
Dirouk wants to merge 3 commits into
HolmesGPT:masterfrom
Dirouk:feature/telegram-trino-flink-airflow

Conversation

@Dirouk

@Dirouk Dirouk commented Jul 25, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features
    • Added built-in diagnostics toolsets for Apache Airflow, Apache Flink, and Trino (bounded, read-only SQL where applicable).
    • Added a Telegram bot adapter with per-chat context, allowed-chat allowlisting, /reset, and safe long-message splitting.
    • Introduced the holmes-telegram console entry point and an opt-in Helm deployment for Telegram.
  • Documentation
    • Added/updated setup and configuration docs plus navigation/README entries for Airflow, Flink, Trino, and Telegram.
  • Tests
    • Added unit and Helm validation coverage for Telegram, and toolset behavior checks for auth, limits, and pagination.
  • Chores
    • Improved checksum verification robustness in the container build.

Signed-off-by: Vladimir.Yakovlev <striker_vlad@mail.ru>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 25, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: Dirouk / name: Vladimir.Yakovlev (5ce4e8a)

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02608195-706c-490a-839c-8ebe5d9f1326

📥 Commits

Reviewing files that changed from the base of the PR and between 097a703 and 54a22ec.

📒 Files selected for processing (1)
  • Dockerfile

Walkthrough

This change adds Airflow, Flink, and Trino built-in HTTP toolsets, a shared HTTP API foundation, and a Telegram long-polling adapter. It registers the integrations, adds Helm deployment support and tests, updates documentation, and normalizes Docker checksum files before verification.

Changes

Data platform integrations

Layer / File(s) Summary
Shared HTTP toolset foundation
holmes/plugins/toolsets/http_api_base.py
Adds shared API URL, authentication, headers, timeout, health-check, request, and structured error handling.
Airflow diagnostics
holmes/plugins/toolsets/airflow/*, tests/plugins/toolsets/test_data_platform_toolsets.py
Adds bounded DAG, run, task-instance, and task-log inspection tools with API version and authentication validation.
Flink diagnostics
holmes/plugins/toolsets/flink/*, tests/plugins/toolsets/test_data_platform_toolsets.py
Adds bounded job, vertex, exception, and checkpoint inspection tools with job ID validation.
Trino diagnostics
holmes/plugins/toolsets/trino/*, tests/plugins/toolsets/test_data_platform_toolsets.py
Adds cluster information and bounded read-only SQL querying with pagination, truncation, and cleanup handling.
Toolset registration and documentation
holmes/plugins/toolsets/__init__.py, docs/data-sources/builtin-toolsets/*, README.md, docs/why-holmesgpt.md
Registers the new toolsets and documents their configuration, navigation, use cases, and supported integrations.

Telegram adapter

Layer / File(s) Summary
Telegram bot runtime
holmes/plugins/telegram/*
Adds Telegram polling, HolmesGPT forwarding, per-chat history, commands, allowlisting, message splitting, and environment-based configuration.
Telegram packaging and deployment
pyproject.toml, helm/holmes/*, tests/test_telegram_helm.py
Adds the holmes-telegram entry point, opt-in Helm deployment, secret-based token wiring, security defaults, and chart tests.
Telegram documentation and behavior tests
docs/installation/*, README.md, tests/plugins/test_telegram_bot.py
Adds installation guidance, navigation, integration status, and adapter behavior tests.

Docker build verification

Layer / File(s) Summary
Checksum normalization
Dockerfile
Normalizes CRLF checksum files before verifying downloaded static binaries.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TelegramUser
  participant TelegramAPI
  participant HolmesTelegramBot
  participant HolmesAPI
  TelegramUser->>TelegramAPI: Send question
  HolmesTelegramBot->>TelegramAPI: Poll update
  TelegramAPI-->>HolmesTelegramBot: Return update
  HolmesTelegramBot->>HolmesAPI: Forward question and chat history
  HolmesAPI-->>HolmesTelegramBot: Return analysis and updated history
  HolmesTelegramBot->>TelegramAPI: Send analysis
  TelegramAPI-->>TelegramUser: Display response
Loading

Suggested reviewers: moshemorad, roiglinik

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change by adding Telegram and data platform integrations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@netlify

netlify Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit 54a22ec
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/6a65193d7123f50008ea7260
😎 Deploy Preview https://deploy-preview-2319--holmes-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (8)
docs/data-sources/builtin-toolsets/airflow.md (2)

40-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Common Use Cases blocks don't follow the documented holmes ask house style.

See consolidated comment covering this file and flink.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/data-sources/builtin-toolsets/airflow.md` around lines 40 - 48, Update
the “Common Use Cases” examples in the Airflow documentation to follow the
documented `holmes ask` house style, matching the format used by the
consolidated guidance and the corresponding Flink documentation examples.

Source: Learnings


1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Common Use Cases sections don't follow the documented holmes ask house style.

Both new toolset docs present raw prompt strings in text fences rather than individual bash blocks with a holmes ask "..." command, which is the documented house style for this section.

  • docs/data-sources/builtin-toolsets/airflow.md#L40-48: wrap each example as its own ```bash block containing holmes ask "...".
  • docs/data-sources/builtin-toolsets/flink.md#L32-40: same change.

Based on learnings: "document each individual holmes ask command in its own separate fenced bash code block (bash ... )... follow the existing house style used across the other toolset docs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/data-sources/builtin-toolsets/airflow.md` at line 1, Update the Common
Use Cases examples in the Airflow and Flink toolset documentation to use the
established house style: place each prompt in its own separate ```bash block
containing a holmes ask "..." command, replacing the existing raw prompt text
fences.

Source: Learnings

docs/data-sources/builtin-toolsets/flink.md (1)

32-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Common Use Cases blocks don't follow the documented holmes ask house style.

See consolidated comment covering this file and airflow.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/data-sources/builtin-toolsets/flink.md` around lines 32 - 40, Update the
Common Use Cases examples in the Flink documentation to follow the documented
`holmes ask` house style, including the required command format for each
example. Preserve the existing Flink questions and intent while aligning both
code blocks with the conventions used in the consolidated guidance.

Source: Learnings

holmes/plugins/toolsets/http_api_base.py (2)

91-102: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a shared requests.Session() for connection reuse.

Each call to _request issues requests.request(...), opening a new connection every time. Airflow/Flink/Trino tools will call this repeatedly during an investigation; a persistent Session per toolset instance would reduce connection setup overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/http_api_base.py` around lines 91 - 102, Update the
HTTP request flow in _request to reuse a persistent requests.Session owned by
each toolset instance instead of calling requests.request directly. Initialize
the session during toolset construction and preserve the existing method, URL,
parameters, authentication, timeout, SSL verification, response validation, and
return behavior.

23-31: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider SecretStr for bearer_token/password.

These are stored as plain str. If the config object or a validation error is ever logged/repr'd (e.g. _health_check's error message wraps arbitrary exceptions), the secret could leak into logs/tracebacks. Pydantic's SecretStr masks the value in repr()/str() while still allowing .get_secret_value() where needed.

🔒 Proposed fix
+from pydantic import Field, SecretStr, model_validator
-    bearer_token: Optional[str] = Field(
+    bearer_token: Optional[SecretStr] = Field(
         default=None, description="Bearer token used for API authentication"
     )
     username: Optional[str] = Field(
         default=None, description="Username used for HTTP basic authentication"
     )
-    password: Optional[str] = Field(
+    password: Optional[SecretStr] = Field(
         default=None, description="Password used for HTTP basic authentication"
     )

Update usages in _request accordingly (self.http_config.bearer_token.get_secret_value(), and pass self.http_config.password.get_secret_value() to HTTPBasicAuth).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/http_api_base.py` around lines 23 - 31, Change the
bearer_token and password fields in the HTTP configuration model to
Optional[SecretStr], then update _request to unwrap them with get_secret_value()
before constructing authorization headers or HTTPBasicAuth; preserve existing
behavior when either value is None.
holmes/plugins/toolsets/airflow/airflow.py (1)

74-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate _bounded implementation (shared with Flink).

See consolidated comment (anchored in holmes/plugins/toolsets/flink/flink.py) covering this duplication and the related unbounded-fetch guideline concern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/airflow/airflow.py` around lines 74 - 88, Remove the
duplicate _bounded implementation from _AirflowTool and reuse the shared
bounded-result helper used by the Flink toolset. Update the Airflow tool flow to
call that common implementation while preserving collection-key truncation and
holmes_truncated behavior.
holmes/plugins/toolsets/trino/trino.py (1)

141-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract repeated cast(TrinoToolset, self._toolset) into a local variable.

The same cast is repeated four times in this method; hoisting it once at the top improves readability without behavior change.

♻️ Proposed refactor
+        trino_toolset = cast(TrinoToolset, self._toolset)
         try:
             response = self._toolset._request(
                 "POST",
                 "/v1/statement",
                 data=query,
                 request_context=context.request_context,
-                headers=cast(TrinoToolset, self._toolset)._trino_headers(),
+                headers=trino_toolset._trino_headers(),
             )
             while True:
                 ...
                 if (
                     not next_uri
-                    or pages >= cast(TrinoToolset, self._toolset).trino_config.max_pages
+                    or pages >= trino_toolset.trino_config.max_pages
                     or len(rows)
-                    >= cast(TrinoToolset, self._toolset).trino_config.max_rows
+                    >= trino_toolset.trino_config.max_rows
                 ):
                     break
                 ...
-            max_rows = cast(TrinoToolset, self._toolset).trino_config.max_rows
+            max_rows = trino_toolset.trino_config.max_rows
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/trino/trino.py` around lines 141 - 190, In the method
containing the paginated Trino request loop, assign cast(TrinoToolset,
self._toolset) to a local variable once near the start, then reuse that variable
for _trino_headers() and both trino_config accesses. Preserve all existing
request and pagination behavior.
docs/data-sources/builtin-toolsets/trino.md (1)

38-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Common Use Cases examples don't follow the established holmes ask bash-block convention.

Other toolset docs wrap each example as an individual holmes ask "..." command in its own bash fence, not bare text blocks.

📝 Proposed fix
-```text
-Show failed Trino queries from the last hour and summarize their error types
-```
-
-```text
-Explain the query plan for this SELECT without running it
-```
+```bash
+holmes ask "Show failed Trino queries from the last hour and summarize their error types"
+```
+
+```bash
+holmes ask "Explain the query plan for this SELECT without running it"
+```

Based on learnings, "document each individual holmes ask command in its own separate fenced bash code block... follow the existing house style used across the other toolset docs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/data-sources/builtin-toolsets/trino.md` around lines 38 - 46, Update the
Common Use Cases examples in the Trino toolset documentation so each example is
a complete holmes ask command, with its prompt quoted, inside its own separate
bash-fenced code block. Preserve both existing example prompts and follow the
established formatting used by the other toolset docs.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/data-sources/builtin-toolsets/.nav.yml`:
- Around line 26-30: Move the “Trino: trino.md” entry out of the Grafana/Helm
group in the navigation list and place it in the later section containing the
other S/T entries, preserving the intended alphabetical order and Grafana-family
grouping.

In `@holmes/plugins/telegram/bot.py`:
- Around line 29-30: Add the required -> None return annotations to the
constructors, including the __init__ methods near Telegram bot initialization,
while preserving their existing parameters and behavior.
- Line 22: Make Telegram access fail closed by requiring a non-empty
allowed_chat_ids configuration in holmes/plugins/telegram/bot.py:22-22, unless
an explicitly named public-access setting is enabled; update the runtime
validation and preserve membership enforcement by default. In
docs/installation/telegram-installation.md:20-22, remove the claim that
TELEGRAM_ALLOWED_CHAT_IDS is optional and document the explicit public-access
opt-in if retained.
- Around line 143-160: Update handle_update so Holmes request/processing
failures are handled separately from Telegram delivery failures. Ensure every
error notification sent via send_message is best-effort and cannot propagate if
delivery fails, allowing polling in run to continue after transient Telegram
send errors.
- Around line 155-160: Update the exception handling around the Telegram request
flow to avoid exposing raw error details: use generic user-facing messages in
both self.telegram.send_message calls, and sanitize or redact token-bearing
Telegram URLs before logging requests.exceptions.RequestException and other
caught errors via logging.exception.

In `@holmes/plugins/toolsets/flink/flink.py`:
- Around line 55-68: Update the Flink fetch flow associated with
_FlinkTool._bounded so the underlying API request applies flink_config.max_items
before retrieving the collection, rather than fetching all values and truncating
afterward. Reuse the existing max_items configuration and preserve
holmes_truncated semantics for responses exceeding the limit; align the
implementation with Airflow’s _AirflowTool._bounded without duplicating
client-side-only logic.
- Line 1: Add a shared HTTPAPITool/toolset helper using JsonFilterMixin that
declares Flink’s supported collection fields and applies jq/max_depth filtering
during JSON handling. Update FlinkGetJob, FlinkGetExceptions, and
FlinkGetCheckpoints to use this helper instead of loading full responses and
manually slicing collections with the Airflow _bounded pattern.
- Around line 89-100: Update _FlinkJobTool._invoke to percent-encode the
validated job_id with path-segment encoding (quote using safe="") before
interpolating it into the /jobs/ request path, preventing ../ or
slash-containing IDs from altering URL resolution. Preserve the existing
missing-parameter handling and add a regression test covering percent-encoding
of a job ID containing path separators.

---

Nitpick comments:
In `@docs/data-sources/builtin-toolsets/airflow.md`:
- Around line 40-48: Update the “Common Use Cases” examples in the Airflow
documentation to follow the documented `holmes ask` house style, matching the
format used by the consolidated guidance and the corresponding Flink
documentation examples.
- Line 1: Update the Common Use Cases examples in the Airflow and Flink toolset
documentation to use the established house style: place each prompt in its own
separate ```bash block containing a holmes ask "..." command, replacing the
existing raw prompt text fences.

In `@docs/data-sources/builtin-toolsets/flink.md`:
- Around line 32-40: Update the Common Use Cases examples in the Flink
documentation to follow the documented `holmes ask` house style, including the
required command format for each example. Preserve the existing Flink questions
and intent while aligning both code blocks with the conventions used in the
consolidated guidance.

In `@docs/data-sources/builtin-toolsets/trino.md`:
- Around line 38-46: Update the Common Use Cases examples in the Trino toolset
documentation so each example is a complete holmes ask command, with its prompt
quoted, inside its own separate bash-fenced code block. Preserve both existing
example prompts and follow the established formatting used by the other toolset
docs.

In `@holmes/plugins/toolsets/airflow/airflow.py`:
- Around line 74-88: Remove the duplicate _bounded implementation from
_AirflowTool and reuse the shared bounded-result helper used by the Flink
toolset. Update the Airflow tool flow to call that common implementation while
preserving collection-key truncation and holmes_truncated behavior.

In `@holmes/plugins/toolsets/http_api_base.py`:
- Around line 91-102: Update the HTTP request flow in _request to reuse a
persistent requests.Session owned by each toolset instance instead of calling
requests.request directly. Initialize the session during toolset construction
and preserve the existing method, URL, parameters, authentication, timeout, SSL
verification, response validation, and return behavior.
- Around line 23-31: Change the bearer_token and password fields in the HTTP
configuration model to Optional[SecretStr], then update _request to unwrap them
with get_secret_value() before constructing authorization headers or
HTTPBasicAuth; preserve existing behavior when either value is None.

In `@holmes/plugins/toolsets/trino/trino.py`:
- Around line 141-190: In the method containing the paginated Trino request
loop, assign cast(TrinoToolset, self._toolset) to a local variable once near the
start, then reuse that variable for _trino_headers() and both trino_config
accesses. Preserve all existing request and pagination behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cb1f330b-cd43-4dbe-b233-876b0e65496c

📥 Commits

Reviewing files that changed from the base of the PR and between 20c62ad and 5ce4e8a.

📒 Files selected for processing (25)
  • README.md
  • docs/data-sources/builtin-toolsets/.nav.yml
  • docs/data-sources/builtin-toolsets/airflow.md
  • docs/data-sources/builtin-toolsets/flink.md
  • docs/data-sources/builtin-toolsets/index.md
  • docs/data-sources/builtin-toolsets/trino.md
  • docs/installation/.nav.yml
  • docs/installation/telegram-installation.md
  • docs/why-holmesgpt.md
  • holmes/plugins/telegram/__init__.py
  • holmes/plugins/telegram/bot.py
  • holmes/plugins/toolsets/__init__.py
  • holmes/plugins/toolsets/airflow/__init__.py
  • holmes/plugins/toolsets/airflow/airflow.py
  • holmes/plugins/toolsets/airflow/instructions.jinja2
  • holmes/plugins/toolsets/flink/__init__.py
  • holmes/plugins/toolsets/flink/flink.py
  • holmes/plugins/toolsets/flink/instructions.jinja2
  • holmes/plugins/toolsets/http_api_base.py
  • holmes/plugins/toolsets/trino/__init__.py
  • holmes/plugins/toolsets/trino/instructions.jinja2
  • holmes/plugins/toolsets/trino/trino.py
  • pyproject.toml
  • tests/plugins/test_telegram_bot.py
  • tests/plugins/toolsets/test_data_platform_toolsets.py

Comment on lines 26 to 30
- Grafana Dashboards: grafanadashboards.md
- Loki: grafanaloki.md
- Tempo: grafanatempo.md
- Trino: trino.md
- Helm: helm.md

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trino entry misplaced in the nav order.

Trino: trino.md is inserted between the Grafana-family cluster (Grafana (MCP)/Grafana Dashboards/Loki/Tempo) and Helm, breaking both the alphabetical continuation and that intentional grouping. It belongs near the other "S"/"T" entries further down.

📝 Proposed fix
   - Tempo: grafanatempo.md
-  - Trino: trino.md
   - Helm: helm.md

and further down:

   - SQLite: database-sqlite.md
+  - Trino: trino.md
   - VictoriaLogs: victorialogs.md
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/data-sources/builtin-toolsets/.nav.yml` around lines 26 - 30, Move the
“Trino: trino.md” entry out of the Grafana/Helm group in the navigation list and
place it in the later section containing the other S/T entries, preserving the
intended alphabetical order and Grafana-family grouping.

class TelegramBotConfig(BaseModel):
bot_token: str
holmes_api_url: str = "http://localhost:8080"
allowed_chat_ids: set[int] = Field(default_factory=set)

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when no Telegram allowlist is configured.

An empty set disables the membership check at runtime, so any chat that can reach the bot can invoke HolmesGPT. Require at least one allowed chat ID by default, or add an explicit, deliberately named public-access opt-in.

  • holmes/plugins/telegram/bot.py#L22-L22: require a non-empty allowlist unless an explicit public-access setting is enabled.
  • docs/installation/telegram-installation.md#L20-L22: remove the claim that TELEGRAM_ALLOWED_CHAT_IDS is optional and document the explicit public-access opt-in if retained.
📍 Affects 2 files
  • holmes/plugins/telegram/bot.py#L22-L22 (this comment)
  • docs/installation/telegram-installation.md#L20-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/telegram/bot.py` at line 22, Make Telegram access fail closed
by requiring a non-empty allowed_chat_ids configuration in
holmes/plugins/telegram/bot.py:22-22, unless an explicitly named public-access
setting is enabled; update the runtime validation and preserve membership
enforcement by default. In docs/installation/telegram-installation.md:20-22,
remove the claim that TELEGRAM_ALLOWED_CHAT_IDS is optional and document the
explicit public-access opt-in if retained.

Comment on lines +29 to +30
def __init__(self, config: TelegramBotConfig):
self.config = config

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add -> None to the constructors.

These methods lack required return type annotations.

Proposed fix
-class TelegramAPI:
-    def __init__(self, config: TelegramBotConfig):
+class TelegramAPI:
+    def __init__(self, config: TelegramBotConfig) -> None:

-class HolmesAPI:
-    def __init__(self, config: TelegramBotConfig):
+class HolmesAPI:
+    def __init__(self, config: TelegramBotConfig) -> None:

 class HolmesTelegramBot:
     def __init__(
         self,
         config: TelegramBotConfig,
         telegram: Optional[TelegramAPI] = None,
         holmes: Optional[HolmesAPI] = None,
-    ):
+    ) -> None:

As per coding guidelines, “Type hints required - use mypy for type checking.”

Also applies to: 74-75, 104-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/telegram/bot.py` around lines 29 - 30, Add the required ->
None return annotations to the constructors, including the __init__ methods near
Telegram bot initialization, while preserving their existing parameters and
behavior.

Source: Coding guidelines

Comment on lines +143 to +160
try:
response = self.holmes.ask(
prompt,
chat_id=chat_id,
history=self._histories.get(chat_id),
)
history = response.get("conversation_history")
if isinstance(history, list):
self._histories[chat_id] = trim_conversation_history(
history, self.config.history_messages
)
self.telegram.send_message(chat_id, response["analysis"])
except requests.exceptions.RequestException as error:
logging.exception("Telegram request failed")
self.telegram.send_message(chat_id, f"HolmesGPT request failed: {error}")
except Exception as error:
logging.exception("Telegram update failed")
self.telegram.send_message(chat_id, f"HolmesGPT could not answer: {error}")

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep a transient Telegram send failure from stopping polling.

A failure in send_message() enters an except block that immediately calls send_message() again. If that attempt also fails, it escapes handle_update() and terminates run(). Split Holmes failures from delivery failures, and make error-notification delivery best-effort.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/telegram/bot.py` around lines 143 - 160, Update handle_update
so Holmes request/processing failures are handled separately from Telegram
delivery failures. Ensure every error notification sent via send_message is
best-effort and cannot propagate if delivery fails, allowing polling in run to
continue after transient Telegram send errors.

Comment on lines +155 to +160
except requests.exceptions.RequestException as error:
logging.exception("Telegram request failed")
self.telegram.send_message(chat_id, f"HolmesGPT request failed: {error}")
except Exception as error:
logging.exception("Telegram update failed")
self.telegram.send_message(chat_id, f"HolmesGPT could not answer: {error}")

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import requests

response = requests.Response()
response.status_code = 502
response.url = "https://api.telegram.org/botTOKEN_REDACTED/sendMessage"

try:
    response.raise_for_status()
except requests.HTTPError as error:
    print(str(error))
PY

Repository: HolmesGPT/holmesgpt

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the Telegram bot adapter around the reported handler.
if [ -f holmes/plugins/telegram/bot.py ]; then
  echo "### holmes/plugins/telegram/bot.py outline"
  ast-grep outline holmes/plugins/telegram/bot.py || true
  echo
  echo "### holmes/plugins/telegram/bot.py lines 120-175"
  sed -n '120,175p' holmes/plugins/telegram/bot.py | nl -ba -v120
else
  echo "missing holmes/plugins/telegram/bot.py"
fi

echo
echo "### send_message implementation/usages"
rg -n "def send_message|send_message\(|Telegram|bot_token|api.telegram.org|logging\.exception" -S .

Repository: HolmesGPT/holmesgpt

Length of output: 884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "### holmes/plugins/telegram/bot.py lines 120-175"
sed -n '120,175p' holmes/plugins/telegram/bot.py

echo
echo "### send_message and telegram-api call sites"
python3 - <<'PY'
from pathlib import Path
p = Path('holmes/plugins/telegram/bot.py')
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if i < 50 or i > 165:
        continue
    if 'send_message' in line or 'TelegramAPI' in line or 'api.telegram.org' in line or 'bot_token' in line or 'logger.exception' in line or 'logging.exception' in line:
        print(f"{i}: {line}")
PY

Repository: HolmesGPT/holmesgpt

Length of output: 3210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "### holmes/plugins/telegram/bot.py lines 1-75"
sed -n '1,75p' holmes/plugins/telegram/bot.py

Repository: HolmesGPT/holmesgpt

Length of output: 2654


Do not expose raw request errors to Telegram users or logs.

This handler catches Telegram API request failures as well, and requests.HTTPError includes the full token-bearing URL in str(error). Use generic user-facing messages and redact the token-containing Telegram URL before logging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/telegram/bot.py` around lines 155 - 160, Update the exception
handling around the Telegram request flow to avoid exposing raw error details:
use generic user-facing messages in both self.telegram.send_message calls, and
sanitize or redact token-bearing Telegram URLs before logging
requests.exceptions.RequestException and other caught errors via
logging.exception.

@@ -0,0 +1,178 @@
import os

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate JsonFilterMixin to confirm its interface before integrating
rg -n -A 15 'class JsonFilterMixin' --type=py
rg -nP 'JsonFilterMixin' --type=py -g '!**/test*'

Repository: HolmesGPT/holmesgpt

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== matching file =="
fd -a 'flink\.py|airflow\.py|http_api_base\.py|pyproject\.toml' . | sed "s#$(pwd)/##"

echo
echo "== broad mixin/similar symbols in python files =="
rg -n 'JsonFilterMixin|_bounded|max_depth|jq|JsonFilter|filter' --type=py .

echo
echo "== toolset files sizes =="
for f in holmes/plugins/toolsets/flink/flink.py holmes/plugins/toolsets/airflow/airflow.py holmes/plugins/toolsets/http_api_base.py
do
  if [ -f "$f" ]; then
    wc -l "$f"
  fi
done

echo
echo "== relevant slices =="
sed -n '1,160p' holmes/plugins/toolsets/flink/flink.py 2>/dev/null || true
echo "---"
sed -n '1,180p' holmes/plugins/toolsets/airflow/airflow.py 2>/dev/null || true
echo "---"
sed -n '1,200p' holmes/plugins/toolsets/http_api_base.py 2>/dev/null || true

Repository: HolmesGPT/holmesgpt

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

MODIFIED_FILES=$(git diff --name-only -- diff --stat 2>/dev/null || true)

echo "== files under holmes/plugins/toolsets =="
git ls-files 'holmes/plugins/toolsets/*.py' 'holmes/plugins/toolsets/**/.py' | sed 's#^\./##' | head -200

echo
echo "== JsonFilterMixin exact =="
rg -n 'class JsonFilterMixin|JsonFilterMixin|filter_parameters|max_depth|jq' --type=py holmes/plugins/toolsets holmes/plugins tests/plugins/toolsets 2>/dev/null | head -240

echo
echo "== Flink/Airflow/HTTPAPI relevant files =="
for f in holmes/plugins/toolsets/flink/flink.py holmes/plugins/toolsets/airflow/airflow.py holmes/plugins/toolsets/http_api_base.py; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    sed -n '1,180p' "$f"
  fi
done

echo
echo "== imports/usages of _bounded =="
rg -n 'def _bounded|_bounded\(|bounded' --type=py .

Repository: HolmesGPT/holmesgpt

Length of output: 43828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== full JsonFilterMixin =="
sed -n '1,190p' holmes/plugins/toolsets/json_filter_mixin.py

echo
echo "== Flink exception/checkpoint classes rest =="
sed -n '120,180p' holmes/plugins/toolsets/flink/flink.py

Repository: HolmesGPT/holmesgpt

Length of output: 9026


Consolidate unbounded response slicing into the shared JSON filter helper.

Flink’s FlinkGetJob, FlinkGetExceptions, and FlinkGetCheckpoints slice result collections only after requests.json() has loaded the full response, and Flink repeats the Airflow _bounded(..., collection_key) slicing pattern. For endpoints without server-side list filters, add a shared HTTPAPITool/toolset helper that exposes supported collection fields plus jq/max_depth filtering via JsonFilterMixin, so future additions don’t hand-roll the same unbounded-memory pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/flink/flink.py` at line 1, Add a shared
HTTPAPITool/toolset helper using JsonFilterMixin that declares Flink’s supported
collection fields and applies jq/max_depth filtering during JSON handling.
Update FlinkGetJob, FlinkGetExceptions, and FlinkGetCheckpoints to use this
helper instead of loading full responses and manually slicing collections with
the Airflow _bounded pattern.

Source: Coding guidelines

Comment on lines +55 to +68
class _FlinkTool(HTTPAPITool):
def _bounded(
self, result: StructuredToolResult, collection_key: str
) -> StructuredToolResult:
if result.status != StructuredToolResultStatus.SUCCESS or not isinstance(
result.data, dict
):
return result
values = result.data.get(collection_key)
if isinstance(values, list):
max_items = cast(FlinkToolset, self._toolset).flink_config.max_items
result.data[collection_key] = values[:max_items]
result.data["holmes_truncated"] = len(values) > max_items
return result

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Unbounded fetch before client-side truncation; duplicated with Airflow's _bounded.

See consolidated comment covering both this file and airflow.py's _AirflowTool._bounded.

Also applies to: 89-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/flink/flink.py` around lines 55 - 68, Update the
Flink fetch flow associated with _FlinkTool._bounded so the underlying API
request applies flink_config.max_items before retrieving the collection, rather
than fetching all values and truncating afterward. Reuse the existing max_items
configuration and preserve holmes_truncated semantics for responses exceeding
the limit; align the implementation with Airflow’s _AirflowTool._bounded without
duplicating client-side-only logic.

Source: Coding guidelines

Comment on lines +89 to +100
class _FlinkJobTool(_FlinkTool):
endpoint_suffix: ClassVar[str] = ""

def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
job_id = str(params.get("job_id", "")).strip()
if not job_id:
return StructuredToolResult(
status=StructuredToolResultStatus.ERROR,
error="Missing required parameter 'job_id'",
params=params,
)
return self._get_json(f"/jobs/{job_id}{self.endpoint_suffix}", params, context)

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

URL-encode job_id before interpolating into the request path.

Unlike Airflow's dag_id/dag_run_id/task_id (percent-encoded via quote(..., safe="")), job_id here is inserted into the URL path raw. A job_id containing ../ sequences can escape the /jobs/ prefix and reach other paths on the same Flink host via urljoin's relative-path resolution in http_api_base.py's _request.

🔒 Proposed fix
+from urllib.parse import quote
 ...
     def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
-        job_id = str(params.get("job_id", "")).strip()
+        job_id = quote(str(params.get("job_id", "")).strip(), safe="")
         if not job_id:
             return StructuredToolResult(
                 status=StructuredToolResultStatus.ERROR,
                 error="Missing required parameter 'job_id'",
                 params=params,
             )
         return self._get_json(f"/jobs/{job_id}{self.endpoint_suffix}", params, context)

Consider a regression test similar to Airflow's orders%2Fdaily percent-encoding test to cover this. Want me to draft one?

📝 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
class _FlinkJobTool(_FlinkTool):
endpoint_suffix: ClassVar[str] = ""
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
job_id = str(params.get("job_id", "")).strip()
if not job_id:
return StructuredToolResult(
status=StructuredToolResultStatus.ERROR,
error="Missing required parameter 'job_id'",
params=params,
)
return self._get_json(f"/jobs/{job_id}{self.endpoint_suffix}", params, context)
from urllib.parse import quote
class _FlinkJobTool(_FlinkTool):
endpoint_suffix: ClassVar[str] = ""
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
job_id = quote(str(params.get("job_id", "")).strip(), safe="")
if not job_id:
return StructuredToolResult(
status=StructuredToolResultStatus.ERROR,
error="Missing required parameter 'job_id'",
params=params,
)
return self._get_json(f"/jobs/{job_id}{self.endpoint_suffix}", params, context)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@holmes/plugins/toolsets/flink/flink.py` around lines 89 - 100, Update
_FlinkJobTool._invoke to percent-encode the validated job_id with path-segment
encoding (quote using safe="") before interpolating it into the /jobs/ request
path, preventing ../ or slash-containing IDs from altering URL resolution.
Preserve the existing missing-parameter handling and add a regression test
covering percent-encoding of a job ID containing path separators.

Signed-off-by: Vladimir.Yakovlev <striker_vlad@mail.ru>

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/installation/telegram-installation.md`:
- Around line 34-36: Update the kubectl secret creation command in the Telegram
installation instructions to use the Helm release namespace instead of
hardcoding holmes, ensuring the created Secret matches the Deployment’s
.Release.Namespace for installations in any namespace.

In `@helm/holmes/templates/telegram-deployment.yaml`:
- Around line 17-18: Update the Telegram Deployment strategy near replicas to
prevent overlapping pollers during rollouts: configure Recreate, or use
RollingUpdate with maxSurge set to 0 and maxUnavailable set to 1. Preserve the
existing replica count and other deployment settings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d037af66-f884-4eb7-b245-5d81efeb211e

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce4e8a and 097a703.

📒 Files selected for processing (6)
  • docs/installation/telegram-installation.md
  • helm/holmes/templates/telegram-deployment.yaml
  • helm/holmes/values.yaml
  • holmes/plugins/telegram/bot.py
  • tests/plugins/test_telegram_bot.py
  • tests/test_telegram_helm.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • holmes/plugins/telegram/bot.py

Comment on lines +34 to +36
kubectl create secret generic holmes-telegram \
--from-literal=bot-token="<bot token>" \
--namespace holmes

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Create the Secret in the release namespace.

The Deployment references Secrets in .Release.Namespace, but this command always creates it in holmes. Parameterize the namespace so installs in another namespace do not fail to start.

Proposed fix
+NAMESPACE=<helm-release-namespace>
 kubectl create secret generic holmes-telegram \
   --from-literal=bot-token="<bot token>" \
-  --namespace holmes
+  --namespace "$NAMESPACE"
📝 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
kubectl create secret generic holmes-telegram \
--from-literal=bot-token="<bot token>" \
--namespace holmes
NAMESPACE=<helm-release-namespace>
kubectl create secret generic holmes-telegram \
--from-literal=bot-token="<bot token>" \
--namespace "$NAMESPACE"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/installation/telegram-installation.md` around lines 34 - 36, Update the
kubectl secret creation command in the Telegram installation instructions to use
the Helm release namespace instead of hardcoding holmes, ensuring the created
Secret matches the Deployment’s .Release.Namespace for installations in any
namespace.

Comment on lines +17 to +18
spec:
replicas: 1

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent overlapping pollers during rollouts.

The default RollingUpdate strategy can create a second Pod before terminating the old one. That temporarily runs two long-pollers for the same bot token, risking duplicate or split update handling. Use Recreate (or maxSurge: 0 with maxUnavailable: 1).

Proposed fix
 spec:
   replicas: 1
+  strategy:
+    type: Recreate
   selector:
📝 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
spec:
replicas: 1
spec:
replicas: 1
strategy:
type: Recreate
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@helm/holmes/templates/telegram-deployment.yaml` around lines 17 - 18, Update
the Telegram Deployment strategy near replicas to prevent overlapping pollers
during rollouts: configure Recreate, or use RollingUpdate with maxSurge set to 0
and maxUnavailable set to 1. Preserve the existing replica count and other
deployment settings.

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.

1 participant