Skip to content

feat: Add image ingestion and update staging tests for Slack#461

Merged
yacosta738 merged 9 commits into
mainfrom
feat/slack-image-ingestion-329
Apr 8, 2026
Merged

feat: Add image ingestion and update staging tests for Slack#461
yacosta738 merged 9 commits into
mainfrom
feat/slack-image-ingestion-329

Conversation

@yacosta738
Copy link
Copy Markdown
Contributor

This pull request adds support for inbound image and file ingestion from Slack channels, making Slack a fully supported multimodal channel alongside Telegram, WhatsApp, and Discord. The changes include the implementation of Slack image/file fetching, staging, and parsing, as well as updates to configuration and documentation to reflect the new capability. Comprehensive tests have been added to ensure correct behavior and security.

Slack multimodal image/file ingestion support:

  • Implemented Slack image/file fetching and staging logic in SlackChannel, including methods to fetch file metadata, download files, and stage images securely. [1] [2] [3] [4]
  • Updated stage_channel_images to support the "slack" channel, enabling image ingestion from Slack.
  • Added parsing logic to extract image parts and captions from Slack messages, ensuring both text and images are handled correctly. [1] [2]

Configuration and documentation updates:

  • Added "slack" to the list of valid multimodal channels in configuration schema and default config, and updated related warnings and assertions. [1] [2] [3] [4]
  • Updated README.md to document required Slack OAuth scopes for image/file ingestion.

Testing and code quality:

  • Added extensive unit and integration tests for Slack image parsing, ingestion, and staging, including a mock Slack API server to verify authentication and file handling. [1] [2] [3]

Security and maintainability improvements:

  • Centralized Slack API URL construction and error sanitization to avoid leaking sensitive information and to facilitate testing. [1] [2] [3] [4] [5]

These changes collectively make Slack a first-class multimodal channel, allowing users to send images and files for processing securely and reliably.

Closes: ##329

Parse Slack image shares, stage authenticated file downloads, and document the files:read scope required for multimodal image ingestion.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Slack image ingestion: parses Slack file/files in messages, resolves url_private_download via files.info, downloads with bearer auth (30s timeout), validates and stages images (fetch_and_stage_image), wires Slack into channel image staging, updates multimodal allowlist/tests, and documents required Slack OAuth scopes (chat:write, channels:history, files:read).

Changes

Cohort / File(s) Summary
Docs & Onboarding
clients/agent-runtime/README.md, clients/agent-runtime/src/integrations/mod.rs, clients/agent-runtime/src/onboard/wizard.rs
Updated Slack setup guidance and CLI hint to require explicit OAuth scopes: chat:write, channels:history, files:read.
Channel staging dispatcher
clients/agent-runtime/src/channels/mod.rs
When msg.channel == "slack", construct Slack channel and invoke image staging flow for image parts (fetch_and_stage_image).
Slack channel implementation & tests
clients/agent-runtime/src/channels/slack.rs
Added api_base_url and api_url(); tightened HTTP/Slack ok checks and token-redacted error logging; parse image parts from file/files, validate mime; new fetch_and_stage_image() to call files.info, download with bearer auth under timeout, and call media staging; added unit and tokio integration tests with mock Slack endpoints.
Multimodal config & tests
clients/agent-runtime/src/config/schema.rs
Added "slack" to MVP_VALID_MULTIMODAL_CHANNELS, updated runtime warning text, fixtures, and tests to accept Slack as an MVP multimodal ingress channel.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Client
    participant Parser as Slack Parser
    participant SlackAPI as Slack API
    participant HTTP as HTTP Client
    participant Media as Media Staging

    Client->>Parser: parse_slack_message(payload)
    Parser->>Parser: extract file IDs, mime, captions
    Parser-->>Client: ContentPart::Image entries

    Client->>SlackAPI: fetch_and_stage_image(file_id, mime, max_bytes)
    SlackAPI->>SlackAPI: call files.info (bearer auth) to get url_private_download
    SlackAPI->>HTTP: GET url_private_download (bearer auth, 30s timeout)
    HTTP-->>SlackAPI: stream bytes
    SlackAPI->>Media: stream_validate_and_stage(bytes, mime, max_bytes)
    Media-->>SlackAPI: StagedImage
    SlackAPI-->>Client: Result<StagedImage, RejectionReason>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • feat: Slack image ingestion #329 — Implements Slack image ingestion (file parsing, staging dispatcher, API helpers, docs); directly aligns with this PR's scope.

