Skip to content

fix(ci): space the cold-connect canary so each sample meets a cold database - #214

Open
wmadden-electric wants to merge 2 commits into
mainfrom
claude/canary-space-samples
Open

fix(ci): space the cold-connect canary so each sample meets a cold database#214
wmadden-electric wants to merge 2 commits into
mainfrom
claude/canary-space-samples

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

On 2026-08-09 the cold-connect canary returned a bug-gone verdict — 14 of 14 successes on run 31330072181 — and printed the full instruction to delete withConnectionRetry and the canary itself. It was wrong. Runs on main minutes either side still saw the rejection, and six live runs since have all rejected on their first sample at 575–588ms.

Why it was wrong

The canary sampled back to back and counted every success as evidence. Across 20 recent runs (63 samples):

rejection rate overall 30% (19/63)
runs rejecting on sample #0 8 of 20
longest clean run 14 (the false alarm)
median latency, success 122ms
median latency, rejection 546ms

That latency split is the tell. A rejection takes ~546ms, matching the "direct endpoint fast-rejects ~0.4–0.6s" already documented in gotchas.md. A 122ms success is a connect to something already warm — it never met a cold database, so it tested nothing, yet it counted towards "the bug is gone".

Why raising the sample count was not the fix

My first instinct was to raise MIN_BUG_GONE_SAMPLES from 14 to about 26. That was wrong, and it's worth writing down why so nobody tries it again.

The threshold's justification — "at a conservative 20% rejection rate, 0.8^14 ≈ 4.4% chance of a lucky streak" — assumes every sample is an independent draw at a fixed rate. The data contradicts that. Eight of twenty runs rejected on their very first sample, while others ran 8, 10 and 14 samples clean. The outcome is dominated by conditions that hold for a whole run, so adding twelve more samples to a run that is already sailing through just collects twelve more clean samples. The doc comment on MIN_BUG_GONE_SAMPLES now records this rather than leaving the misleading arithmetic in place.

The change

Samples are spaced 60 seconds apart, so each one has a chance of meeting a genuinely cold database. This is the sibling cold-start canary's approach: SAMPLE_INTERVAL_MS there is also 60s, adopted for the same family of bug, and per gotchas.md that canary goes further and refuses to count a sample it cannot confirm was cold.

The pause sits between samples only. Putting one between provisioning a database and connecting to it would warm the very thing under test — there's a comment saying so, because it is an easy mistake to make later.

The cost is smaller than it looks. The loop still stops at the first rejection, which is the normal outcome today, so a typical run finishes in a couple of minutes. Only a fully clean run pays all 13 intervals. The script has its own 15-minute budget so it stops and still reports, with timeout-minutes raised from 3 to 20 as headroom for that — the same arrangement the sibling job documents.

60s is adopted from the sibling rather than derived from measurement here. The comment says so, and says it is the first number to re-derive if false verdicts persist.

Verification

The sampling loop moves into the classify module so it can be tested offline against a virtual clock, rather than waited out. Four new tests cover: no pause before the first sample, one pause between each pair after, stopping at the first rejection, continuing past minSamples while everything succeeds, and stopping on the run budget. The budget test also asserts the resulting short run classifies as inconclusive, which is why stopping early can never manufacture the forcing signal.

Reverting the samples.length > 0 guard fails two of them, so they catch the regression they exist for.

Six live runs against the real API confirm the wiring end to end: correct verdict, exit 0, project torn down each time. All six rejected on sample #0, so the spacing path itself is covered by the unit tests rather than by a live run.

🤖 Generated with Claude Code

…tabase

The canary returned a bug-gone verdict on 2026-08-09 (run 31330072181, 14/14 successes) — the forcing signal to delete `withConnectionRetry` — while runs on main minutes either side still saw the rejection. Six live runs since have all rejected on their first sample, at 575-588ms.

It sampled back to back, and counted every success as evidence. Across 20 recent runs (63 samples) the successes came back in a median of 122ms against 546ms for rejections, which matches the documented ~0.4-0.6s fast-reject: the quick ones were reaching an already-warm upstream and testing nothing.

