Skip to content
This repository was archived by the owner on Apr 12, 2026. It is now read-only.

fix: Implement configurable timeout settings for MCP client and bridge operations#151

Open
nicolas-wyrwa-celebrate wants to merge 4 commits into
tuannvm:mainfrom
kartenmacherei:bugfix/DI-5665_timeout_configurations
Open

fix: Implement configurable timeout settings for MCP client and bridge operations#151
nicolas-wyrwa-celebrate wants to merge 4 commits into
tuannvm:mainfrom
kartenmacherei:bugfix/DI-5665_timeout_configurations

Conversation

@nicolas-wyrwa-celebrate
Copy link
Copy Markdown
Contributor

@nicolas-wyrwa-celebrate nicolas-wyrwa-celebrate commented Nov 19, 2025

This pull request improves configuration-driven timeout handling and error reporting throughout the MCP client initialization, LLM bridge, and Slack client tool processing. The changes ensure that timeouts are consistently read from configuration, provide more specific error messages on timeout, and fall back to sensible defaults if configuration is missing or invalid.

Timeout configuration improvements:

  • MCP client initialization in cmd/main.go now determines the initialization timeout by checking for a server-specific value, then falling back to a global configuration value (cfg.Timeouts.MCPInitTimeout), and finally to a hardcoded default if neither is set. The timeout is now passed as a time.Duration for clarity and precision. [1] [2] [3] [4]
  • In internal/handlers/llm_mcp_bridge.go, both CallLLMAgent and CallLLM now use a configurable BridgeOperationTimeout from the config, with a default fallback if parsing fails. [1] [2]
  • In internal/slack/client.go, the tool processing timeout is now configurable via ToolProcessingTimeout in the config, with a fallback to a default if parsing fails.

Improved error handling and messaging:

  • When a timeout occurs in the LLM bridge or Slack client tool processing, the error message now specifically indicates a timeout and suggests increasing the relevant timeout configuration. This provides clearer guidance to users and operators. [1] [2] [3]

Summary by CodeRabbit

  • Improvements
    • Added configurable timeouts with server- and global-level selection for initialization, bridge operations, and tool processing; sensible defaults used on unset/invalid values.
    • Improved timeout parsing, validation, logging, and propagation across initialization and runtime flows.
    • Enhanced error responses to surface specific timeout codes/messages for bridge, LLM/agent calls, and tool processing to guide remediation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 19, 2025

Walkthrough

Thread global configuration through MCP initialization and replace multiple hardcoded timeouts with configurable durations (MCP init, bridge operations, Slack tool processing). Update timeout parameter types to time.Duration, propagate durations, and add timeout-specific error mapping and logging.

Changes

Cohort / File(s) Summary
MCP Server Initialization
cmd/main.go
Thread cfg *config.Config into processSingleMCPServer and initializeMCPClients; changed initializeMCPClientInstance to accept timeout time.Duration; compute server/global init timeout with parsing/fallback (5s); propagate duration and enrich timeout-related error context/logging.
LLM Bridge Timeouts & Errors
internal/handlers/llm_mcp_bridge.go
Replace fixed 3m timeouts with b.cfg.Timeouts.BridgeOperationTimeout (3m fallback); use parsed bridgeTimeout in CallLLMAgent, CallLLM, and chat paths; detect deadline exceeded and map to bridge_operation_timeout with explanatory messages; add error detection imports.
Slack Tool Processing Timeout
internal/slack/client.go
Replace fixed 1m tool processing timeout with c.cfg.Timeouts.ToolProcessingTimeout (1m fallback); parse and log invalid values; use parsed timeout for tool execution and re-prompt flows; detect deadline exceeded and return explicit timeout error messages.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant CLI as cmd/main
    participant MCP as MCP Init
    participant Client as mcp.Client
    CLI->>MCP: processSingleMCPServer(cfg, serverConf)
    MCP->>Client: initializeMCPClientInstance(timeout: time.Duration)
    Note right of Client `#e6f4ea`: uses context.WithTimeout(timeout)
    Client-->>MCP: success / context.DeadlineExceeded
    MCP-->>CLI: register client or record failed server
