Skip to content

Add /health/ready dependency check; fix oversized SECRET_KEY placeholder#3

Closed
sarangs1621 wants to merge 1 commit into
mainfrom
fix/readiness-check-and-secret-placeholder
Closed

Add /health/ready dependency check; fix oversized SECRET_KEY placeholder#3
sarangs1621 wants to merge 1 commit into
mainfrom
fix/readiness-check-and-secret-placeholder

Conversation

@sarangs1621

@sarangs1621 sarangs1621 commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds GET /health/ready, which checks Postgres (SELECT 1) and Redis (PING) and returns 503 with per-dependency status if either is unreachable. /health remains a static liveness probe (existing behavior unchanged).
  • Fixes .env.example's SECRET_KEY placeholder: the old value (change-me-to-a-long-random-secret) is 33 characters, which happens to pass the >=32-char startup validation in app/core/config.py -- a copy-paste deploy that forgets to regenerate it would silently run with a public, known JWT secret instead of failing at startup. New placeholder (change-me) is intentionally short so the app refuses to start until a real secret is generated.

Test plan

  • New tests in tests/test_security_headers.py: /health/ready returns 200 + {"status":"ok","database":"ok","redis":"ok"} when healthy, and 503 with the relevant dependency marked "error" when Postgres or Redis is unreachable (simulated via monkeypatched broken clients).
  • Full suite: 206/206 passing, 96.57% coverage (gated at 95%).
  • ruff check and mypy clean on changed files.

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a health check endpoint that reports the operational status of system dependencies.
  • Documentation

    • Updated configuration examples with enhanced clarity on secret key requirements (minimum 32 characters).
  • Tests

    • Added test coverage for health check responses across various dependency scenarios.

/health stays a static liveness probe; /health/ready now checks Postgres
(SELECT 1) and Redis (PING) and returns 503 with per-dependency status if
either is unreachable, so platforms that gate routing/restarts on a health
endpoint can detect a broken DB/Redis connection.

The .env.example SECRET_KEY placeholder was 33 chars, which happens to pass
the >=32-char startup validation -- a copy-paste deploy that forgot to
regenerate it would silently run with a public, known JWT secret instead of
failing fast. Shortened it to a value that fails validation until replaced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a health readiness endpoint (/health/ready) that checks database and Redis connectivity, returning HTTP 200 when both succeed or HTTP 503 on failure, and updates environment documentation to clarify SECRET_KEY generation requirements.

Changes

Health Readiness Endpoint and Configuration

Layer / File(s) Summary
Environment configuration documentation
.env.example
SECRET_KEY documentation is expanded with a multi-line comment requiring a minimum 32-character value and clarifying that the app refuses to start without a real secret.
Readiness check endpoint implementation
app/main.py
Error handling imports for Redis and SQLAlchemy, plus client access imports (get_redis_client, AsyncSessionLocal), are added; a /health/ready endpoint runs async database (SELECT 1) and Redis (ping()) checks, maps failures to "error", and returns HTTP 200 with {status: "ok", ...} or HTTP 503 with {status: "error", ...} including per-service status.
Readiness check test coverage
tests/test_security_headers.py
Error type imports are added; three async tests verify healthy dependencies return HTTP 200 with all-ok status, and two tests verify HTTP 503 with error status when database or Redis connectivity fails, using monkeypatch to simulate failures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops 'round databases and caches with glee,
Checking that health flows smoothly as can be!
Redis pings, and queries dance their way,
Ready or not, this endpoint's here to stay! 🏥✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: adding a /health/ready dependency check endpoint and fixing the SECRET_KEY placeholder in .env.example to be oversized.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 fix/readiness-check-and-secret-placeholder

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@app/main.py`:
- Around line 83-92: The health checks for AsyncSessionLocal (database) and
get_redis_client().ping() (redis) can hang; wrap each awaited operation in
asyncio.wait_for with a short timeout (e.g., 2–5s) and handle
asyncio.TimeoutError to mark checks["database"] or checks["redis"] = "error";
keep existing exception handlers for SQLAlchemyError and RedisError but ensure
timeouts are caught separately to avoid blocking readiness probes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86bb6f4f-1fc9-44ee-84c6-72d3336650f6

📥 Commits

Reviewing files that changed from the base of the PR and between 102262d and f982260.

📒 Files selected for processing (3)
  • .env.example
  • app/main.py
  • tests/test_security_headers.py

Comment thread app/main.py
Comment on lines +83 to +92
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
except SQLAlchemyError:
checks["database"] = "error"

try:
await get_redis_client().ping()
except RedisError:
checks["redis"] = "error"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Add timeouts to dependency health checks.

Neither the database nor Redis connectivity check has an explicit timeout. If either dependency hangs (network partition, slow query, etc.) rather than immediately refusing the connection, the readiness probe will hang indefinitely. Orchestration systems (Kubernetes, Docker Swarm, etc.) expect health checks to complete within a configured timeout window; a hanging probe can prevent proper failover, scaling, and traffic routing.

Consider wrapping each check in asyncio.wait_for() with a reasonable timeout (e.g., 2-5 seconds).

⏱️ Proposed fix to add timeouts
+import asyncio
+
 from fastapi import FastAPI, Request, status
 from fastapi.middleware.cors import CORSMiddleware
 ...

     `@app.get`("/health/ready", tags=["health"])
     async def readiness_check() -> JSONResponse:
         checks = {"database": "ok", "redis": "ok"}

         try:
-            async with AsyncSessionLocal() as session:
-                await session.execute(text("SELECT 1"))
+            async with asyncio.timeout(5.0):
+                async with AsyncSessionLocal() as session:
+                    await session.execute(text("SELECT 1"))
-        except SQLAlchemyError:
+        except (SQLAlchemyError, TimeoutError):
             checks["database"] = "error"

         try:
-            await get_redis_client().ping()
-        except RedisError:
+            async with asyncio.timeout(5.0):
+                await get_redis_client().ping()
+        except (RedisError, TimeoutError):
             checks["redis"] = "error"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/main.py` around lines 83 - 92, The health checks for AsyncSessionLocal
(database) and get_redis_client().ping() (redis) can hang; wrap each awaited
operation in asyncio.wait_for with a short timeout (e.g., 2–5s) and handle
asyncio.TimeoutError to mark checks["database"] or checks["redis"] = "error";
keep existing exception handlers for SQLAlchemyError and RedisError but ensure
timeouts are caught separately to avoid blocking readiness probes.

@sarangs1621 sarangs1621 deleted the fix/readiness-check-and-secret-placeholder branch June 27, 2026 15:13
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