Raising MIN_BUG_GONE_SAMPLES would not have helped. Its arithmetic assumes independent samples at a fixed rate, and the data says otherwise — 8 of those 20 runs rejected on sample #0 while others ran 8, 10 and 14 clean, so the outcome is dominated by whole-run conditions. More samples from a run that is already sailing through are just more clean samples. The doc comment now says so.

Samples are spaced 60s apart, matching the sibling cold-start canary, which adopted the same spacing for the same family of bug. The pause sits between samples only — never between provisioning a database and connecting to it, which would warm the thing under test. Cost is small in practice because the loop still stops at the first rejection, the normal outcome today; only a fully clean run pays all 13 intervals, and the run has its own 15 minute budget below the job timeout.

The sampling loop moves into the classify module so the spacing, the early stop and the budget stop are unit-tested against a virtual clock instead of waited out.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 61da584d-7ac0-4f3d-a462-03a846c10392

📥 Commits

Reviewing files that changed from the base of the PR and between 71c0f25 and 7ed2a42.

📒 Files selected for processing (3)
  • scripts/cold-connect-canary-classify.test.ts
  • scripts/cold-connect-canary-classify.ts
  • scripts/cold-connect-canary.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved cold-connect canary accuracy by spacing samples 60 seconds apart, reducing warm-up false positives.
    • Added safeguards to stop sampling after a rejection or runtime limit.
    • Successful results now require multiple spaced samples before being classified as bug-gone.
    • Runs that exceed the time budget are reported as inconclusive.
  • Documentation

    • Clarified intermittent results and recommended confirming future bug-gone verdicts with a second run.
  • Tests

    • Added coverage for sampling intervals, early rejection, success thresholds, and runtime limits.

Walkthrough

The cold-connect canary now uses an asynchronous collector with 60-second sample spacing and a 15-minute runtime limit. The collector stops on rejection or timeout and continues successful sampling until the required threshold. Tests cover timing, termination, logging, and inconclusive results. The deployment workflow timeout is 20 minutes. Documentation describes warm-database false positives and confirmation of bug-gone verdicts.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: spacing cold-connect canary samples to test cold databases.
Description check ✅ Passed The description directly explains the false verdict, sampling changes, timeout budget, documentation updates, and test coverage.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/canary-space-samples
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/canary-space-samples

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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@214
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@214

commit: 7ed2a42

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

🤖 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 `@scripts/cold-connect-canary-classify.ts`:
- Around line 180-192: Update the sampling loop in
scripts/cold-connect-canary-classify.ts:180-192 to use a >= deadline check, skip
any interval wait that cannot fit within the remaining maxRunMs budget, and
recheck the deadline after sleep before invoking sample. Update the boundary
assertion in scripts/cold-connect-canary-classify.test.ts:158-165 so the
virtual-clock case collects four samples and three waits without starting a
fifth sample.

In `@scripts/cold-connect-canary.ts`:
- Around line 63-65: Validate the runtime controls before starting the canary:
require COLD_CONNECT_SAMPLE_INTERVAL_MS and COLD_CONNECT_MAX_RUN_MS to be finite
positive numbers, and COLD_CONNECT_SAMPLES to be a positive safe integer. Route
invalid values through the existing non-blocking canary error path and prevent
startup until configuration is valid, while preserving the current
environment-variable defaults.
🪄 Autofix

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: ASSERTIVE

Plan: Pro Plus

Run ID: 7b4ec42e-d661-4299-ae08-b5d12da27741

📥 Commits

Reviewing files that changed from the base of the PR and between 82090f7 and 71c0f25.

📒 Files selected for processing (5)
  • .github/workflows/e2e-deploy.yml
  • gotchas.md
  • scripts/cold-connect-canary-classify.test.ts
  • scripts/cold-connect-canary-classify.ts
  • scripts/cold-connect-canary.ts

Comment thread scripts/cold-connect-canary-classify.ts Outdated
Comment thread scripts/cold-connect-canary.ts Outdated
Addresses two review findings, both real.

The sampling loop could begin a full interval with a second of budget left and then sample on the far side of it, overrunning by more than the interval itself. It now declines to start a wait the budget cannot cover, which also makes a recheck after the sleep redundant. The budget test asserts the new boundary — four samples and three waits against a 200s budget — rather than just "fewer than 14".