Loading
sequenceDiagram
    autonumber
    participant Slack as Slack client
    participant Bridge as LLM Bridge
    participant LLM as LLM/Agent
    Slack->>Bridge: CallLLM / CallLLMAgent (bridgeTimeout)
    Bridge->>LLM: request (with bridgeTimeout)
    alt success
      LLM-->>Bridge: response
      Bridge-->>Slack: processed response
    else timeout
      LLM-->>Bridge: context.DeadlineExceeded
      Bridge-->>Slack: error(code: bridge_operation_timeout)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • cfg propagation in cmd/main.go call chains and updated call sites.
    • Correct parsing and construction of time.Duration values and fallbacks.
    • Consistent detection/mapping of context.DeadlineExceeded across files.
    • Error/ log message clarity and any sensitive data in contexts.

Possibly related PRs

Suggested reviewers

  • tuannvm

Poem

🐰
I hopped through configs, tiny and spry,
Replaced hardcoded waits with timeouts that fly.
Servers now start with durations precise,
Bridges and tools timeout with clearer advice.
Hop on — configurable fields make things spry! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main change: implementing configurable timeout settings across MCP client initialization, LLM bridge, and Slack client operations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Copy Markdown

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

🧹 Nitpick comments (6)
internal/slack/client.go (2)

670-676: Configurable tool processing timeout is correct; consider guarding invalid/edge values.

ToolProcessingTimeout is parsed and falls back cleanly to 1 minute on parse error, which matches the PR intent. You might optionally harden this by:

  • Logging when time.ParseDuration fails so misconfigured values are visible at runtime.
  • Treating timeout <= 0 as invalid and falling back to the default to avoid accidental “instant timeout” configs.

705-710: Timeout‑specific tool error message works; consider more robust timeout detection.

The new branch that surfaces a clearer “processing the tool took too long…increase the toolProcessingTimeout” message is helpful. Since you’re matching on strings.Contains(err.Error(), "context deadline exceeded"), consider (now or later) using errors.Is(err, context.DeadlineExceeded) first, then falling back to the string check for wrapped errors, to make the detection less brittle.

internal/handlers/llm_mcp_bridge.go (2)

542-549: Bridge timeout parsing and defaults look good; consider centralizing and validating.

Using BridgeOperationTimeout with a 3‑minute fallback in both CallLLMAgent and CallLLM matches the desired behavior. To reduce duplication and edge‑case risk, you could:

  • Factor this into a small helper (e.g., b.getBridgeTimeout()), including the default.
  • Log when parsing fails, and optionally treat non‑positive durations as invalid and fall back to the default.

This would keep bridge timeout behavior consistent as config evolves.

Also applies to: 590-596


575-583: Timeout‑specific error codes/messages are a nice improvement; consider using typed timeout checks.

Mapping deadline‑exceeded errors to bridge_operation_timeout with user‑facing guidance about bridgeOperationTimeout is a solid UX improvement. Similar to the Slack client, you might want to:

  • Prefer errors.Is(err, context.DeadlineExceeded) before falling back to a substring check on "context deadline exceeded" so wrapped errors are handled more reliably.
  • Optionally add a brief comment or test to ensure these codes are what downstream Slack handlers expect, since they’re now domain Slack errors (WrapSlackError).

Also applies to: 663-671

cmd/main.go (2)

303-316: MCP init timeout selection is correct; consider logging invalid MCPInitTimeout values.

The precedence (per‑server InitializeTimeoutSeconds → global Timeouts.MCPInitTimeout → 5s default) is sensible and matches the PR description. Two small robustness tweaks to consider:

  • If cfg.Timeouts.MCPInitTimeout is set but time.ParseDuration fails, log a warning so mis‑typed config doesn’t silently fall back to 5s.
  • Optionally treat negative or zero values (from either source) as invalid and revert to the default to avoid accidental “instant timeout” configs.

This would also help when “checking server initialization timeouts when debugging MCP issues.” Based on learnings.

Also applies to: 319-319


514-518: initializeMCPClientInstance behavior looks good; pointer timeout could be passed by value.

Using context.WithTimeout around client.Initialize and annotating deadline‑exceeded vs “file already closed” errors preserves existing behavior while working with the new time.Duration‑based timeout. Given you never mutate the timeout, you could simplify the signature to take timeout time.Duration and pass it directly (no pointer), which would slightly clean up call sites and logging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19bb934 and dee2d8c.

