fix(billing): move auto-intro metadata from schedule create to update#1389
Merged
jeanduplessis merged 4 commits intomainfrom Mar 23, 2026
Merged
fix(billing): move auto-intro metadata from schedule create to update#1389jeanduplessis merged 4 commits intomainfrom
jeanduplessis merged 4 commits intomainfrom
Conversation
Stripe rejects metadata on subscriptionSchedules.create when from_subscription is set, causing 100% failure on auto-intro schedule creation since commit df6ff4c. Move the origin metadata to the subsequent update call and add error recovery to release half-created schedules if the update fails.
Contributor
Code Review SummaryStatus: 1 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments: None. Files Reviewed (3 files)
Fix these issues in Kilo Cloud Reviewed by gpt-5.4-20260305 · 648,289 tokens |
Stripe rejects metadata on subscriptionSchedules.create with from_subscription, so a window exists where a schedule has no origin tag. Make ensureAutoIntroSchedule and handleAutoIntroCreateRace claim untagged schedules as auto-intro and repair them, closing the race window where concurrent readers would skip orphaned schedules.
…edules User-initiated plan switches and kilo-pass changes also create from_subscription schedules without metadata.origin. Without a phase-count check, ensureAutoIntroSchedule could incorrectly claim and repair these as auto-intro schedules. Require phases.length === 1 (the from_subscription default) since createAutoIntroSchedule sets metadata and phases atomically in the same update call.
alex-alecu
reviewed
Mar 23, 2026
alex-alecu
reviewed
Mar 23, 2026
…elper Add metadata.origin to user-switch and kilo-pass-switch schedule creation paths so they are never mistaken for auto-intro orphans. Extract the duplicated orphan-detect-and-claim logic into a shared claimIfAutoIntro() helper to prevent the heuristic from drifting between ensureAutoIntroSchedule and handleAutoIntroCreateRace.
alex-alecu
approved these changes
Mar 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Problem
createAutoIntroSchedulepassesmetadata: { origin: 'auto-intro' }alongsidefrom_subscriptioninstripe.subscriptionSchedules.create(). Stripe rejects this combination with HTTP 400, causing 100% failure on auto-intro schedule creation since commit df6ff4c (PR #1362). Affected intro-priced subscriptions never get the scheduled transition to the regular standard price.Solution
metadatafrom thesubscriptionSchedules.create()call where Stripe forbids it alongsidefrom_subscription.metadata: { origin: 'auto-intro' }to the subsequentsubscriptionSchedules.update()call, which already sets phases and end behavior.updatecall in error recovery: if it fails, the half-created schedule is released so retry paths (Sweep 5 cron) can start fresh instead of encountering an untagged schedule they cannot identify.ensureAutoIntroScheduleandhandleAutoIntroCreateRacenow detect and claim untagged orphan schedules (create succeeded, update never ran) by tagging them asauto-introand running repair.phases.length === 1guard to the untagged-schedule claiming logic to avoid incorrectly claiming user-initiated plan switches or kilo-pass changes, which also createfrom_subscriptionschedules withoutmetadata.originbut always have 2+ phases after their update completes.Why this approach
The metadata must live on the schedule for downstream code to distinguish auto-intro schedules from user-initiated plan switches. Moving it to the
updatecall is the minimal fix — the only alternative would be two separateupdatecalls (one for metadata, one for phases), which doubles API calls for no benefit since both fields can be set atomically in oneupdate.Related issues
feat(billing): replace coupon with intro pricing and enable promo codes)Verification
pnpm typecheck— passpnpm test -- src/routers/kiloclaw-billing-router.test.ts— 59/59 passVisual Changes
N/A
Reviewer Notes
createandupdatewhere the schedule exists without metadata. Three layers of mitigation: (1) release on update failure, (2) race recovery paths claim untagged orphans, (3)phases.length === 1guard prevents incorrectly claiming user-initiated schedules.ensureAutoIntroSchedule, which invokes the now-fixedcreateAutoIntroSchedule.