Possibly related PRs

Suggested labels

area:rust, area:docs, risk:security

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed PR description comprehensively covers implementation details, configuration updates, tests, and security improvements with clear structure and related issue reference.
Title check ✅ Passed The title follows Conventional Commit style with 'feat:' prefix, is clear and directly summarizes the main changes (Slack image ingestion and staging tests), is well under 72 characters (60 chars), and accurately reflects the PR's primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/slack-image-ingestion-329

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

✅ Contributor Report

User: @yacosta738
Status: Passed (12/13 metrics passed)

Metric Description Value Threshold Status
PR Merge Rate PRs merged vs closed 89% >= 30%
Repo Quality Repos with ≥100 stars 0 >= 0
Positive Reactions Positive reactions received 10 >= 1
Negative Reactions Negative reactions received 0 <= 5
Account Age GitHub account age 3085 days >= 30 days
Activity Consistency Regular activity over time 108% >= 0%
Issue Engagement Issues with community engagement 0 >= 0
Code Reviews Code reviews given to others 530 >= 0
Merger Diversity Unique maintainers who merged PRs 2 >= 0
Repo History Merge Rate Merge rate in this repo 91% >= 0%
Repo History Min PRs Previous PRs in this repo 215 >= 0
Profile Completeness Profile richness (bio, followers) 90 >= 0
Suspicious Patterns Spam-like activity detection 1 N/A

Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-04-08 to 2026-04-08

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
clients/agent-runtime/src/channels/slack.rs (1)

57-72: ⚠️ Potential issue | 🟠 Major

Normalize Slack app-level error handling across auth, listen, and health.

Slack returns HTTP 200 with "ok": false for auth/scope/channel failures. get_bot_user_id(), poll_history(), and health_check() currently treat those payloads as success, so bad credentials can look healthy and listen() can spin forever with no messages. Please reuse the same ok == true validation that send() and resolve_file_download_url() already apply, and add a regression test for a 200/"ok": false response.

Based on learnings, "Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests."

Also applies to: 89-104, 367-374

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