📒 Files selected for processing (3)
  • cmd/main.go (4 hunks)
  • internal/handlers/llm_mcp_bridge.go (3 hunks)
  • internal/slack/client.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
internal/slack/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Slack client implementation and message formatting should be in internal/slack/

Files:

  • internal/slack/client.go
internal/handlers/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Tool handlers and LLM-MCP bridge should be implemented in internal/handlers/

Files:

  • internal/handlers/llm_mcp_bridge.go
cmd/main.go

📄 CodeRabbit inference engine (CLAUDE.md)

Application entry point and initialization should be in cmd/main.go

Files:

  • cmd/main.go
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Graceful degradation should occur when MCP servers fail
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues

Applied to files:

  • internal/slack/client.go
  • internal/handlers/llm_mcp_bridge.go
  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/handlers/**/*.go : Tool handlers and LLM-MCP bridge should be implemented in `internal/handlers/`

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : Update provider constants when adding a new LLM provider

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : LangChain gateway should provide a unified interface for LLM providers

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : Support both native tools and system prompt-based tools in LLM provider code

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Component-specific loggers should be used for MCP servers

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : MCP client implementations (SSE, HTTP, stdio) should be in `internal/mcp/`

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Enable `--mcpdebug` for MCP client logs when debugging MCP issues

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*_test.go : Integration tests should be written for MCP client functionality

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Failed servers should be logged but must not crash the application

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Tool discovery should happen at startup with allow/block lists

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-10T21:48:54.979Z
Learnt from: AdamShannag
Repo: tuannvm/slack-mcp-client PR: 64
File: internal/mcp/sseClient.go:56-58
Timestamp: 2025-07-10T21:48:54.979Z
Learning: The mcp-go package handles client initialization internally when Start() is called on an SSE client. Explicit initialization calls are not needed in wrapper Start() methods - only when creating new client instances like in reconnect scenarios.

Applied to files:

  • cmd/main.go
🧬 Code graph analysis (2)
internal/handlers/llm_mcp_bridge.go (1)
internal/common/errors/app_errors.go (1)
  • WrapSlackError (213-215)
cmd/main.go (3)
internal/config/config.go (1)
  • Config (25-38)
internal/slack/client.go (1)
  • Client (29-40)
internal/mcp/client.go (1)
  • Client (29-38)
🔇 Additional comments (1)
cmd/main.go (1)

224-235: Threading cfg into processSingleMCPServer keeps MCP init consistent with global timeouts.

Passing cfg into processSingleMCPServer so each server can honor both per‑server and global timeout settings aligns well with the timeout configuration model and maintains graceful degradation when some servers fail to initialize. Based on learnings.

Also applies to: 265-265

Copy link
Copy Markdown

@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

🧹 Nitpick comments (3)
internal/handlers/llm_mcp_bridge.go (1)

577-585: Timeout‑specific error mapping is good; consider centralizing timeout detection

The new mapping of deadline errors to bridge_operation_timeout with user‑actionable messages (“Consider increasing the bridgeOperationTimeout.”) is a nice improvement in both agent and chat paths.

Given the duplication:

  • The condition errors.Is(err, context.DeadlineExceeded) || strings.Contains(err.Error(), "context deadline exceeded") appears twice; you could extract a small helper like isBridgeTimeout(err error) bool on the bridge to keep this logic in one place.
  • That helper could also be reused elsewhere (e.g. Slack client) if you want consistent timeout detection across components.

Functionally this is fine as‑is; the suggestion is mainly about DRY and consistency.

Also applies to: 666-673

cmd/main.go (2)

514-523: New initializeMCPClientInstance signature is cleaner; minor doc comment nit

Switching initializeMCPClientInstance to accept a time.Duration is a nice simplification and aligns well with the new timeout calculation logic. The logging of timeout.Seconds() also makes the effective timeout easy to understand in logs.

One small nit: the function header comments above this definition are duplicated; you can safely remove the extra pair to avoid confusion.


304-318: Tighten timeout validation to prevent context cancellation surprises

