Skip to content

fix(links): fail health check when Kafka delivery is unavailable - #721

Open
Tyagiquamar wants to merge 4 commits into
databuddy-analytics:mainfrom
Tyagiquamar:fix-links-health-fail-kafka-719
Open

fix(links): fail health check when Kafka delivery is unavailable#721
Tyagiquamar wants to merge 4 commits into
databuddy-analytics:mainfrom
Tyagiquamar:fix-links-health-fail-kafka-719

Conversation

@Tyagiquamar

@Tyagiquamar Tyagiquamar commented Sep 4, 2026

Copy link
Copy Markdown

Root cause

f16085061 hardcoded ssl: true on the links Kafka producer, but
.github/workflows/health-check.yml still stands up a plaintext Redpanda
(--advertise-kafka-addr localhost:9092). The TLS client cannot connect,
candidate.connect() throws, and the failure was swallowed: the health path
set kafka_health_connect_failed: true, nulled the producer, and resolved
normally.

Why CI was falsely green

The swallowed failure leaves the producer in cooldown, so /health/status
reported redpanda: "error". With ClickHouse healthy, calculateLinkReadiness
returns { 200, "degraded" } — which the workflow gate accepts. Every PR
passed without the Kafka path ever being exercised. (The throw alone would
not suffice either: startup warmup arms the 60s reconnect cooldown, so health
probes during CI are suppressed and never attempt a connection.)

Exact fix (code only, no workflow changes)

  • apps/links/src/lib/producer.ts: the health probe (refreshProducerConnection)
    throws Kafka health probe failed when no connection is established, and a
    persistent kafkaConnectFailed flag is set on any failed attempt / cleared
    on any success — so a known outage stays loud across the reconnect-
    suppression window. The throw lives in refreshProducerConnection, not in
    the shared connect(), so a health-owned in-flight attempt can never reject
    a concurrent sendLinkVisit() past its ClickHouse fallback. Production
    send/warmup fallback behavior unchanged; production TLS semantics unchanged;
    no ssl flag reintroduced.
  • apps/links/src/index.ts: /health/status returns { 503, "unavailable" } while didKafkaConnectFail() is set, instead of a
    degraded 200 that CI accepts. Broker-unconfigured (disabled) behavior
    is untouched.

Note: with the plaintext CI Redpanda still in place, this job will now fail
loudly until CI gets a TLS listener — that visible failure is the point of
this fix (issue option 3). TLS setup for CI Redpanda is left as a follow-up;
the same gap exists for basket/uptime and is out of scope here.

Regression coverage