In `@clients/agent-runtime/src/channels/slack.rs` around lines 57 - 72, The code
treats HTTP 200/"ok": false Slack responses as success; update get_bot_user_id,
poll_history, and health_check to parse the JSON response and verify
resp.get("ok").and_then(|v| v.as_bool()) == Some(true) (the same validation used
by send and resolve_file_download_url), return None or an Err when "ok" is false
and log the error message from resp.get("error"); add a regression test that
mocks a 200 with {"ok": false, "error": "..."} for auth/listen/health to assert
these methods fail/return None and that Channel trait implementations (send,
listen, health_check) exhibit consistent auth/allowlist/health semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/config/schema.rs`:
- Line 277: The new constant MVP_VALID_MULTIMODAL_CHANNELS added "slack" but
code in channels::mod (specifically where stage_channel_images() is dispatched
on msg.channel) does not check config.multimodal.allowed_channels; update the
dispatch logic to consult the multimodal allowlist first and only call
stage_channel_images() for channels present in
config.multimodal.allowed_channels (or when multimodal.enabled and the channel
is explicitly allowed). Ensure the check references the same channel identifier
values used in MVP_VALID_MULTIMODAL_CHANNELS and deny by default when the
channel is not in the allowlist.

---

Outside diff comments:
In `@clients/agent-runtime/src/channels/slack.rs`:
- Around line 57-72: The code treats HTTP 200/"ok": false Slack responses as
success; update get_bot_user_id, poll_history, and health_check to parse the
JSON response and verify resp.get("ok").and_then(|v| v.as_bool()) == Some(true)
(the same validation used by send and resolve_file_download_url), return None or
an Err when "ok" is false and log the error message from resp.get("error"); add
a regression test that mocks a 200 with {"ok": false, "error": "..."} for
auth/listen/health to assert these methods fail/return None and that Channel
trait implementations (send, listen, health_check) exhibit consistent
auth/allowlist/health semantics.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c000adf4-c7c2-418f-8429-ea8db8e5dda7

📥 Commits

Reviewing files that changed from the base of the PR and between b45ef9a and ce72d81.

📒 Files selected for processing (6)
  • clients/agent-runtime/README.md
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/channels/slack.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/onboard/wizard.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: report / Contributor Quality Report
  • GitHub Check: pr-checks
  • GitHub Check: sonar
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{md,mdx}

⚙️ CodeRabbit configuration file

**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes.
For user-facing docs, check EN/ES parity or explicitly note pending translation gaps.

Files:

  • clients/agent-runtime/README.md
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/agent-runtime/README.md
  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/onboard/wizard.rs
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/src/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency

Files:

  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/onboard/wizard.rs
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Run cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, and cargo test for code validation, or document which checks were skipped and why

Files:

  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/onboard/wizard.rs
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/channels/slack.rs
**/*.rs

⚙️ CodeRabbit configuration file

**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.

Files:

  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/onboard/wizard.rs
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/src/channels/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests

Files:

  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable

Files:

  • clients/agent-runtime/src/config/schema.rs
🧠 Learnings (5)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable

Applied to files:

  • clients/agent-runtime/README.md
  • clients/agent-runtime/src/config/schema.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks

Applied to files:

  • clients/agent-runtime/README.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths

Applied to files:

  • clients/agent-runtime/src/integrations/mod.rs
  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/agent-runtime/src/channels/mod.rs
  • clients/agent-runtime/src/config/schema.rs
  • clients/agent-runtime/src/channels/slack.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/*.rs : Run `cargo fmt --all -- --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` for code validation, or document which checks were skipped and why

Applied to files:

  • clients/agent-runtime/src/config/schema.rs
🔇 Additional comments (4)
clients/agent-runtime/README.md (1)

249-250: Slack scope docs update is accurate and aligned with ingestion behavior.

Adding files:read alongside chat:write and channels:history is consistent with the new Slack image/file ingestion flow.

clients/agent-runtime/src/onboard/wizard.rs (1)

3409-3409: Onboarding prompt now correctly reflects required Slack scopes for multimodal ingestion.

Good update—this should prevent misconfigured Slack setups when image/file ingestion is enabled.

clients/agent-runtime/src/integrations/mod.rs (1)

123-123: Integration help text is now consistent with Slack file/image ingestion requirements.

The explicit scope list improves setup clarity and aligns with the updated Slack channel capabilities.

clients/agent-runtime/src/channels/mod.rs (1)

1612-1617: Slack image staging branch is correctly integrated.

Line 1612-Line 1617 mirrors the existing channel-specific staging pattern, preserves fail-closed behavior, and correctly propagates fetch/staging errors via ImageRejectionReason.


/// Valid MVP channel names for multimodal image ingress.
const MVP_VALID_MULTIMODAL_CHANNELS: &[&str] = &["telegram", "whatsapp", "discord"];
const MVP_VALID_MULTIMODAL_CHANNELS: &[&str] = &["telegram", "whatsapp", "discord", "slack"];
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

Enforce multimodal.allowed_channels before expanding Slack support.

Adding "slack" here widens an existing policy bypass: clients/agent-runtime/src/channels/mod.rs currently dispatches stage_channel_images() on msg.channel without checking config.multimodal.allowed_channels. With multimodal.enabled = true, Slack images can now be fetched even when operators intentionally leave "slack" out of the allowlist. Please gate staging on the configured allowlist before treating Slack as supported.

As per coding guidelines, "Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable."

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

In `@clients/agent-runtime/src/config/schema.rs` at line 277, The new constant
MVP_VALID_MULTIMODAL_CHANNELS added "slack" but code in channels::mod
(specifically where stage_channel_images() is dispatched on msg.channel) does
not check config.multimodal.allowed_channels; update the dispatch logic to
consult the multimodal allowlist first and only call stage_channel_images() for
channels present in config.multimodal.allowed_channels (or when
multimodal.enabled and the channel is explicitly allowed). Ensure the check
references the same channel identifier values used in
MVP_VALID_MULTIMODAL_CHANNELS and deny by default when the channel is not in the
allowlist.

Treat Slack JSON responses with ok=false as failures in auth, history polling, and health checks, and add regression tests for those cases.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 8, 2026

Deploying corvus with  Cloudflare Pages  Cloudflare Pages

Latest commit: 352b328
Status: ✅  Deploy successful!
Preview URL: https://b156dcd1.corvus-42x.pages.dev
Branch Preview URL: https://feat-slack-image-ingestion-3.corvus-42x.pages.dev

View logs

@coderabbitai coderabbitai Bot added risk:high and removed area:rust labels Apr 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
clients/agent-runtime/src/channels/slack.rs (1)

128-131: ⚠️ Potential issue | 🟡 Minor

Inconsistent error sanitization.

poll_history logs the reqwest error directly without using sanitize_error(), unlike get_bot_user_id() and other methods. While unlikely to leak the token in practice, it's inconsistent with the defensive pattern used elsewhere.

Proposed fix
         .send()
         .await
         .map_err(|e| {
-            tracing::warn!("Slack poll error: {e}");
-            e
+            tracing::warn!("Slack poll error: {}", self.sanitize_error(&e));
+            e
         })?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/agent-runtime/src/channels/slack.rs` around lines 128 - 131, The