The timeout precedence (server-specific InitializeTimeoutSeconds → global Timeouts.MCPInitTimeout → 5s default) is sound. However, three robustness improvements would prevent edge cases:

  1. Empty MCPInitTimeout: If cfg.Timeouts.MCPInitTimeout is empty, time.ParseDuration errors and the warning message says "Invalid" when empty is simply unset. Skip the parse call and silently use the default when the string is empty.

  2. Guard against ≤0 durations: After obtaining InitializeTimeoutSeconds (line 309) and mcpInitTimeout (line 313), validate that both are > 0. Per Go docs, context.WithTimeout with d ≤ 0 creates an already-cancelled context, which can be surprising if misconfigured.

  3. Parse global timeout once: The global MCPInitTimeout is parsed inside the server loop. Parse it once before the loop and reuse it to avoid repeated time.ParseDuration calls per server.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dee2d8c and d4efcde.

📒 Files selected for processing (3)
  • cmd/main.go (4 hunks)
  • internal/handlers/llm_mcp_bridge.go (4 hunks)
  • internal/slack/client.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/slack/client.go
🧰 Additional context used
📓 Path-based instructions (2)
internal/handlers/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Tool handlers and LLM-MCP bridge should be implemented in internal/handlers/

Files:

  • internal/handlers/llm_mcp_bridge.go
cmd/main.go

📄 CodeRabbit inference engine (CLAUDE.md)

Application entry point and initialization should be in cmd/main.go

Files:

  • cmd/main.go
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/handlers/**/*.go : Tool handlers and LLM-MCP bridge should be implemented in `internal/handlers/`

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : Update provider constants when adding a new LLM provider

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : LangChain gateway should provide a unified interface for LLM providers

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : Support both native tools and system prompt-based tools in LLM provider code

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Component-specific loggers should be used for MCP servers

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Enable `--mcpdebug` for MCP client logs when debugging MCP issues

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : MCP client implementations (SSE, HTTP, stdio) should be in `internal/mcp/`

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Failed servers should be logged but must not crash the application

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*_test.go : Integration tests should be written for MCP client functionality

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-10T21:48:54.979Z
Learnt from: AdamShannag
Repo: tuannvm/slack-mcp-client PR: 64
File: internal/mcp/sseClient.go:56-58
Timestamp: 2025-07-10T21:48:54.979Z
Learning: The mcp-go package handles client initialization internally when Start() is called on an SSE client. Explicit initialization calls are not needed in wrapper Start() methods - only when creating new client instances like in reconnect scenarios.

Applied to files:

  • cmd/main.go
🧬 Code graph analysis (2)
internal/handlers/llm_mcp_bridge.go (2)
internal/common/errors/errors.go (1)
  • Is (145-147)
internal/common/errors/app_errors.go (1)
  • WrapSlackError (213-215)
cmd/main.go (2)
internal/config/config.go (1)
  • Config (25-38)
internal/mcp/client.go (1)
  • Client (29-38)
🔇 Additional comments (2)
internal/handlers/llm_mcp_bridge.go (1)

5-15: Use of errors import for timeout classification looks appropriate

The new errors import is used to classify context.DeadlineExceeded in the bridge methods; this is consistent with Go idioms and keeps timeout handling explicit. No issues here.

cmd/main.go (1)

217-235: Config propagation into processSingleMCPServer looks consistent

Passing cfg into processSingleMCPServer and threading it through the call in initializeMCPClients is straightforward and keeps timeout logic close to per‑server config. No functional concerns here.

Also applies to: 257-266

