Why a prospect was marked failed on a circuit breaker error
What this page covers
If a LinkedIn prospect on your campaign was marked status='failed' with a last_error field containing the phrase "Circuit breaker is half_open" or "Circuit breaker is open", this guide explains what that error meant pre-V15, what V15 changed (May 7, 2026), and what to expect going forward.
WarmySender is a 4-pillar outreach platform — Cold Emailing, Email Warmup, LinkedIn Outreach, and Multichannel sequences. This page is part of the LinkedIn Outreach pillar and applies to any LinkedIn action type that goes through the unified campaign engine (invites, messages, InMails, profile views, engagements).
What is the circuit breaker, and why does it fire?
WarmySender wraps every Unipile API call in a defensive circuit breaker. The breaker is named MASS-404-BURST internally (covered in detail by LL#244) and exists to protect customer LinkedIn accounts from a specific failure mode: when Unipile's upstream LinkedIn integration starts returning bursts of HTTP 404 responses, retrying every request individually can rapidly degrade into account restriction. The breaker watches the recent Unipile error rate, and when the rate crosses a threshold, it trips.
While the breaker is tripped, every Unipile call from WarmySender's side throws an error of the form Circuit breaker is half_open. Retry after 0s (or Circuit breaker is open. Retry after Ns). This is the breaker doing its job: refusing to send into a known-degraded upstream so the calls don't compound the degradation.
The key distinction: this error is internal load-shedding from our own code, not a signal from LinkedIn that the prospect is unreachable. The prospect is fine; the moment the breaker recovers, the same prospect is fully sendable.
The pre-V15 bug
Before May 7, 2026, the campaign engine's retry handler didn't distinguish internal-circuit-breaker errors from external (LinkedIn-side) failures. Both classes funneled into the same 3-strikes-and-out retry budget. So if the breaker happened to be tripped during 3 different attempts on the same prospect — even attempts spaced an hour apart — the prospect would burn its full retry budget and be marked status='failed' permanently.
This was wrong on three axes:
- Wrong outcome. The prospect was retryable the moment the breaker recovered; marking them failed permanently stranded them.
- Wrong dashboard signal. Failed-status prospects show up in operator dashboards as "this campaign has problems" — when the actual problem was a transient infrastructure trip, not a campaign issue.
- Wrong customer-support pattern. Customers who saw the failed status reasonably asked "why did my prospect fail?" — the answer turned out to be "our circuit breaker tripped 3 times during their attempts" which is not a customer-actionable problem.
Production audit on May 7 found Talha's prospect Axel had hit this exact pattern: 3 Circuit breaker is half_open errors in the view_profile retry path, all in a single afternoon during a Unipile-side incident, and the prospect was marked status='failed' as a result.
The V15 fix
V15 introduces a new error classification: circuit_breaker_internal. The engine retry handler now checks every error message against an exact Circuit breaker is prefix matcher BEFORE consulting the retry budget. When matched:
- Defer, do not strike. The step's
run_atis set toNOW() + retryAfter(clamped to [60s, 1h] + 0-30s jitter), and the prospect'slinkedinRetryCountis NOT incremented. The retry budget is preserved entirely for external (LinkedIn-side) failures. - Log structured JSON. Every breaker-defer emits a
{"source":"view_profile_circuit_breaker_defer","prospect_id":"...","action_type":"...","retry_after_s":...,"ts":"..."}log line for grep-ability and dashboard ingestion. - Reschedule via the canonical helper. The deferral goes through
rescheduleEnrollment, which clamps to the campaign's sending window, respects the campaign endDate, expires existing pending/processing send-jobs cleanly so the scheduler will mint a fresh one at the right time.
The 60-second floor is account-safety: never re-fire instantly into a still-open breaker (cascading re-trip). The 1-hour ceiling is operator-safety: never strand a prospect for hours if the breaker reports a multi-hour mass-trip — the next scheduler tick will re-check and either succeed or defer again with a fresh retry-after.
What still burns the retry budget
The 3-strikes retry budget is preserved for actual external (LinkedIn-side) failures. Examples that DO consume retry budget:
- HTTP 5xx from LinkedIn / Unipile (server errors on their side that aren't covered by the breaker — rare).
- Profile resolution failures after re-resolution (e.g. a prospect's LinkedIn URL has truly become unreachable post-import).
- Generic uncaught errors from the action wrappers (defensive bucket — gets investigated case-by-case).
Examples that DO NOT consume retry budget post-V15 (deferral path):
- Circuit breaker is half_open / open / closed — internal load-shedding (this page).
- HTTP 429 rate-limited responses — separate deferral path that's been in place since Apr 2026.
- HTTP 404 on profile lookups — re-resolution path that re-fetches the prospect's URL and retries with a 60s delay (since Apr 2026, V12).
- Daily / weekly cap reached — defers to the next reset window, no retry-count bump.
What to do if you see this in your campaign history
The bug only affected prospects who hit 3 breaker trips before V15 shipped (May 7, 2026). For those prospects:
- Open the affected campaign's prospect list. Filter by
status=failed. - Inspect each failed prospect's last_error. If it contains "Circuit breaker is half_open" or "Circuit breaker is open" or similar, that's the V15 bug.
- Re-enable the prospect. Open the prospect editor, click "Re-enable" to flip status back to
scheduledand reset the retry count. The prospect will re-enroll on the next sending window. - Bulk recovery via support. If you have many affected prospects, contact support — we can run a one-shot heal script that re-enables every prospect that failed with this exact error pattern.
Going forward (post-V15), no new prospects can fail for this reason. The breaker-defer path replaces the retry-strike path for every internal-breaker error message.
Account safety
Per CLAUDE.md, account safety is the top invariant of the WarmySender platform. The V15 retry-classifier change is strictly safety-improving:
- It cannot cause a duplicate send. The deferral path goes through
rescheduleEnrollmentwhich expires pending/processing send-jobs cleanly; the next attempt mints a fresh job through the standard scheduler tick → cap → ramp → cooldown machinery. - It cannot bypass a cap. The deferred attempt re-enters every existing safety gate at execution time.
- It cannot accelerate sends. The deferral is by definition slower than the previous behavior (immediate retry-with-strike); the only behavioral change is "do not consume retry budget on internal errors", which is strictly less aggressive.
- It cannot ever cause a re-trip. The 60-second floor on retry-after is the safety guarantee: even if the breaker reports retry-after=0s (the fast-half-open case), we wait at least 60 seconds before the next attempt.
Related documentation
- LinkedIn rate limits and ramp schedule — covers the per-account daily/weekly limits that operate independently of the breaker.
- Why no InMails sent — credits exhausted — covers the V15 InMail credit-exhausted breaker (different breaker, related pattern).
- Why my LinkedIn account shows Connected but is not sending — covers the V15 split-state fix (different root cause; helpful diagnostic if you're triaging multiple V15 issues at once).
- Why a connection accept is missed — for the matcher / bridge-miss family (different root cause).
Frequently asked questions
What's the difference between the MASS-404-BURST circuit breaker and Sales Nav credits-exhausted?
They're two different breakers covering different failure modes:
- MASS-404-BURST circuit breaker (LL#244, this page) — INTERNAL: protects against Unipile-upstream HTTP 404 bursts that could otherwise restrict LinkedIn accounts. Trips when Unipile's recent 404 rate crosses a threshold. Auto-recovers when the rate drops.
- Sales Nav credits-exhausted breaker (LL#339, separate page) — EXTERNAL/per-account: refuses InMail dispatch when LinkedIn returns "Not enough credits" on a Sales Nav account. Auto-clears on calendar-month rollover or recent-success evidence.
Both are refusal-to-send patterns, but they fire in different code paths and recover via different signals.
Why does the deferral take up to an hour?
The breaker recovery time is set by the upstream incident's severity. For a fast-half-open trip (retry-after=0s), we still floor at 60s to avoid re-tripping immediately. For a full-open trip with a known multi-minute recovery (retry-after=300s), we honor the breaker's reported time. Anything over 1 hour is clamped to 1 hour because the next scheduler tick will re-check anyway, and we don't want to strand the prospect if the breaker recovers earlier.
Will my campaign appear paused while the breaker is tripped?
No — the campaign continues running. Individual prospects whose attempts hit the tripped breaker are deferred ~60s-1h. The campaign-level status remains running, and the dispatcher will continue to enqueue the next due prospects on the next sending tick.
Can I see when the breaker last tripped?
Operators can grep production logs for "source":"view_profile_circuit_breaker_defer" to see every defer with its retry-after and timestamp. We don't currently expose this directly in the customer dashboard — if your campaigns are seeing systematic deferrals, contact support and we can run an audit query against the campaign-event log for you.
Does this apply to InMail / message / invite steps too, or only view_profile?
It applies to every LinkedIn action type that goes through the unified campaign engine: invites, messages, InMails, view-profile, engage-post, endorse-skill. The classifier matches on the error message prefix (Circuit breaker is ) and the retry-handler block is shared across all action types. The log line says view_profile_circuit_breaker_defer historically because the first audit case was a view_profile step (Axel), but the same defer-not-strike behavior now applies to all action types.
Do I need to do anything to get this fix?
No — the V15 fix shipped May 7, 2026 and applies automatically to every LinkedIn campaign on the platform. Pre-V15 prospects that had already been marked failed are not auto-recovered (we don't unilaterally flip customer-visible state); use the "Re-enable" button on each affected prospect, or contact support for bulk re-enable.