poll_history map_err closure logs reqwest errors raw—make it consistent with
get_bot_user_id by passing the error through sanitize_error() before logging;
update the closure in poll_history (the .map_err(|e| { tracing::warn!(...) ; e
})?) to call sanitize_error(&e) in the tracing::warn! message and return the
original error afterwards, so the token/credentials aren't exposed and behavior
matches other methods using sanitize_error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/channels/slack.rs`:
- Around line 253-265: The download request
(self.client.get(&download_url).bearer_auth(&self.bot_token).send().await) has
no timeout and can hang; wrap the send() future in a tokio::time::timeout (e.g.,
Duration::from_secs(30)) or use reqwest's per-request timeout if enabled, then
map a timeout error to the same media::ImageRejectionReason::FetchFailed and
emit a tracing::warn (use self.sanitize_error or a clear message) so
slow/unresponsive downloads fail fast; update the error mapping around the
send() and any subsequent body reads to handle elapsed timeouts consistently.
- Around line 226-233: Replace the duplicated Slack error-extraction logic with
the existing helper Self::slack_api_error(payload): call
Self::slack_api_error(&payload) to get the api error string (or default), use
that value in the tracing::warn message, and keep returning
media::ImageRejectionReason::FetchFailed; update the block that currently
manually reads payload.get("error") to delegate to Self::slack_api_error for
consistency and single-source parsing.

---

Outside diff comments:
In `@clients/agent-runtime/src/channels/slack.rs`:
- Around line 128-131: The poll_history map_err closure logs reqwest errors
raw—make it consistent with get_bot_user_id by passing the error through
sanitize_error() before logging; update the closure in poll_history (the
.map_err(|e| { tracing::warn!(...) ; e })?) to call sanitize_error(&e) in the
tracing::warn! message and return the original error afterwards, so the
token/credentials aren't exposed and behavior matches other methods using
sanitize_error.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0518d123-8411-4ccb-9884-a540afa2675a

📥 Commits

Reviewing files that changed from the base of the PR and between ce72d81 and 5b08494.

📒 Files selected for processing (1)
  • clients/agent-runtime/src/channels/slack.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sonar
  • GitHub Check: pr-checks
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (5)
clients/agent-runtime/src/channels/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests

Files:

  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/src/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency

Files:

  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Run cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, and cargo test for code validation, or document which checks were skipped and why

Files:

  • clients/agent-runtime/src/channels/slack.rs
**/*.rs

⚙️ CodeRabbit configuration file

**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.

Files:

  • clients/agent-runtime/src/channels/slack.rs
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/agent-runtime/src/channels/slack.rs
🧠 Learnings (1)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/agent-runtime/src/channels/slack.rs
🔇 Additional comments (10)
clients/agent-runtime/src/channels/slack.rs (10)

1-54: LGTM!

The new api_base_url field with test-only constructor is a clean pattern for mocking. The sanitize_error() helper properly redacts the bot token from error messages, and slack_api_error() avoids unnecessary allocations by returning &str.


63-104: LGTM!

Robust layered error handling: HTTP status → JSON parse → API ok field. Token redaction via sanitize_error() is properly applied.


154-194: LGTM!

Correctly handles both files array and single file field from Slack's API. MIME validation delegates to AllowedImageMime::from_mime_str() which enforces the exact allowlist (jpeg, png, webp). The empty file_id check is good defensive coding.


304-315: Asymmetric parts handling for text-only vs image messages.

When image_parts is empty, parts becomes an empty Vec, so text-only messages have no ContentPart::Text in parts (only in content). Image messages get both content and parts populated. This asymmetry appears intentional for backward compatibility, but ensure downstream consumers handle both patterns.


340-375: LGTM!

The send() method correctly uses api_url() and checks both HTTP status and API ok field.


414-454: LGTM!

The health_check() method follows the hardened pattern: HTTP status check → JSON parse → API ok validation. Token redaction is correctly applied.


729-791: LGTM!

Well-structured mock server that validates the two-step metadata → download flow. The Connection: close header ensures clean socket termination.


840-888: LGTM!

Excellent test coverage: verifies bearer auth on both requests, validates staged image properties (MIME, size, content, prefix), and confirms cleanup removes the temp file.


890-959: LGTM!

These regression tests verify that Slack API-level errors (ok: false) are correctly propagated. Good coverage of get_bot_user_id, poll_history, and health_check.


576-659: LGTM!

Thorough test coverage for image parsing: files array with caption, and file_share subtype with empty text. Both verify the ContentPart::Image structure correctly.

Comment thread clients/agent-runtime/src/channels/slack.rs
Comment thread clients/agent-runtime/src/channels/slack.rs Outdated
Add a timeout around Slack file downloads, sanitize polling transport errors, and reuse the shared Slack API error helper for metadata failures.
@coderabbitai coderabbitai Bot added area:rust and removed risk:high labels Apr 8, 2026
@yacosta738 yacosta738 changed the title fest: Add image ingestion and update staging tests for Slack feat: Add image ingestion and update staging tests for Slack Apr 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/channels/slack.rs`:
- Around line 202-208: The call to Slack's files.info (the HTTP request built in
the client.get(...).bearer_auth(...).query(&[("file", file_id)]).send().await)
is executed before the 30s timeout started in resolve_file_download_url(), so a
stalled metadata call can hang ingestion; move or wrap the files.info request
and its response handling inside the same timeout used by
resolve_file_download_url() (i.e., ensure the await for files.info is performed
within the tokio::time::timeout span used by resolve_file_download_url()), avoid
unnecessary cloning of file_id or bot_token when passing into the timeout
closure, and propagate any timeout error upward so the Slack image ingestion
path is bounded end-to-end.
- Around line 68-80: The auth.test bot-ID lookup currently swallows
transport/auth errors by calling .ok() and returning None; change the auth.test
call in Slack client so it returns a Result (propagate the transport/auth error
using the sanitize_error message rather than collapsing to None) and validate
the response payload to return Err if user_id is missing or malformed rather
than Ok(None). Update the caller (listen()) to abort/return an error when the
bot-ID lookup yields None (do not convert None to empty string), so self-message
filtering remains enabled on failures; refer to the auth.test request block that
uses
self.client.get(self.api_url("auth.test")).bearer_auth(&self.bot_token).send().await.inspect_err(...).ok()?
and the listen() method where the bot ID is used.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d1081e3-c1b2-4967-a215-c8be47df8ba9