A misspelt numeric override became NaN, and NaN quietly disabled the things it named: `setTimeout(NaN)` fires at once, so the spacing vanished, and every comparison against a NaN budget is false, so the run never stopped itself. The three knobs are now validated up front and refuse to start, routed through the same non-blocking exit-0-with-a-warning path as every other way this canary fails to reach a verdict.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Both findings were real and are fixed in 7ed2a42.

Run budget. The loop checked the budget, then started a full interval, then sampled — so with a second of budget left it would still wait 60s and sample on the far side, overrunning by more than the interval itself. It now declines to start a wait the budget cannot cover. That also makes the suggested recheck after the sleep redundant: if elapsed + intervalMs < maxRunMs holds before sleeping, the loop-top check cannot fail after it. Comparison is now >=. The budget test asserts the exact boundary — four samples and three waits against a 200s budget — instead of the weaker "fewer than MIN_BUG_GONE_SAMPLES".

Override validation. Worth flagging how badly this one bit: Number("abc") is NaN, setTimeout(NaN) fires immediately, and every comparison against a NaN budget is false. A single typo would therefore have silently removed both the spacing this PR adds and the budget meant to bound it, while the canary carried on reporting verdicts as if nothing were wrong. All three numeric knobs are now validated up front and refuse to start, routed through the same non-blocking exit-0-with-a-warning path used everywhere else this canary cannot reach a verdict. Both failure modes verified by hand.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --no-ext-diff --format=fuller --stat 7ed2a42f
git diff --no-ext-diff --unified=80 7ed2a42f^ 7ed2a42f -- \
  scripts/cold-connect-canary-classify.ts \
  scripts/cold-connect-canary-classify.test.ts \
  scripts/cold-connect-canary.ts \
  scripts/cold-connect-canary.test.ts 2>/dev/null || true

for f in \
  scripts/cold-connect-canary-classify.ts \
  scripts/cold-connect-canary-classify.test.ts \
  scripts/cold-connect-canary.ts \
  scripts/cold-connect-canary.test.ts
do
  if [ -f "$f" ]; then
    echo "===== AST OUTLINE: $f ====="
    ast-grep outline "$f" --items all || true
  fi
done

echo "===== Numeric override and collector references ====="
rg -n -C 5 --type ts \
  'collectColdConnectSamples|MIN_BUG_GONE_SAMPLES|intervalMs|maxRunMs|Number\(|isFinite|Number\.isFinite|setTimeout|sleep' \
  scripts

Length of output: 59587


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== PR metadata ====="
gh pr view 214 --repo prisma/composer --json baseRefName,headRefName,commits,files --jq \
  '{baseRefName, headRefName, commits: [.commits[].oid], files: [.files[].path]}'

base="$(git merge-base HEAD "origin/$(gh pr view 214 --repo prisma/composer --json baseRefName --jq .baseRefName)")"
echo "===== Merge base: $base ====="
git diff --no-ext-diff --stat "$base"...HEAD
git diff --no-ext-diff --unified=50 "$base"...HEAD -- \
  scripts/cold-connect-canary.ts \
  scripts/cold-connect-canary-classify.ts \
  scripts/cold-connect-canary-classify.test.ts \
  .github/workflows/e2e-deploy.yml \
  gotchas.md

echo "===== runCanary and final exit paths ====="
sed -n '200,310p' scripts/cold-connect-canary.ts

echo "===== Changed-file references ====="
rg -n -C 3 --type ts \
  'positiveNumber\s*\(|COLD_CONNECT_(SAMPLES|SAMPLE_INTERVAL_MS|MAX_RUN_MS)|collectColdConnectSamples\s*\(' \
  scripts

Length of output: 59577


@wmadden-electric The static review found no new actionable issues.

Commit 7ed2a42f fixes both findings:

  • The collector stops at elapsed >= maxRunMs.
  • The collector does not start an interval when that interval would reach or exceed the remaining run budget.
  • The virtual-clock test now verifies the exact 200-second boundary.
  • COLD_CONNECT_SAMPLES, COLD_CONNECT_SAMPLE_INTERVAL_MS, and COLD_CONNECT_MAX_RUN_MS reject invalid, non-finite, and non-positive values.
  • Invalid overrides use the existing warning and exit-zero path.

I did not execute tests because the review sandbox does not run repository code.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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