Skip to content

feat: implement production-grade liveness and readiness probes - #4292

Open
ujjwalredd wants to merge 4 commits into
firecrawl:mainfrom
ujjwalredd:fix/production-probes
Open

feat: implement production-grade liveness and readiness probes#4292
ujjwalredd wants to merge 4 commits into
firecrawl:mainfrom
ujjwalredd:fix/production-probes

Conversation

@ujjwalredd

@ujjwalredd ujjwalredd commented Aug 12, 2026

Copy link
Copy Markdown

Resolves #4291

This PR implements the feature requested in the associated issue.

It implements production-grade readiness and liveness probes in src/controllers/v0/readiness.ts and src/controllers/v0/liveness.ts that actively verify connections to Postgres and Redis before returning 200 OK. This resolves existing TODOs in those files and ensures that orchestration systems like Kubernetes only route traffic to fully healthy pods.


Summary by cubic

Adds production-grade liveness and readiness probes so orchestrators route traffic only to healthy pods. Previously both endpoints returned 200 without checks; now liveness verifies the rate limiter Redis is responsive, and readiness verifies rate limiter Redis, queue Redis, and optionally Postgres before advertising readiness.

Liveness returns 503 if the rate limiter Redis client is not ready or its PING exceeds 2s; timers are cleared in finally to prevent leaks. Readiness requires both Redis clients to be ready and respond within 3s; when config.USE_DB_AUTHENTICATION is true, it runs SELECT 1 on main and replica via drizzle-orm. On failure, it logs the error and returns 503 with "Service is not ready" and no internal details.

  • Rollout: Update orchestration health checks to the v0 liveness/readiness endpoints; pods can remain Unready until Redis and Postgres are available.

Written for commit 3985229. Summary will update on new commits.

Review in cubic

@superagent-security superagent-security 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.

Superagent found 1 security concern(s).

res.status(503).json({
status: "error",
message: "Service is not ready",
detail: error instanceof Error ? error.message : "Unknown 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.

P2: Readiness probe exposes internal error details in HTTP response

Internal error messages are leaked in the readiness probe 503 response body.

Remove the detail field from the 503 response; log the full error server-side only.

AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.

<file name="apps/api/src/controllers/v0/readiness.ts">
<violation number="1" location="apps/api/src/controllers/v0/readiness.ts:37">
<priority>P2</priority>
<title>Readiness probe exposes internal error details in HTTP response</title>
<evidence>The catch block returns the raw exception message to unauthenticated callers: `detail: error instanceof Error ? error.message : "Unknown error"`. This leaks internal failure details such as database connection errors, Redis failure messages, or any downstream exception text to anyone who can reach the health endpoint.</evidence>
<recommendation>Remove the `detail` field from the 503 JSON response, or replace it with a static opaque message such as `detail: "Readiness check failed"`. Continue logging the full error server-side via the existing `logger.error` call.</recommendation>
</violation>
</file>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/controllers/v0/liveness.ts Outdated
Comment thread apps/api/src/controllers/v0/readiness.ts Outdated
Comment thread apps/api/src/controllers/v0/readiness.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/controllers/v0/liveness.ts">

<violation number="1" location="apps/api/src/controllers/v0/liveness.ts:10">
P2: When Redis stops replying while its socket remains `ready`, this race returns 503 but leaves the ioredis `ping()` pending; successful pings also leave the two-second timer alive until it fires. Use a command-level timeout or coalesced probe, and clear the timer in `finally`, so repeated health checks cannot accumulate work on the shared client.</violation>
</file>

<file name="apps/api/src/controllers/v0/readiness.ts">

<violation number="1" location="apps/api/src/controllers/v0/readiness.ts:10">
P2: When a Redis or PostgreSQL check hangs, `withTimeout` only rejects the wrapper; the underlying operation continues after the handler returns 503. Repeated readiness probes can accumulate pending Redis commands or occupy PostgreSQL pool clients, so use cancellable/native client timeouts for these checks and clean up the race timer.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

return res.status(503).json({ status: "error", message: "Redis connection is not ready" });
}
// Set a short timeout to prevent the ping from hanging indefinitely
await Promise.race([

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

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.

P2: When Redis stops replying while its socket remains ready, this race returns 503 but leaves the ioredis ping() pending; successful pings also leave the two-second timer alive until it fires. Use a command-level timeout or coalesced probe, and clear the timer in finally, so repeated health checks cannot accumulate work on the shared client.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/controllers/v0/liveness.ts, line 10:

<comment>When Redis stops replying while its socket remains `ready`, this race returns 503 but leaves the ioredis `ping()` pending; successful pings also leave the two-second timer alive until it fires. Use a command-level timeout or coalesced probe, and clear the timer in `finally`, so repeated health checks cannot accumulate work on the shared client.</comment>

<file context>
@@ -2,8 +2,17 @@ import { Request, Response } from "express";
+      return res.status(503).json({ status: "error", message: "Redis connection is not ready" });
+    }
+    // Set a short timeout to prevent the ping from hanging indefinitely
+    await Promise.race([
+      redisRateLimitClient.ping(),
+      new Promise((_, reject) => setTimeout(() => reject(new Error("Redis ping timed out")), 2000))
</file context>
Fix with cubic

import { config } from "../../config";

const withTimeout = <T>(promise: Promise<T>, timeoutMs: number, name: string): Promise<T> => {
return Promise.race([

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

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.

P2: When a Redis or PostgreSQL check hangs, withTimeout only rejects the wrapper; the underlying operation continues after the handler returns 503. Repeated readiness probes can accumulate pending Redis commands or occupy PostgreSQL pool clients, so use cancellable/native client timeouts for these checks and clean up the race timer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/controllers/v0/readiness.ts, line 10:

<comment>When a Redis or PostgreSQL check hangs, `withTimeout` only rejects the wrapper; the underlying operation continues after the handler returns 503. Repeated readiness probes can accumulate pending Redis commands or occupy PostgreSQL pool clients, so use cancellable/native client timeouts for these checks and clean up the race timer.</comment>

<file context>
@@ -4,28 +4,36 @@ import { db, dbRr } from "../../db/connection";
+import { config } from "../../config";
+
+const withTimeout = <T>(promise: Promise<T>, timeoutMs: number, name: string): Promise<T> => {
+  return Promise.race([
+    promise,
+    new Promise<T>((_, reject) => setTimeout(() => reject(new Error(`${name} check timed out after ${timeoutMs}ms`)), timeoutMs))
</file context>
Fix with cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

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] Production-grade Liveness and Readiness Probes

1 participant