📥 Commits

Reviewing files that changed from the base of the PR and between 5b08494 and 352b328.

📒 Files selected for processing (1)
  • clients/agent-runtime/src/channels/slack.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pr-checks
  • GitHub Check: sonar
  • GitHub Check: submit-gradle
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (5)
clients/agent-runtime/src/channels/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests

Files:

  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/src/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency

Files:

  • clients/agent-runtime/src/channels/slack.rs
clients/agent-runtime/**/*.rs

📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)

Run cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, and cargo test for code validation, or document which checks were skipped and why

Files:

  • clients/agent-runtime/src/channels/slack.rs
**/*.rs

⚙️ CodeRabbit configuration file

**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.

Files:

  • clients/agent-runtime/src/channels/slack.rs
**/*

⚙️ CodeRabbit configuration file

**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.

Files:

  • clients/agent-runtime/src/channels/slack.rs
🧠 Learnings (2)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests

Applied to files:

  • clients/agent-runtime/src/channels/slack.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths

Applied to files:

  • clients/agent-runtime/src/channels/slack.rs
🔇 Additional comments (1)
clients/agent-runtime/src/channels/slack.rs (1)

210-213: The suggested fix has incorrect syntax; &str has no .get() method for byte ranges.

While UTF-8 panic on &file_id[..file_id.len().min(8)] is a valid code smell, Slack file IDs are always ASCII (e.g., "F123" format), so the practical risk is minimal. If defensive truncation is desired, use .chars().take(8).collect::<String>() instead.