Comment on lines 543 to 551
func (b *LLMMCPBridge) CallLLMAgent(userDisplayName, systemPrompt, prompt, contextHistory string, callbackHandler callbacks.Handler) (string, error) {
// Create a context with an appropriate timeout
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
// Create a context with the configured bridgeTimeout
bridgeTimeout, err := time.ParseDuration(b.cfg.Timeouts.BridgeOperationTimeout)
if err != nil {
b.logger.WarnKV("Invalid bridge operation timeout, using default of 3 minutes", "configured_value", b.cfg.Timeouts.BridgeOperationTimeout, "error", err)
bridgeTimeout = 3 * time.Minute // fallback to default if parsing fails
}
ctx, cancel := context.WithTimeout(context.Background(), bridgeTimeout)
defer cancel()
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Nov 19, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Bridge timeout parsing is correct but could be more robust and DRY

The BridgeOperationTimeout parsing with a 3m fallback is functionally sound in both CallLLMAgent and CallLLM. A couple of small improvements to consider:

  • Treat an empty BridgeOperationTimeout as “not configured” and silently use the default instead of logging a warning on every call when it’s unset.
  • After parsing, guard against non‑positive durations (e.g. 0s, negative values) and fall back to the default to avoid creating immediately‑expired contexts.
  • Factor this parsing into a small helper (e.g. getBridgeTimeout() on LLMMCPBridge) so CallLLMAgent and CallLLM share identical logic and logging.

These don’t block correctness but would reduce log noise and configuration footguns.

Please double‑check the desired behavior for zero/negative timeouts.bridgeOperationTimeout in your configuration docs and time.ParseDuration semantics.

Also applies to: 592-599


🌐 Web query:

In Go, how does `context.WithTimeout` behave when given a non-positive timeout, and what error does it return on expiry?

💡 Result:

Short answer: WithTimeout calls WithDeadline(parent, time.Now().Add(timeout)). If timeout is non‑positive the deadline is already past, so the returned child context is already expired (Done is closed). When it expires its Err() is context.DeadlineExceeded (i.e. "context deadline exceeded"). [1][2]

Sources:

  • pkg: context.WithTimeout == WithDeadline(parent, time.Now().Add(timeout)). [1]
  • example showing ctx.Done() / ctx.Err() prints "context deadline exceeded". [2]

🏁 Script executed:

#!/bin/bash
# Examine both timeout parsing locations
echo "=== Lines 543-551 (CallLLMAgent) ==="
sed -n '543,551p' internal/handlers/llm_mcp_bridge.go

echo -e "\n=== Lines 592-599 (CallLLM - implied) ==="
sed -n '592,599p' internal/handlers/llm_mcp_bridge.go

echo -e "\n=== Check for existing timeout helper functions ==="
rg -n "BridgeOperationTimeout|bridgeTimeout|getTimeout|ParseDuration" internal/handlers/llm_mcp_bridge.go -C2

echo -e "\n=== Config structure search ==="
rg -n "type.*Timeouts|BridgeOperationTimeout" internal/ -C3

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Examine both timeout parsing locations
echo "=== Lines 543-551 (CallLLMAgent) ==="
sed -n '543,551p' internal/handlers/llm_mcp_bridge.go

echo -e "\n=== Lines 592-599 (CallLLM - implied) ==="
sed -n '592,599p' internal/handlers/llm_mcp_bridge.go

echo -e "\n=== Check for existing timeout helper functions ==="
rg -n "BridgeOperationTimeout|bridgeTimeout|getTimeout|ParseDuration" internal/handlers/llm_mcp_bridge.go -C2

echo -e "\n=== Config structure search ==="
rg -n "type.*Timeouts|BridgeOperationTimeout" internal/ -C3

echo -e "\n=== Count CallLLMAgent and CallLLM invocations ==="
rg -n "\.CallLLMAgent\(|\.CallLLM\(" --type=go | head -20

Length of output: 6394


Extract timeout parsing to helper method and guard against non-positive durations

The identical parsing logic at both callsites (543–551, 592–599) should be extracted into a helper (e.g., getBridgeTimeout() on LLMMCPBridge) to follow DRY and ensure consistent behavior. Additionally, guard against non-positive durations—if timeout is non‑positive the deadline is already past, so the returned child context is already expired. Since time.ParseDuration("0s") and time.ParseDuration("-5s") parse successfully without error, the current code does not catch these misconfigurations. The config layer already defaults BridgeOperationTimeout to "3m" when empty (internal/config/config.go:361–362), so the warning on parse failure is only triggered by user misconfiguration; silently use the fallback in that case and add a duration > 0 check to prevent misconfigured timeouts from creating immediately-expired contexts.

🤖 Prompt for AI Agents
In internal/handlers/llm_mcp_bridge.go around lines 543–551 (and the similar
block at 592–599), extract the timeout parsing/fallback logic into a new method
on LLMMCPBridge (e.g., func (b *LLMMCPBridge) getBridgeTimeout() time.Duration)
that: parses b.cfg.Timeouts.BridgeOperationTimeout, logs a warning and returns
the hardcoded 3*time.Minute fallback on parse error, and additionally checks
that the parsed duration is > 0 (log a warning and return the fallback if not);
then replace both callsites to call getBridgeTimeout() and create the context
with context.WithTimeout(context.Background(), timeout). Ensure logging includes
the configured value and error when parse fails and do not return an
immediately-expired duration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tuannvm for me it was not super clear, which timeout parameter is supposed to define which timeout. I used the bridge operation parameter here, because it's both in the bridge file. But I think the non-agent completion could also be the response processing Timeout

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

…e operations

Signed-off-by: Nicolas Wyrwa <nicolas.wyrwa@celebrate.company>
Signed-off-by: Nicolas Wyrwa <nicolas.wyrwa@celebrate.company>
Signed-off-by: Nicolas Wyrwa <nicolas.wyrwa@celebrate.company>
Signed-off-by: Nicolas Wyrwa <nicolas.wyrwa@celebrate.company>
@nicolas-wyrwa-celebrate nicolas-wyrwa-celebrate force-pushed the bugfix/DI-5665_timeout_configurations branch from d4efcde to 3e2a055 Compare November 19, 2025 09:20
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
internal/handlers/llm_mcp_bridge.go (1)

543-551: Extract duplicate timeout parsing to helper method.

The identical timeout parsing logic appears in both CallLLMAgent (lines 543–551) and CallLLM (lines 592–599). This should be extracted into a helper method like getBridgeTimeout() on LLMMCPBridge to eliminate duplication and ensure consistent behavior. Additionally, the helper should guard against non-positive durations (see next comment).

Apply this refactor:

+// getBridgeTimeout parses and returns the bridge operation timeout from config
+// with validation and fallback to 3 minutes
+func (b *LLMMCPBridge) getBridgeTimeout() time.Duration {
+	timeout, err := time.ParseDuration(b.cfg.Timeouts.BridgeOperationTimeout)
+	if err != nil || timeout <= 0 {
+		if err != nil {
+			b.logger.WarnKV("Invalid bridge operation timeout, using default of 3 minutes", "configured_value", b.cfg.Timeouts.BridgeOperationTimeout, "error", err)
+		} else {
+			b.logger.WarnKV("Non-positive bridge operation timeout, using default of 3 minutes", "configured_value", b.cfg.Timeouts.BridgeOperationTimeout)
+		}
+		return 3 * time.Minute
+	}
+	return timeout
+}
+
 func (b *LLMMCPBridge) CallLLMAgent(userDisplayName, systemPrompt, prompt, contextHistory string, callbackHandler callbacks.Handler) (string, error) {
 	// Create a context with the configured bridgeTimeout
-	bridgeTimeout, err := time.ParseDuration(b.cfg.Timeouts.BridgeOperationTimeout)
-	if err != nil {
-		b.logger.WarnKV("Invalid bridge operation timeout, using default of 3 minutes", "configured_value", b.cfg.Timeouts.BridgeOperationTimeout, "error", err)
-		bridgeTimeout = 3 * time.Minute // fallback to default if parsing fails
-	}
-	ctx, cancel := context.WithTimeout(context.Background(), bridgeTimeout)
+	ctx, cancel := context.WithTimeout(context.Background(), b.getBridgeTimeout())
 	defer cancel()

Apply the same simplification to CallLLM at lines 592–599.

Also applies to: 592-599

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4efcde and 3e2a055.

📒 Files selected for processing (3)
  • cmd/main.go (4 hunks)
  • internal/handlers/llm_mcp_bridge.go (4 hunks)
  • internal/slack/client.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
internal/slack/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Slack client implementation and message formatting should be in internal/slack/

Files:

  • internal/slack/client.go
internal/handlers/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Tool handlers and LLM-MCP bridge should be implemented in internal/handlers/

Files:

  • internal/handlers/llm_mcp_bridge.go
cmd/main.go

📄 CodeRabbit inference engine (CLAUDE.md)

Application entry point and initialization should be in cmd/main.go

Files:

  • cmd/main.go
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Check server initialization timeouts when debugging MCP issues

Applied to files:

  • internal/slack/client.go
  • internal/handlers/llm_mcp_bridge.go
  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/monitoring/**/*.go : Prometheus metrics should include tool invocation counters with error rates and LLM token usage histograms by model and type

Applied to files:

  • internal/slack/client.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/handlers/**/*.go : Tool handlers and LLM-MCP bridge should be implemented in `internal/handlers/`

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : LangChain gateway should provide a unified interface for LLM providers

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/llm/**/*.go : Support both native tools and system prompt-based tools in LLM provider code

Applied to files:

  • internal/handlers/llm_mcp_bridge.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Component-specific loggers should be used for MCP servers

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : MCP client implementations (SSE, HTTP, stdio) should be in `internal/mcp/`

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Enable `--mcpdebug` for MCP client logs when debugging MCP issues

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*_test.go : Integration tests should be written for MCP client functionality

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-20T02:54:03.119Z
Learnt from: CR
Repo: tuannvm/slack-mcp-client PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T02:54:03.119Z
Learning: Applies to internal/mcp/**/*.go : Failed servers should be logged but must not crash the application

Applied to files:

  • cmd/main.go
📚 Learning: 2025-07-10T21:48:54.979Z
Learnt from: AdamShannag
Repo: tuannvm/slack-mcp-client PR: 64
File: internal/mcp/sseClient.go:56-58
Timestamp: 2025-07-10T21:48:54.979Z
Learning: The mcp-go package handles client initialization internally when Start() is called on an SSE client. Explicit initialization calls are not needed in wrapper Start() methods - only when creating new client instances like in reconnect scenarios.

Applied to files:

  • cmd/main.go
🧬 Code graph analysis (2)
internal/handlers/llm_mcp_bridge.go (1)
internal/common/errors/app_errors.go (1)
  • WrapSlackError (213-215)
cmd/main.go (3)
internal/config/config.go (1)
  • Config (25-38)
internal/slack/client.go (1)
  • Client (30-41)
internal/mcp/client.go (1)
  • Client (29-38)
⏰ 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). (1)
  • GitHub Check: Security Scan
🔇 Additional comments (3)
internal/slack/client.go (1)

707-712: Timeout error handling is comprehensive and user-friendly.

The dual check using both errors.Is(err, context.DeadlineExceeded) and string matching is appropriate for catching wrapped errors and different error representations. The specific error message clearly indicates a timeout and suggests the relevant configuration parameter.

internal/handlers/llm_mcp_bridge.go (1)

577-584: Timeout-specific error handling is well-implemented.

The timeout detection and error mapping in both CallLLMAgent and CallLLM is comprehensive—using both errors.Is and string matching to catch wrapped errors. The specialized error code bridge_operation_timeout and the actionable error messages provide clear guidance to users.

Also applies to: 666-673

cmd/main.go (1)

519-552: Refactored signature and error handling improve clarity.

The change from timeoutSeconds *int to timeout time.Duration is a clean improvement that aligns with Go idioms and simplifies the context creation. The enhanced error handling at lines 535–544 provides helpful structured data for specific failure modes (timeout exceeded, process exited), making debugging easier.

Comment thread cmd/main.go
Comment on lines +304 to +318
// Determine timeout
initTimeout := time.Duration(5) * time.Second // fallback if no configuration found
if serverConf.InitializeTimeoutSeconds != nil {
// Prefer server-specific timeout if set
initTimeout = time.Duration(*serverConf.InitializeTimeoutSeconds) * time.Second
} else {
// Parse global timeout from config if set
mcpInitTimeout, err := time.ParseDuration(cfg.Timeouts.MCPInitTimeout)
if err == nil {
// Use parsed value
initTimeout = mcpInitTimeout
} else {
serverLogger.WarnKV("Invalid global MCP init timeout, using default of 5 seconds", "configured_value", cfg.Timeouts.MCPInitTimeout, "error", err)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against non-positive global timeout duration.

The timeout resolution logic is clear and follows a sensible precedence (server-specific → global → default). However, after successfully parsing the global cfg.Timeouts.MCPInitTimeout, the code doesn't verify that the duration is positive. A misconfigured value like "0s" or "-5s" would parse successfully but create an immediately‑expired context.

Apply this guard:

 	} else {
 		// Parse global timeout from config if set
 		mcpInitTimeout, err := time.ParseDuration(cfg.Timeouts.MCPInitTimeout)
-		if err == nil {
+		if err == nil && mcpInitTimeout > 0 {
 			// Use parsed value
 			initTimeout = mcpInitTimeout
 		} else {
-			serverLogger.WarnKV("Invalid global MCP init timeout, using default of 5 seconds", "configured_value", cfg.Timeouts.MCPInitTimeout, "error", err)
+			if err != nil {
+				serverLogger.WarnKV("Invalid global MCP init timeout, using default of 5 seconds", "configured_value", cfg.Timeouts.MCPInitTimeout, "error", err)
+			} else {
+				serverLogger.WarnKV("Non-positive global MCP init timeout, using default of 5 seconds", "configured_value", cfg.Timeouts.MCPInitTimeout)
+			}
 		}
 	}
🤖 Prompt for AI Agents
In cmd/main.go around lines 304 to 318, after parsing
cfg.Timeouts.MCPInitTimeout you must guard against non-positive durations: if
time.ParseDuration succeeds, check that the parsed mcpInitTimeout is > 0 before
assigning it to initTimeout; if it is <= 0, log a warning (include the
configured value and that it is non-positive) and continue using the existing
default (or server-specific value if set), rather than assigning the
non-positive duration which would immediately expire contexts.

Comment thread internal/slack/client.go
Comment on lines +671 to +677
// Create a context with the configured timeout for tool processing
timeout, err := time.ParseDuration(c.cfg.Timeouts.ToolProcessingTimeout)
if err != nil {
c.logger.WarnKV("Invalid tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout, "error", err)
timeout = 1 * time.Minute // fallback to default if parsing fails
}
toolCtx, cancel := context.WithTimeout(ctx, timeout)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against non-positive timeout durations.

The current code doesn't validate that the parsed duration is positive. time.ParseDuration("0s") and time.ParseDuration("-5s") parse successfully but would create an immediately‑expired context. Additionally, the warning is logged on every tool processing attempt when the config value is invalid, which could be noisy in production.

Apply this fix to validate the parsed duration:

 	// Create a context with the configured timeout for tool processing
 	timeout, err := time.ParseDuration(c.cfg.Timeouts.ToolProcessingTimeout)
-	if err != nil {
+	if err != nil || timeout <= 0 {
-		c.logger.WarnKV("Invalid tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout, "error", err)
+		if err != nil {
+			c.logger.WarnKV("Invalid tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout, "error", err)
+		} else {
+			c.logger.WarnKV("Non-positive tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout)
+		}
 		timeout = 1 * time.Minute // fallback to default if parsing fails
 	}
 	toolCtx, cancel := context.WithTimeout(ctx, timeout)
📝 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
// Create a context with the configured timeout for tool processing
timeout, err := time.ParseDuration(c.cfg.Timeouts.ToolProcessingTimeout)
if err != nil {
c.logger.WarnKV("Invalid tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout, "error", err)
timeout = 1 * time.Minute // fallback to default if parsing fails
}
toolCtx, cancel := context.WithTimeout(ctx, timeout)
// Create a context with the configured timeout for tool processing
timeout, err := time.ParseDuration(c.cfg.Timeouts.ToolProcessingTimeout)
if err != nil || timeout <= 0 {
if err != nil {
c.logger.WarnKV("Invalid tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout, "error", err)
} else {
c.logger.WarnKV("Non-positive tool processing timeout, using default of 1 minute", "configured_value", c.cfg.Timeouts.ToolProcessingTimeout)
}
timeout = 1 * time.Minute // fallback to default if parsing fails
}
toolCtx, cancel := context.WithTimeout(ctx, timeout)
🤖 Prompt for AI Agents
internal/slack/client.go around lines 671 to 677: validate that the parsed
duration is positive and avoid noisy per-call warnings by falling back to the
default when the value is non-positive; specifically, after parsing
c.cfg.Timeouts.ToolProcessingTimeout, check if err != nil OR timeout <= 0 and in
either case log a single warning and set timeout = 1*time.Minute; additionally
consider moving/parsing this config at client initialization (or using
sync.Once) so the warning is emitted at startup rather than on every tool
processing attempt.

@nicolas-wyrwa-celebrate
Copy link
Copy Markdown
Contributor Author

@tuannvm Do you think it's feasible to merge this?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant