Skip to content

[FIX] Bump tool versions for Redis Sentinel compatibility#1859

Merged
chandrasekharan-zipstack merged 1 commit intorefactor/redis-client-creation-dryfrom
fix/bump-tool-versions-sentinel
Mar 15, 2026
Merged

[FIX] Bump tool versions for Redis Sentinel compatibility#1859
chandrasekharan-zipstack merged 1 commit intorefactor/redis-client-creation-dryfrom
fix/bump-tool-versions-sentinel

Conversation

@chandrasekharan-zipstack
Copy link
Contributor

What

  • Patch version bump for all tools that depend on unstract-core's Redis client:
    • classifier: 0.0.760.0.77
    • structure: 0.0.970.0.98
    • text_extractor: 0.0.720.0.73

Why

  • Tools depend on unstract-core which was updated with Redis Sentinel support (create_redis_client() factory). The version bump ensures new tool container images are built with the updated dependency, enabling proper Sentinel-aware Redis connections (e.g., MetricsMixin).

How

  • Updated toolVersion in each tool's properties.json
  • Updated public_tools.json registry with new versions and image tags
  • Updated backend/sample.env with new structure tool version

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • No. This is a patch version bump only. No functional changes to tool code — only the underlying unstract-core dependency gains Sentinel support.

Database Migrations

  • None

Env Config

  • STRUCTURE_TOOL_IMAGE_TAG updated to 0.0.98 in sample.env

Relevant Docs

  • N/A

Related Issues or PRs

Dependencies Versions

  • N/A

Notes on Testing

  • Build and deploy new tool images with bumped versions
  • Verify tools function correctly in both standalone Redis and Sentinel environments

Checklist

I have read and understood the Contribution Guidelines.

Patch bump for tools that depend on unstract-core's updated
Redis client with Sentinel support:
- classifier: 0.0.76 -> 0.0.77
- structure: 0.0.97 -> 0.0.98
- text_extractor: 0.0.72 -> 0.0.73

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Caution

Review failed

Pull request was closed or merged during review

Summary by CodeRabbit

  • New Features

    • Added Redis Sentinel mode support for improved connection resilience
    • Added SSL support for Redis connections
    • Tool version updates: classifier (0.0.77), text extractor (0.0.73), structure tool (0.0.98)
  • Bug Fixes

    • Enhanced Redis client initialization and error handling for better reliability

Walkthrough

This pull request adds Redis Sentinel support through new environment variables and refactors Redis client initialization to use a factory pattern. It bumps versions for classifier (0.0.76→0.0.77), structure (0.0.97→0.0.98), and text_extractor (0.0.72→0.0.73) tools. It also introduces the core package as an editable dependency across tools and SDK components.

Changes

Cohort / File(s) Summary
Redis Sentinel Configuration
runner/src/unstract/runner/constants.py, runner/src/unstract/runner/runner.py, unstract/workflow-execution/src/unstract/workflow_execution/constants.py, unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py
Adds REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME constants and propagates them through runner sidecar configuration and tool environment variables.
Redis Client Refactoring
unstract/core/src/unstract/core/cache/redis_client.py, unstract/sdk1/src/unstract/sdk1/utils/metrics_mixin.py, workers/shared/cache/cache_backends.py, workers/shared/infrastructure/caching/cache_utils.py
Refactors Redis client initialization to use create_redis_client factory; adds SSL support handling; removes direct redis.StrictRedis construction; improves error handling with try/except wrappers.
Tool Version Bumps and Dockerfiles
backend/sample.env, tools/classifier/Dockerfile, tools/classifier/src/config/properties.json, tools/structure/Dockerfile, tools/structure/src/config/properties.json, tools/text_extractor/Dockerfile, tools/text_extractor/src/config/properties.json, unstract/tool-registry/tool_registry_config/public_tools.json
Updates tool versions and Docker image URLs for classifier, structure, and text_extractor; adds core package copy directives to tool Dockerfiles.
Core Package Dependencies
tools/classifier/requirements.txt, tools/structure/requirements.txt, tools/text_extractor/requirements.txt, unstract/sdk1/pyproject.toml
Adds core package as editable local dependency; updates requirements.txt and pyproject.toml to include -e file:/unstract/core and tool.uv.sources configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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
Title check ✅ Passed The title clearly and specifically summarizes the primary change: patch version bumps for tools to support Redis Sentinel compatibility.
Description check ✅ Passed The PR description follows the template structure and includes all critical sections: What (version bumps), Why (Redis Sentinel support), How (specific file updates), impact analysis, env config changes, related issues, and testing notes.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/bump-tool-versions-sentinel
📝 Coding Plan
  • Generate coding plan for human review comments

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
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 63 passed, 0 failed (63 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{63}}$$ $$\textcolor{#23d18b}{\tt{63}}$$

@sonarqubecloud
Copy link

@chandrasekharan-zipstack chandrasekharan-zipstack changed the base branch from main to refactor/redis-client-creation-dry March 15, 2026 08:58
@chandrasekharan-zipstack
Copy link
Contributor Author

Not waiting for approvals on this PR since its a trivial version bump and due to urgency

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit 73b8216 into refactor/redis-client-creation-dry Mar 15, 2026
8 of 9 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the fix/bump-tool-versions-sentinel branch March 15, 2026 09:01
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR does two things under one patch-version bump: (1) it increments the versions of three tool containers (classifier, structure, text_extractor) to force image rebuilds with an updated unstract-core dependency, and (2) it ships the actual unstract-core changes that add Redis Sentinel support — a create_redis_client() factory, SSL support, and a refactored RedisClient wrapper — and propagates the two new env vars (REDIS_SENTINEL_MODE, REDIS_SENTINEL_MASTER_NAME) through every layer that spawns tool containers (runner, workflow-execution) and worker cache managers.

Key changes:

  • unstract/core/…/redis_client.py: New create_redis_client() factory handles both Sentinel and standalone modes transparently; RedisClient.__init__ is removed — the class is now only constructible via from_env() or .__new__(); SSL support added to _resolve_redis_env and _build_connection_kwargs.
  • unstract/sdk1/…/metrics_mixin.py: Replaces hard-wired StrictRedis(host="unstract-redis", …) with create_redis_client(db=1), gaining Sentinel awareness; Redis operations wrapped in try/except for resilience.
  • workers/shared/…/cache_backends.py + cache_utils.py: Conditional standalone/Sentinel branching removed in favour of the unified factory.
  • runner/runner.py, workflow_execution/tools_utils.py, runner/constants.py, workflow_execution/constants.py: New Sentinel env var constants added and forwarded to tool/sidecar containers.
  • tools/*/properties.json + public_tools.json + backend/sample.env: Version bumps and registry entries updated.

Issue found: In _build_connection_kwargs, the include_auth_only branch (used to build sentinel_kwargs) returns early before the SSL block, so TLS settings are never forwarded to the Sentinel management connections. Combined SSL + Sentinel mode will fail at the Sentinel layer even though the master Redis connection would use TLS correctly.

Confidence Score: 3/5

  • Safe to merge for standalone Redis and non-SSL Sentinel deployments; SSL + Sentinel combined mode has a logic bug that will cause connection failures.
  • The tool version bumps, env-var propagation, and worker cache simplifications are all correct. The Sentinel-only (no SSL) path works as intended. However, _build_connection_kwargs never forwards SSL settings into sentinel_kwargs, meaning any deployment that enables both Sentinel mode and TLS will encounter Sentinel management-channel connection failures. This is a latent bug introduced alongside the new SSL feature in the same file. The rest of the changes are low-risk refactors with no functional regressions for the current (non-SSL) Sentinel use case.
  • unstract/core/src/unstract/core/cache/redis_client.py — specifically the _build_connection_kwargs function's include_auth_only branch and its interaction with the new SSL fields.

Important Files Changed

Filename Overview
unstract/core/src/unstract/core/cache/redis_client.py Core Redis factory refactored to support SSL and Sentinel. SSL settings are correctly propagated to standalone connections, but are omitted from sentinel_kwargs (the Sentinel management-channel kwargs), meaning SSL+Sentinel combinations will fail at the Sentinel negotiation step.
unstract/sdk1/src/unstract/sdk1/utils/metrics_mixin.py Switched from direct StrictRedis construction (with hardcoded host default unstract-redis) to create_redis_client(), gaining Sentinel awareness. Error handling improved with try/except around Redis ops. Minor observability reduction from removed error log on collect_metrics when client is None.
unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py Reads REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME from env and forwards them to tool container env vars with safe defaults (False / mymaster). Straightforward propagation, no issues.
runner/src/unstract/runner/runner.py Adds REDIS_SENTINEL_MODE and REDIS_SENTINEL_MASTER_NAME to both the tool container env and the sidecar container env with safe defaults. Consistent with the ToolsUtils changes.
workers/shared/cache/cache_backends.py Replaced conditional standalone/Sentinel branching with unified create_redis_client() call. The enabled guard from cache_config is preserved. Removed verbose host:port log on success, which reduces some debug visibility but is not a correctness issue.
workers/shared/infrastructure/caching/cache_utils.py Same simplification as cache_backends.py — both standalone and Sentinel paths unified via create_redis_client(). The cache_config["enabled"] check still controls initialization; actual connection parameters now come from env vars read by the factory (consistent with WorkerConfig which also reads them from env).
unstract/sdk1/pyproject.toml Adds unstract-core as an explicit dependency (editable path source) so the SDK picks up the new create_redis_client factory. Correct and expected.

Sequence Diagram

sequenceDiagram
    participant Runner as Runner / ToolsUtils
    participant Factory as create_redis_client()
    participant Env as Environment Variables
    participant Sentinel as Redis Sentinel
    participant Master as Redis Master

    Runner->>Env: read REDIS_SENTINEL_MODE, REDIS_SENTINEL_MASTER_NAME
    Runner->>Runner: inject into tool/sidecar container env
    note over Runner: New in this PR ↑

    Runner->>Factory: create_redis_client(env_prefix, db, ...)
    Factory->>Env: _resolve_redis_env() → host, port, password, ssl
    alt SENTINEL_MODE == true
        Factory->>Sentinel: Sentinel([(host, port)], sentinel_kwargs)
        note over Factory,Sentinel: ⚠️ ssl missing from sentinel_kwargs
        Sentinel-->>Factory: SentinelConnectionPool
        Factory->>Master: sentinel.master_for(master_name, **master_kwargs)
        Master-->>Factory: redis.Redis (Sentinel-backed)
    else standalone
        Factory->>Master: redis.Redis(host, port, **kwargs)
        Master-->>Factory: redis.Redis (direct)
    end
    Factory-->>Runner: redis.Redis client

    note over Runner: MetricsMixin also calls create_redis_client(db=1)
Loading

Comments Outside Diff (3)

  1. unstract/core/src/unstract/core/cache/redis_client.py, line 33 (link)

    Module-level env var captured at import time

    _DEFAULT_SENTINEL_MASTER_NAME reads REDIS_SENTINEL_MASTER_NAME at module import time rather than at each call site. In most deployments this is harmless since env vars are stable, but it means any override of REDIS_SENTINEL_MASTER_NAME made after the module is first imported (e.g., in tests that manipulate os.environ) will be silently ignored. Prefer reading it lazily inside _create_sentinel_client to avoid subtle test-isolation bugs:

    # Remove module-level constant:
    # _DEFAULT_SENTINEL_MASTER_NAME = os.getenv("REDIS_SENTINEL_MASTER_NAME", "mymaster")
    
    def _create_sentinel_client(...):
        master_name = os.getenv(
            f"{env_prefix}SENTINEL_MASTER_NAME",
            os.getenv("REDIS_SENTINEL_MASTER_NAME", "mymaster"),
        )
  2. unstract/sdk1/src/unstract/sdk1/utils/metrics_mixin.py, line 50-51 (link)

    Silent failure loses observability

    The previous code logged an error-level message when redis_client is None so operators could distinguish "Redis never initialised" from a key-not-found case. The log was removed in this PR, meaning repeated collect_metrics calls after a failed init will return None with no trace in the logs beyond the single init-time error. Consider at least emitting a debug log here to keep the signal without being noisy:

    if self.redis_client is None:
        logger.debug("Redis client unavailable; skipping metrics collection.")
        return {self.TIME_TAKEN_KEY: None}
  3. unstract/core/src/unstract/core/cache/redis_client.py, line 137-146 (link)

    SSL not forwarded to Sentinel management connections

    In the include_auth_only branch, the early-return on line 146 exits before the SSL block at the bottom of the function. The resulting sentinel_kwargs therefore never carry ssl or ssl_cert_reqs, even when the env dict has ssl=True set by _resolve_redis_env.

    These kwargs are passed directly to the Sentinel() constructor and govern connections to the Sentinel nodes themselves (quorum queries, failover detection, etc.). If SSL is enabled, those management-channel connections will attempt a plain-text socket while the service expects TLS, causing a connection failure at the Sentinel layer even though the master Redis connection would use SSL correctly via master_kwargs.

    The fix is to carry SSL settings through in that branch too:

    if include_auth_only:
        kwargs: dict[str, Any] = {
            "socket_connect_timeout": socket_connect_timeout,
            "socket_timeout": socket_timeout,
        }
        if env.get("password"):
            kwargs["password"] = env["password"]
        if env.get("username"):
            kwargs["username"] = env["username"]
        if env.get("ssl"):
            kwargs["ssl"] = True
            kwargs["ssl_cert_reqs"] = env.get("ssl_cert_reqs", "required")
        return kwargs
Prompt To Fix All With AI
This is a comment left during a code review.
Path: unstract/core/src/unstract/core/cache/redis_client.py
Line: 33

Comment:
**Module-level env var captured at import time**

`_DEFAULT_SENTINEL_MASTER_NAME` reads `REDIS_SENTINEL_MASTER_NAME` at module import time rather than at each call site. In most deployments this is harmless since env vars are stable, but it means any override of `REDIS_SENTINEL_MASTER_NAME` made after the module is first imported (e.g., in tests that manipulate `os.environ`) will be silently ignored. Prefer reading it lazily inside `_create_sentinel_client` to avoid subtle test-isolation bugs:

```python
# Remove module-level constant:
# _DEFAULT_SENTINEL_MASTER_NAME = os.getenv("REDIS_SENTINEL_MASTER_NAME", "mymaster")

def _create_sentinel_client(...):
    master_name = os.getenv(
        f"{env_prefix}SENTINEL_MASTER_NAME",
        os.getenv("REDIS_SENTINEL_MASTER_NAME", "mymaster"),
    )
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: unstract/sdk1/src/unstract/sdk1/utils/metrics_mixin.py
Line: 50-51

Comment:
**Silent failure loses observability**

The previous code logged an `error`-level message when `redis_client is None` so operators could distinguish "Redis never initialised" from a key-not-found case. The log was removed in this PR, meaning repeated `collect_metrics` calls after a failed init will return `None` with no trace in the logs beyond the single init-time error. Consider at least emitting a `debug` log here to keep the signal without being noisy:

```python
if self.redis_client is None:
    logger.debug("Redis client unavailable; skipping metrics collection.")
    return {self.TIME_TAKEN_KEY: None}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: unstract/core/src/unstract/core/cache/redis_client.py
Line: 137-146

Comment:
**SSL not forwarded to Sentinel management connections**

In the `include_auth_only` branch, the early-return on line 146 exits before the SSL block at the bottom of the function. The resulting `sentinel_kwargs` therefore never carry `ssl` or `ssl_cert_reqs`, even when the `env` dict has `ssl=True` set by `_resolve_redis_env`.

These kwargs are passed directly to the `Sentinel()` constructor and govern connections to the Sentinel nodes themselves (quorum queries, failover detection, etc.). If SSL is enabled, those management-channel connections will attempt a plain-text socket while the service expects TLS, causing a connection failure at the Sentinel layer even though the master Redis connection would use SSL correctly via `master_kwargs`.

The fix is to carry SSL settings through in that branch too:

```python
if include_auth_only:
    kwargs: dict[str, Any] = {
        "socket_connect_timeout": socket_connect_timeout,
        "socket_timeout": socket_timeout,
    }
    if env.get("password"):
        kwargs["password"] = env["password"]
    if env.get("username"):
        kwargs["username"] = env["username"]
    if env.get("ssl"):
        kwargs["ssl"] = True
        kwargs["ssl_cert_reqs"] = env.get("ssl_cert_reqs", "required")
    return kwargs
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 8fa6f89

chandrasekharan-zipstack added a commit that referenced this pull request Mar 15, 2026
* [FIX] Fix Redis Sentinel crash in MetricsMixin (prompt-service)

MetricsMixin was creating a raw StrictRedis client using REDIS_HOST/PORT
directly, which in Sentinel HA mode points to the Sentinel process (port
26379). Sentinel doesn't support the SELECT command, causing
ResponseError on every /answer-prompt call.

- Replace StrictRedis with create_redis_client() from unstract-core
  which handles Sentinel discovery via master_for()
- Add unstract-core as sdk1 dependency (all services using sdk1 already
  depend on core, so zero new transitive deps)
- Add error handling around set_start_time() and collect_metrics() so
  Redis failures degrade gracefully instead of crashing requests
- Add missing COPY for unstract/core in tool Dockerfiles (classifier,
  structure, text_extractor) since sdk1 now depends on core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Commit uv.lock changes

* [FIX] Add unstract-core to tool requirements.txt for pip resolution

Tool Dockerfiles (classifier, text_extractor, structure) use pip, not
uv. Since sdk1 now depends on unstract-core (which isn't on PyPI),
pip can't resolve it via [tool.uv.sources]. Adding explicit editable
install of core before sdk1 ensures pip finds it locally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [REFACTOR] DRY Redis client creation in worker cache modules

Worker cache modules (cache_backends.py, cache_utils.py) duplicated
sentinel/standalone branching logic for Redis client creation. Both
now use create_redis_client() from unstract-core as the single entry
point, which already handles mode detection internally.

- Add SSL support (ssl, ssl_cert_reqs) to create_redis_client() via
  env vars ({prefix}SSL, {prefix}SSL_CERT_REQS), disabled by default
- Remove if-sentinel-else-standalone branching in cache_backends.py
  and cache_utils.py — replaced with single create_redis_client() call
- Remove unused os and redis imports from simplified modules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [FIX] Pass Redis Sentinel env vars to tool and sidecar containers

The runner was not forwarding REDIS_SENTINEL_MODE and
REDIS_SENTINEL_MASTER_NAME to tool containers and sidecars, causing the
sidecar to connect in standalone mode to the Sentinel port (26379) and
fail on all data commands (HGETALL, SETEX).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [FIX] Bump tool versions for Redis Sentinel compatibility (#1859)

Patch bump for tools that depend on unstract-core's updated
Redis client with Sentinel support:
- classifier: 0.0.76 -> 0.0.77
- structure: 0.0.97 -> 0.0.98
- text_extractor: 0.0.72 -> 0.0.73

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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