Repository navigation
fix(backend): stop logging an error when no trial offer is configured - #14802
Conversation
The card-required-trial-offer flag's disabled variation is an object,
{"enabled": false}, not null. get_trial_offer treated only None as "no
offer", so the disabled variation reached TrialOffer.model_validate,
failed extra="forbid" and logged an ERROR on every eligibility check.
Treat a payload that never claims to be an offer -- no version key, or
not an object at all -- as absence, at DEBUG. A payload that carries
version and fails validation is still an ERROR: that one is a broken
offer someone needs to fix.
Return values are unchanged for every possible flag value; only the log
level moves.
Co-Authored-By: Claude Opus 5 (Claude Code) <noreply@anthropic.com>
🤖 EvidenceThe premise, read from LaunchDarkly
The flag is on with no rules and no targets, so its fallthrough serves variation 0 to every user. MutationRestoring the original The four object/bool cases fail because the unpatched code emits the ERROR the PR exists to remove; the The log assertions attach a plain Test runsNote for whoever merges second#14799 also touches |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review. 📜 Recent review details🔇 Additional comments (2)
Walkthrough
ChangesTrial offer flag handling
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to Disabled or non-offer flag values now avoid erroneous validation logs without changing handling of malformed configured offers. The change is ready to merge. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🔴 Merge Conflicts DetectedThe following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.
Summary: 1 conflict(s), 0 medium risk, 0 low risk (out of 1 PRs with file overlap) Auto-generated on push. Ignores: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #14802 +/- ##
=======================================
Coverage 83.65% 83.65%
=======================================
Files 4054 4054
Lines 305117 305146 +29
Branches 27672 27670 -2
=======================================
+ Hits 255241 255270 +29
Misses 45208 45208
Partials 4668 4668
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
/review |
|
🤖 Review of |
There was a problem hiding this comment.
✅ Verdict: Approved — No findings require changes before approval; this review counts as an approval.
A well-scoped log-severity fix that downgrades a false-positive ERROR to DEBUG when the trial-offer flag serves its disabled variation, while preserving ERROR alerts for genuinely broken offers. Return values are provably unchanged for all inputs, both callers are unaffected, and targeted tests cover both branches.
Risk level: low | Human review: not required | Duration: 189s | Reviewed: 0ab9a247
GitHub CI on reviewed head: success
Validation and specialist details
Specialist Reports
| Specialist | Status | Summary |
|---|---|---|
| security | ✅ PASS | Logging-level-only change in get_trial_offer that narrows the pre-validation guard; validation is not weakened, return values and enrollment gating are provably unchanged, and no user-controlled data or secrets are exposed. |
| architect | ✅ PASS | Minimal, well-scoped log-severity fix that narrows a raw LaunchDarkly flag value before validation, matching an existing sibling pattern with return behavior provably unchanged and good test coverage. |
| performance | ✅ PASS | Two-line guard on a hot path that skips Pydantic validation for the disabled flag variation and cuts steady-state error-pipeline load; no performance regressions found. |
| testing | ✅ PASS | Small, well-targeted log-level fix with strong, regression-catching tests that pin both the new DEBUG path and the preserved ERROR path; no blocking defects. |
| quality | ✅ PASS | Clean, well-tested log-level fix that is provably behavior-preserving and follows the established raw-flag-narrowing convention; no defects found. |
| product | ✅ PASS | Correct, well-scoped logging-level fix that silences false-positive ERRORs for the flag's disabled variation while preserving ERROR alerts for genuinely broken offers, with return values provably unchanged for all inputs. |
| discussion | ✅ PASS | CI is green on head 0ab9a24 and there are no open human-reviewer requests. |
| ui-reviewer (local) | ✅ PASS | Correct, well-tested backend logging fix: a non-offer flag value is now DEBUG instead of ERROR while return values and caller behavior are provably unchanged. |
| ui-reviewer (hosted) | ✅ PASS | Correct, minimal backend fix that downgrades a benign non-offer flag payload from ERROR to DEBUG while preserving ERROR for genuinely broken offers; return values are unchanged for all flag values, no caller behavior changes, and the 43 targeted tests pass. |
Why / What / How
Why:
get_trial_offerlogsInvalid card-required-trial-offer; refusing trial enrollmentat ERROR on every eligibility check in production — roughly four lines per 20 minutes intoplatform_server_errorsand Sentry since the v0.8.0 rollout. Nothing is broken: thecard-required-trial-offerflag simply has two variations, and the one production serves is{"enabled": false}. That object is notnull, so it reachedTrialOffer.model_validate, failedextra="forbid", and took the error path. Dev serves the real offer, so the noise never showed there.What: a flag payload that never claims to be an offer — no
versionkey, or not an object at all — is now read as "no offer configured" and logged at DEBUG. A payload that carriesversionand still fails validation keeps its ERROR: that one is a broken offer someone has to fix.How:
versionis the discriminator becauseTrialOfferrequires it with no default, so every offer that can validate carries it and no disabled variation does. The guard sits beforemodel_validate, matching how the two sibling LaunchDarkly JSON-object consumers (_fetch_cost_limits_flag,get_tier_stripe_price) narrow a raw flag value.Return values are unchanged for every possible flag value —
Nonein,Noneout, exactly as before, for the disabled variation and for a broken offer alike. Only the log level moves, so no caller behaviour changes.Changes 🏗️
subscription_trial_config.py:get_trial_offerreturnsNoneat DEBUG for a non-offer flag value instead of falling through to the ERROR handler.subscription_trial_config_test.py: the disabled variation and the other non-offer shapes produce no record above DEBUG; aversion-carrying offer that fails validation still produces exactly one ERROR.The LaunchDarkly side is unchanged and is not part of this PR.
Verified
I ran
backend/data/subscription_trial_config_test.py(43 passed),backend/util/architecture_test.pyandbackend/blocks/test/test_block.py(1755 passed, 84 skipped), andruff checkon both changed files. I read production's flag configuration through the LaunchDarkly REST API to confirm the premise rather than inferring it. I mutated the fix back to the originalif raw is Noneshape and confirmed the new tests fail — 5 failed / 38 passed. Evidence in a comment below.The two consumers of
get_trial_offer(subscription_trial_routes.py,subscription_trial_checkout.py) both branch onis None; I reasoned about them from the call sites rather than executing their suites, on the grounds that the return value is provably unchanged.Agents and large language models used
Claude Code with Claude Opus 5
Checklist 📋
For code changes:
{"enabled": false}returnsNoneand logs nothing above DEBUGNone,False,{}and{"enabled": true}behave the same wayversionthat fails validation still logs exactly one ERROR and returnsNone