apps/links/src/lib/producer.test.tshealth probe failures (issue #719):

  • health refresh rejects loudly when Kafka is unreachable (fails on main)
  • health refresh resolves once Kafka is reachable
  • production sends still fall back to ClickHouse when Kafka is down
  • outage flag set on failure and cleared on recovery
  • suppressed health probes still report a known outage (fails without the flag)
  • a failing health-owned attempt does not break concurrent sends (fails
    without the refresh-local throw; guards the shared-promise fallback path
    flagged by Greptile/cubic review)

Validation

  • bun test apps/links/src/lib/producer.test.ts apps/links/src/lib/health.test.ts → 19 pass, 0 fail
  • bunx biome check on changed files → clean
  • Mutation check: removed the flag → new tests fail; restored → green
  • Pre-existing, unrelated local env gaps (identical on clean main): full
    apps/links suite needs elysia installed; tsc --noEmit needs bun types

Fixes #719


AI disclosure (per AI_POLICY.md): implemented with OpenCode/Muse Spark
(agent-assisted), human-reviewed and human-verified — reproduction, tests,
and validation above were all executed on this machine.

tripwire-sh[bot]
tripwire-sh Bot previously requested changes Sep 4, 2026

@tripwire-sh tripwire-sh 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.

blocked — it touches a protected workflow file.

@tripwire-sh

tripwire-sh Bot commented Sep 4, 2026

Copy link
Copy Markdown

blocked@Tyagiquamar, this can't merge yet.

it touches a protected workflow file

superseded — see the newer check below.

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

@Tyagiquamar is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 93654075-b8a4-4faa-a8a5-4a65acf9682b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes Kafka health-probe connection failures reject and tightens the Links workflow gate to require fully healthy dependency status. Two blocking interactions remain:

  • A production send can inherit the health probe's rejecting shared connection promise and bypass its ClickHouse fallback.
  • The strict workflow gate is currently unreachable because its plaintext Redpanda listener is incompatible with the Links producer's TLS client.

Confidence Score: 3/5

The PR is not safe to merge until production sends retain fallback behavior during concurrent health probes and the workflow provisions a Kafka listener compatible with the strict health gate.

Two blocking failures remain: shared connection state can turn a health-probe rejection into a lost production visit, and the health-check workflow now requires a status its TLS/plaintext Kafka configuration cannot reach.

Files Needing Attention: apps/links/src/lib/producer.ts, .github/workflows/health-check.yml

Important Files Changed

Filename Overview
apps/links/src/lib/producer.ts Health failures now reject, but the shared connection promise lets concurrent production sends inherit that behavior and skip fallback delivery.
.github/workflows/health-check.yml The Links gate now requires ok, although the workflow's plaintext broker cannot satisfy the producer's TLS connection.
apps/links/src/lib/producer.test.ts Adds isolated health and fallback regression tests, but does not cover health and production callers concurrently sharing a connection attempt.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  H[Health request] --> C[connect false]
  S[Concurrent link visit] --> P[Reuse shared connectPromise]
  C --> P
  P --> F{Kafka connection}
  F -->|Failure| R[Promise rejects]
  R --> L[sendLinkVisit exits before ClickHouse fallback]
  L --> X[Visit reported lost]

  W[CI Links health gate] --> T[TLS Kafka client]
  T --> Q[Plaintext Redpanda listener]
  Q --> D[Readiness remains degraded]
  D --> E[ok-only gate exhausts retries]
Loading

Reviews (1): Last reviewed commit: "fix(links): fail health check when Kafka..." | Re-trigger Greptile

Comment thread apps/links/src/lib/producer.ts Outdated
Comment thread .github/workflows/health-check.yml Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 3 files

Architecture diagram
sequenceDiagram
    participant CI as GH Actions Health Gate
    participant App as Links App
    participant Probe as connect(reportFailure)
    participant Kafka as Kafka/Redpanda
    participant CH as ClickHouse

    Note over CI,CH: Health Check Flow with New Failure Semantics

    CI->>App: GET /health/status (poll every 1s, max 30)
    App->>App: calculateLinkReadiness()
    App->>Probe: refreshProducerConnection() -> connect(false)

    alt Kafka unreachable (TLS mismatch in CI)
        Probe->>Kafka: TLS handshake (ssl:true)
        Kafka-->>Probe: REJECT: tls handshake failed
        Probe->>Probe: Bookkeeping: set cooldown, kafka_connected:false, null producer
        Probe-->>App: THROW error (NEW: no longer swallowed)
        App-->>CI: 200 {"status":"error"}
        CI->>CI: jq check .status == "ok"
        CI-->>CI: FAIL gate (NEW: "degraded" no longer accepted)
    else Kafka reachable
        Probe->>Kafka: connect()
        Kafka-->>Probe: OK
        Probe-->>App: resolve true
        App-->>CI: 200 {"status":"ok"}
        CI->>CI: jq check .status == "ok"
        CI-->>CI: PASS gate
    end

    Note over App,CH: Production Path (unchanged, still graceful fallback)

    App->>Probe: sendLinkVisit() -> connect(true)
    alt Kafka down (same unreachable scenario)
        Probe->>Kafka: connect attempt
        Kafka-->>Probe: REJECT
        Probe->>Probe: set cooldown, kafka_connected:false
        Probe-->>App: return false (no throw for production)
        App->>CH: INSERT link_visit (direct fallback)
        CH-->>App: success
    else Kafka up
        Probe->>Kafka: send message
        Kafka-->>Probe: delivered
    end
Loading

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

Comment thread apps/links/src/lib/producer.ts Outdated
Comment thread .github/workflows/health-check.yml Outdated
Comment thread apps/links/src/lib/producer.ts Outdated
@Tyagiquamar
Tyagiquamar force-pushed the fix-links-health-fail-kafka-719 branch from 27139f5 to 4824956 Compare September 4, 2026 12:25
@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
dashboard Skipped Skipped Sep 5, 2026 5:28pm UTC
documentation Skipped Skipped Sep 5, 2026 5:28pm UTC

@tripwire-sh

tripwire-sh Bot commented Sep 4, 2026

Copy link
Copy Markdown

passed@Tyagiquamar, that's cleared. good to merge.

superseded — see the newer check below.

@vercel
vercel Bot temporarily deployed to Preview – documentation September 4, 2026 12:25 Inactive
@vercel
vercel Bot temporarily deployed to Preview – dashboard September 4, 2026 12:25 Inactive
@tripwire-sh
tripwire-sh Bot dismissed their stale review September 4, 2026 12:25

cleared — this change now passes tripwire's checks.

@Tyagiquamar
Tyagiquamar force-pushed the fix-links-health-fail-kafka-719 branch from 4824956 to 8767e94 Compare September 4, 2026 12:28
@vercel
vercel Bot temporarily deployed to Preview – dashboard September 4, 2026 12:29 Inactive
@vercel
vercel Bot temporarily deployed to Preview – documentation September 4, 2026 12:29 Inactive
tripwire-sh[bot]
tripwire-sh Bot previously requested changes Sep 5, 2026

@tripwire-sh tripwire-sh 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.

blocked — it touches a protected workflow file.

@tripwire-sh

tripwire-sh Bot commented Sep 5, 2026

Copy link
Copy Markdown

blocked@Tyagiquamar, the last push brought something back.

it touches a protected workflow file

superseded — see the newer check below.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 files (changes from recent commits).

Confidence score: 3/5

  • In .github/workflows/health-check.yml, the strict .status == "ok" gate can leave CI failing permanently after one startup Kafka Connect failure because producer.ts persists kafkaConnectFailed; add recovery or retry/reset handling before enforcing the final status.
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=".github/workflows/health-check.yml">

<violation number="1" location=".github/workflows/health-check.yml:761">
P2: The status gate now requires `.status == "ok"`, but a single failed Kafka connect during startup makes this CI job fail with no recovery. On any failed attempt, `producer.ts` sets the persistent `kafkaConnectFailed` flag and arms a 60s reconnect cooldown (`nextReconnectAt = Date.now() + 60000`); during cooldown every `connect()` returns false without trying, so `didKafkaConnectFail()` stays true and `/health/status` returns 503 `"unavailable"` (apps/links/src/index.ts). The poll loop here only retries for ~30s (`for i in {1..30}; sleep 1`), which is shorter than the 60s cooldown, so one transient failure at startup (e.g., Redpanda briefly unreachable during container bring-up) permanently fails the job even though the earlier looser `or .status == "degraded"` gate accepted it. Consider extending the wait past the 60s cooldown (e.g. 90 iterations) so a transient first-connect failure isn't fatal, or confirm the single-attempt behavior is intended.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

Comment thread .github/workflows/health-check.yml Outdated
STATUS_BODY=$(curl -sS http://localhost:2500/health/status)
echo "Links /health/status: $STATUS_BODY"
if echo "$STATUS_BODY" | jq -e '.status == "ok" or .status == "degraded"' > /dev/null; then
if echo "$STATUS_BODY" | jq -e '.status == "ok"' > /dev/null; then

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: The status gate now requires .status == "ok", but a single failed Kafka connect during startup makes this CI job fail with no recovery. On any failed attempt, producer.ts sets the persistent kafkaConnectFailed flag and arms a 60s reconnect cooldown (nextReconnectAt = Date.now() + 60000); during cooldown every connect() returns false without trying, so didKafkaConnectFail() stays true and /health/status returns 503 "unavailable" (apps/links/src/index.ts). The poll loop here only retries for ~30s (for i in {1..30}; sleep 1), which is shorter than the 60s cooldown, so one transient failure at startup (e.g., Redpanda briefly unreachable during container bring-up) permanently fails the job even though the earlier looser or .status == "degraded" gate accepted it. Consider extending the wait past the 60s cooldown (e.g. 90 iterations) so a transient first-connect failure isn't fatal, or confirm the single-attempt behavior is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/health-check.yml, line 761:

<comment>The status gate now requires `.status == "ok"`, but a single failed Kafka connect during startup makes this CI job fail with no recovery. On any failed attempt, `producer.ts` sets the persistent `kafkaConnectFailed` flag and arms a 60s reconnect cooldown (`nextReconnectAt = Date.now() + 60000`); during cooldown every `connect()` returns false without trying, so `didKafkaConnectFail()` stays true and `/health/status` returns 503 `"unavailable"` (apps/links/src/index.ts). The poll loop here only retries for ~30s (`for i in {1..30}; sleep 1`), which is shorter than the 60s cooldown, so one transient failure at startup (e.g., Redpanda briefly unreachable during container bring-up) permanently fails the job even though the earlier looser `or .status == "degraded"` gate accepted it. Consider extending the wait past the 60s cooldown (e.g. 90 iterations) so a transient first-connect failure isn't fatal, or confirm the single-attempt behavior is intended.</comment>

<file context>
@@ -757,7 +758,7 @@ jobs:
             STATUS_BODY=$(curl -sS http://localhost:2500/health/status)
             echo "Links /health/status: $STATUS_BODY"
-            if echo "$STATUS_BODY" | jq -e '.status == "ok" or .status == "degraded"' > /dev/null; then
+            if echo "$STATUS_BODY" | jq -e '.status == "ok"' > /dev/null; then
               echo "Links dependency health is valid"
               break
</file context>

Comment thread apps/links/src/lib/producer.ts Outdated
@vercel
vercel Bot temporarily deployed to Preview – dashboard September 5, 2026 05:19 Inactive
@vercel
vercel Bot temporarily deployed to Preview – documentation September 5, 2026 05:19 Inactive
@tripwire-sh

tripwire-sh Bot commented Sep 5, 2026

Copy link
Copy Markdown

passed@Tyagiquamar, that's cleared. good to merge.

View on Tripwire

@tripwire-sh
tripwire-sh Bot dismissed their stale review September 5, 2026 05:19

cleared — this change now passes tripwire's checks.

@cubic-dev-ai cubic-dev-ai 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.

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

Confidence score: 2/5

  • In .github/workflows/health-check.yml, removing REDPANDA_SSL=false while starting Redpanda without TLS makes the links-health-check job deterministically fail; restore the plaintext setting or configure TLS consistently.
  • In .github/workflows/health-check.yml, allowing a degraded status can hide Kafka delivery failures because the producer enters cooldown while the failure flag remains false, allowing /health to pass despite unavailable delivery; require a healthy status or explicitly detect Kafka errors.
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=".github/workflows/health-check.yml">

<violation number="1">
P2: Removing `REDPANDA_SSL=false` while the CI Redpanda is still started in plaintext (`redpanda start --mode dev-container`, no TLS config) makes the `links-health-check` job deterministically fail. With the env var gone, `producer.ts` defaults `useSsl` to `true`, so KafkaJS performs a TLS handshake against a plaintext listener, which always fails, leaving `kafkaConnectFailed=true` forever. `/health/status` then always returns `503 {"status":"unavailable"}` (the `didKafkaConnectFail()` early return short-circuits before the readiness path that could produce "degraded"), so the retry loop below never sees `ok`/`degraded` and exits 1 after 30 attempts. The "degraded" widening does not help, since "degraded" requires `kafkaConnectFailed === false`. The result is that this job cannot pass its own CI in the merged state; it will stay red until the acknowledged CI-TLS follow-up lands. Land the CI-Redpanda TLS config in this same PR, or keep `REDPANDA_SSL=false` (a genuine Redpanda outage still sets the flag and fails the check loudly, so the goal is preserved).</violation>

<violation number="2">
P2: Accepting `.status == "degraded"` lets the health check pass when Kafka delivery is unavailable. After a Kafka send failure the producer enters cooldown (redpanda `error`), `didKafkaConnectFail()` stays false, and `/health/status` returns `degraded` with HTTP 200. This masks exactly the Kafka delivery outage the PR intends to fail loudly on, once CI Redpanda TLS is enabled and connect() succeeds. Keep the check strictly on `"ok"`.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@vercel
vercel Bot temporarily deployed to Preview – documentation September 5, 2026 17:28 Inactive
@vercel
vercel Bot temporarily deployed to Preview – dashboard September 5, 2026 17:28 Inactive

@cubic-dev-ai cubic-dev-ai 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.

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

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue 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.

links health check no longer exercises Kafka delivery

1 participant