Comment on lines +68 to +80
let response = self
.client
.get("https://slack.com/api/auth.test")
.get(self.api_url("auth.test"))
.bearer_auth(&self.bot_token)
.send()
.await
.ok()?
.inspect_err(|err| {
tracing::warn!(
"Slack auth.test request failed: {}",
self.sanitize_error(err)
);
})
.ok()?;
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

Don't collapse bot-ID lookup failures into None.

Line 80 and Lines 96-101 make transport/auth failures indistinguishable from “no user_id present”. listen() still converts that to "", which disables self-message filtering and can let the bot consume its own posts after an auth.test failure. Please surface an error here, or explicitly abort listen() when this returns None.

Based on learnings: "Implement Channel trait in src/channels/ with consistent send, listen, and health_check semantics and cover auth/allowlist/health behavior with tests".

Also applies to: 96-101

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

In `@clients/agent-runtime/src/channels/slack.rs` around lines 68 - 80, The
auth.test bot-ID lookup currently swallows transport/auth errors by calling
.ok() and returning None; change the auth.test call in Slack client so it
returns a Result (propagate the transport/auth error using the sanitize_error
message rather than collapsing to None) and validate the response payload to
return Err if user_id is missing or malformed rather than Ok(None). Update the
caller (listen()) to abort/return an error when the bot-ID lookup yields None
(do not convert None to empty string), so self-message filtering remains enabled
on failures; refer to the auth.test request block that uses
self.client.get(self.api_url("auth.test")).bearer_auth(&self.bot_token).send().await.inspect_err(...).ok()?
and the listen() method where the bot ID is used.

Comment on lines +202 to +208
let response = self
.client
.get(self.api_url("files.info"))
.bearer_auth(&self.bot_token)
.query(&[("file", file_id)])
.send()
.await
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

Put files.info under the timeout too.

Line 251 starts the 30s budget only after resolve_file_download_url() has already awaited. A stalled metadata call on Lines 202-208 can still hang Slack image ingestion indefinitely, so this path is not actually bounded end-to-end.

Suggested fix
     pub async fn fetch_and_stage_image(
         &self,
         file_id: &str,
         declared_mime: Option<&str>,
         max_bytes: u64,
     ) -> Result<media::StagedImage, media::ImageRejectionReason> {
-        let download_url = self.resolve_file_download_url(file_id).await?;
         tokio::time::timeout(SLACK_DOWNLOAD_TIMEOUT, async {
+            let download_url = self.resolve_file_download_url(file_id).await?;
             let response = self
                 .client
                 .get(&download_url)
                 .bearer_auth(&self.bot_token)
                 .send()
@@
         })
         .await
         .map_err(|_| {
             tracing::warn!(
-                "Slack file download timed out after {}s",
+                "Slack file fetch timed out after {}s",
                 SLACK_DOWNLOAD_TIMEOUT.as_secs()
             );
             media::ImageRejectionReason::FetchFailed
         })?
     }

As per coding guidelines: "Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency."

Also applies to: 251-283

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

In `@clients/agent-runtime/src/channels/slack.rs` around lines 202 - 208, The call
to Slack's files.info (the HTTP request built in the
client.get(...).bearer_auth(...).query(&[("file", file_id)]).send().await) is
executed before the 30s timeout started in resolve_file_download_url(), so a
stalled metadata call can hang ingestion; move or wrap the files.info request
and its response handling inside the same timeout used by
resolve_file_download_url() (i.e., ensure the await for files.info is performed
within the tokio::time::timeout span used by resolve_file_download_url()), avoid
unnecessary cloning of file_id or bot_token when passing into the timeout
closure, and propagate any timeout error upward so the Slack image ingestion
path is bounded end-to-end.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 8, 2026

@yacosta738 yacosta738 merged commit f752038 into main Apr 8, 2026
14 of 15 checks passed
@yacosta738 yacosta738 deleted the feat/slack-image-ingestion-329 branch April 8, 2026 19:37
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.

feat: Slack image ingestion

1 participant