Skip to content

feat(cli): billing shows the price and pays from the terminal on Enter - #7085

Closed
WaterrrForever wants to merge 9 commits into
mainfrom
miao/billing-pay-from-terminal
Closed

WaterrrForever wants to merge 9 commits into
mainfrom
miao/billing-pay-from-terminal

Conversation

@WaterrrForever

Copy link
Copy Markdown
Collaborator

Takes over #7068 from @miguel-heygen. Thanks Miguel: the feature and its tests are his two commits, kept as he wrote them. This PR rebases them onto main and adds one fix.

What

hyperframes billing subscribe and billing top-up show what you are buying and take you to payment.

  1. Opens the pricing page (the credits page now opens at the credit packs).
  2. Prints the price: Pro per month, or "this run needs N credits" with the cost of the packs that cover it.
  3. At a terminal, "Press Enter to pay" creates the checkout with the CLI's own sign-in and opens Stripe. With no terminal (CI, a coding agent, Claude Code's TUI), it prints the checkout link.
  4. Then it waits for the plan or credits to land, as before.

This closes two bug bash items: the top-up page that offered no credit packs, and the paywall that could not be opened from inside the TUI.

Changes on top of #7068

  • Rebase: main's fix(cli): reverse on Free runs billing subscribe credited to the run #7046 already had --feature / --session and the reverse start refusal that runs billing subscribe, so both conflicts keep main's side.
  • No second checkout: the pricing page opens before "Press Enter to pay". A person who bought on the page and then pressed Enter got a second Stripe checkout. The plan is now read again on Enter, and nothing opens if it is already covered.
  • Offline tests: billing.test.ts now mocks the offer and the checkout; before, it went to the network.
  • Docs: the billing section in docs/packages/cli.mdx describes the price line, Enter, and the printed link.

Tests

  • billing.test.ts:
    • price and link printed when unattended
    • Enter opens Stripe
    • no checkout when already paid on the page (fails without the fix)
    • feature and run carried on the page link
  • plan.test.ts: Miguel's 7 tests are unchanged.
  • src/commands/reverse: passes.
  • With billing.test.ts and plan.test.ts: 27 passed.
  • Lint, format and typecheck are clean.

Live walk (prod, Pro account, no terminal attached)

billing top-up --needed 510000 --tool reverse:

  • It opened pricing?from=cli&needed_credits=510000&tool=reverse#packs.
  • It printed "This run needs 510,000 credits. 9,000 credits: $9.00", read from the live offer (8,312 short, so 9 packs).
  • The checkout was refused: "Your current role (Creator) does not have permission…". The CLI printed that and kept waiting, as intended.

Not walked:

  • A successful Stripe checkout, which needs an account allowed to buy.
  • The Enter path in a real terminal.

🤖 Generated with Claude Code

miguel-heygen and others added 3 commits November 3, 2026 19:09
…sts stay offline

The pricing page opens before "Press Enter to pay", so a person who bought there and then
pressed Enter got a second Stripe checkout. The plan is read again on Enter first.
billing.test.ts mocks the offer and checkout, which otherwise went to the network.
@mintlify

mintlify Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Nov 3, 2026, 4:07 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent AI-agent review of 17dc9a3762919a9a247ee4be2fc659cf4cb537e2.

The successful-read guard in packages/cli/src/commands/billing.ts:60 covers payment before Enter, and packages/cli/src/heygen/plan.ts:135 restricts checkout URLs to Stripe HTTPS. I read the five-file diff, billing/plan implementations and tests, and their auth/browser/terminal consumers. There were no prior review findings in the initial capture.

Blocker — an unreadable plan still opens a second checkout (packages/cli/src/commands/billing.ts:60-61). After browser payment, if the Enter-time subscription read times out or returns 401/500, readPlan() returns null (packages/cli/src/heygen/plan.ts:28-46). done(null) is false, so the next line creates and opens another checkout. An unknown plan must not authorize that action: require a verified, non-mock unmet plan, otherwise keep the existing page/poll or report a retryable read failure. Add a regression asserting zero checkout calls after an unreadable recheck. This is a source-supported duplicate-checkout path, not a claim that a second charge was executed.

Important — partial top-ups retain the old quantity (packages/cli/src/commands/billing.ts:47,60-61). With --needed 10000, initial balance 0, and a browser purchase bringing the balance to 6000 before Enter, the guard still creates a 10000-credit checkout using the old deficit. Recompute the missing credits from the fresh plan and show the revised quantity/price before confirmation.

Source tracing only: no local tests, real checkout, payment, browser or account mutation performed. Initial CI was still running; no current test-pass/native execution claim. Prior #7068/#7046 assurances are not inherited evidence. No authority to close #7068 or clear rollout/queue/merge gates.

Verdict: REQUEST CHANGES
Reasoning: The happy-path recheck is present, but an unknown plan still permits the second checkout it is intended to prevent. The partial-top-up path also prices the stale deficit.

— Magi (AI-agent technical review, not human sign-off)

…riced again

Review on #7085: on Enter, a plan read that failed (timeout, 401, 500) came back null,
counted as not done, and opened a second checkout after a browser payment. It now opens
nothing and the wait carries on. A partial top-up on the page before Enter kept the old
deficit: the price is now worked out from the fresh balance, and when it changed the new
price is shown and Enter is asked again before any checkout.
@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

Thanks for the review, both fixed in 13a8245.

  • Unreadable plan on Enter: a null or mock plan now opens no checkout. It prints "Could not read your plan, so no checkout opened. Finish on the page." and the wait carries on. Regression test: opens no checkout when the plan cannot be read on Enter (zero checkout calls).
  • Partial top-up before Enter: the deficit is worked out from the fresh balance. When the price changed, the new price is printed and Enter is asked again; the checkout is created only for a price the person has seen. Regression test: shows the new price and asks again when the balance moved before Enter (--needed 10000, balance 0 → 6000: prints $4.00, two Enters, checkout for 4,000).

Both tests fail on 17dc9a3 and pass here; billing and plan tests: 13 passed.

@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

@miguel-heygen thanks again for the careful review! Both points are addressed in 13a8245 (details in my comment above), and CI is green now. Whenever you have a moment, could you take another look? Thank you!

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 45ba9968efea7a721be47e94ac0de55349003f82 (the live head, newer than the requested 13a8245f4).

Magi's Enter-time findings are fixed: null/mock rechecks return without checkout, and a partial top-up recomputes the deficit, prints the new amount and requires another Enter (packages/cli/src/commands/billing.ts:51–65). Source inspection of packages/cli/src/heygen/plan.ts:135–142 confirms the returned URL is limited to HTTPS checkout.stripe.com. The broader checkout flow still has three Important gaps:

  1. Unknown plans still permit unattended checkout (billing.ts:78–85). This branch never applies the null/mock guard. An initial subscription read returning HTTP 500 still produces a subscription checkout POST; a failed read with mock pro:0 produces a 10-block add-on POST for --needed 10000. This includes coding-agent/TUI callers. Fix: require a verified non-mock plan before either branch creates a checkout, recheck coverage after opening the pricing page, and otherwise leave the existing page/poll with retry guidance. Add null/mock unattended controls asserting zero POSTs.

  2. An unavailable Pro quote still permits checkout (billing.ts:59–61,78–85; packages/cli/src/heygen/plan.ts:151–165). readOffer() can return {} after a failed/malformed read. The price line is then undefined; Enter compares undefined with undefined and creates the Pro checkout without showing a price. The unattended branch does likewise. Fix: require the selected offer and a valid displayed price before preparing checkout; otherwise print a retryable offer-read failure and keep the page path. Test both attended and unattended missing-quote cases.

  3. Paying on the page without Enter no longer resumes the command, and timeout never starts (billing.ts:51,137–145). The new awaited prompt runs before the polling loop/deadline. With Free initially and Pro ready on the next read, a held Enter and --timeout 1 leave the command pending after 10 seconds, with only the first subscription read performed. Releasing Enter allows completion with no checkout. Fix: poll coverage and enforce the deadline alongside a cancellable prompt; close it when covered/timed out and prevent a late Enter from opening checkout. Add held-prompt payment and timeout tests.

Validation: full root build passed; bun run --cwd packages/cli test src/commands/billing.test.ts src/heygen/plan.test.ts src/commands/reverse reports 29 passed. Seven separate actual-module offline probes exercise the failures above plus known-plan and attended-null controls; I inspected and reran all seven. Browser/auth/readline/fetch are mocked; billing, readPlan/readOffer and createCheckout execute as written. These prove checkout requests and blocked completion, not a second charge. The separate probes are in my review workspace at review-artifacts/7085/adversarial/probe.test.ts, run from that directory with <worktree>/node_modules/.bin/vitest run --config vitest.config.mjs. No live browser/readline interaction, purchase, Stripe session or account mutation was performed.

Need: yes · Size: reviewed five-file PR · Reuse: existing plan/offer guards and polling loop · Simpler: share fail-closed checks across both checkout paths · Structure: keep prompt and payment polling under one completion/deadline lifecycle
Risk: medium — prepares payment sessions and must retain reliable refusal and completion behavior.

Verdict: REQUEST CHANGES
Reasoning: The two earlier Enter-time cases are fixed, but unattended unknown state, missing quotes and the blocking prompt remain verified failures in the new checkout flow.

— hf-review

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review of #7085 at current head 45ba9968efea7a721be47e94ac0de55349003f82 (the requested 13a8245f4 is its parent; the intervening commit renames/comment-refactors billing). Magi's Enter-time fixes are present: a null/mock fresh plan opens no checkout, and a changed credit balance is re-priced and asks for Enter again. I do not find proof that a checkout creation itself charges twice; the server inventories subscriptions/open sessions and applies its own idempotency key. Still not ready to approve. Rames's current-head review already details three independently corroborated paths, so I have not repeated them as inline comments: an attended user paying on the browser page is stuck at an unanswered readline prompt and cannot reach the command's polling/timeout; the unattended path creates a checkout link from the old or unknown balance without a fresh covered-plan check; and a missing Pro offer lets undefined === undefined create a checkout with no displayed price. My separate inline finding addresses a changed valid Pro quote while Enter is pending: the CLI never refreshes it, and the Pro checkout request does not bind the shown price. These are checkout-link/consent and liveness defects, not an observed automatic second charge or refund failure. Exact-head pull_request CLI test shard 1 executed 4,045 passing tests; build/lint/typecheck checks ran, with no live payment test. No payment, local test, merge, or checkout session was created by this review.

— Jerrai

Comment thread packages/cli/src/commands/billing.ts Outdated
done: Done,
needed?: number,
) {
const offer = await readOffer();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Pro Enter confirms a quote that can change before checkout. This offer is fetched only once, before an arbitrarily long rl.question wait. The Enter-time guard re-reads the plan/balance, not the offer; priceLine keeps the old offer.pro.amount, while createCheckout("plans", ...) POSTs only {plan_name, period, request_id}. The server resolves its own current Stripe price at checkout creation, so if the monthly price changes while the person reads/pays on the page, the terminal can still show and 'confirm' the old amount and open a checkout at a different amount. Stripe's final page shows the actual terms, so this is a stale terminal price disclosure, not proof of an automatic charge. Please refresh/reconfirm the offer on Enter and ideally bind the expected amount/currency to checkout creation (or clearly defer price consent to the Stripe page); add a changed-offer case. Rames's earlier review covers the separate missing-offer/undefined-line path.

… no longer blocks the wait

- An unreadable or stand-in plan at the start opens no checkout, attended or not.
- A missing price opens no checkout, attended or not.
- The Enter prompt and the payment poll run together: paying on the page ends the
  prompt and the command, the timeout applies, and a late Enter opens nothing.
@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

Thanks @jrusso1020 and Jerrai, all four points are fixed.

fbb6487, Rames's three:

  • Unknown plan at the start: an unreadable (null) or stand-in (mock) plan opens no checkout, attended or unattended. It prints "Could not read your plan…" and the page and poll go on as before.
  • Missing quote: with no price line, it opens no checkout on either path and prints "Could not read the price…".
  • Enter blocked the wait: the Enter prompt and the payment poll now run together. Paying on the page ends the prompt (aborted through its signal) and the command, --timeout applies while the prompt is open, and an Enter that lands after that opens nothing.

20eaced, Jerrai's inline: on Enter it re-reads the offer as well as the plan. If the price line changed, it prints the new one and asks for Enter again. If the price can't be read, it opens no checkout. The Pro checkout body still carries no amount, because the server picks the Stripe price. So the terminal is never ahead of the server, and Stripe's page states the final terms.

8 new tests: unreadable or stand-in plan, attended and unattended; missing price, both paths; a held prompt with payment on the page; a held prompt with timeout; a changed Pro price. They fail on 45ba9968e and pass now (14 billing + 7 plan). Could you take another look? Thank you!

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 20eaced0e858e6a68e7f79a25e74408b3baa0a15, delta from my reviewed 45ba9968e.

The initial null/mock-plan and missing-price guards now cover both attended and unattended checkout. Enter refreshes the plan and offer, reprints a changed amount and asks again; an unanswered prompt no longer blocks polling or its deadline. The new tests and independent controls verify those earlier cases, including Jerrai's changed-price case.

Important — cancellation does not reach in-flight offer/payment work (packages/cli/src/commands/billing.ts:56–64,98–105,173–174). Both paths can finish readOffer() after the poll has already observed payment or timed out. There is no aborted-signal check between that await and createCheckout(). With the Enter-time offer held, the poll reads Pro and aborts the signal; resolving the offer at 10 seconds still sends a checkout POST. The same occurs after timeout. The unattended initial-offer path also creates checkout and prints its link after completion. The held delay is below the helper's real 15-second request timeout. These prove late checkout preparation, not a charge.

await paying also postpones command completion until the outstanding plan/offer/checkout request returns, even after the payment poll has succeeded. Independent held-plan/offer/checkout controls observe the paid poll complete while the command remains pending until the held response is released. The prompt-only tests cannot expose this: their HTTP answers resolve immediately.

Fix: propagate the shared abort signal into the pending plan/offer/checkout requests (combined with their existing request deadlines), recheck it after each await and immediately before creating checkout, and suppress late links. Ensure completion/timeout cancels and drains the payment task promptly. Add held initial-offer and Enter-offer tests where the poll wins through both payment and timeout; use an abort-aware HTTP seam and assert zero subsequent checkout POSTs plus prompt completion without manually releasing the held response. Retain the current refusal/repricing controls.

Validation: full root build passed. The focused billing/plan/reverse suite passes 37 tests. A separate fresh adversarial pass runs actual billing/readPlan/readOffer/createCheckout with offline HTTP/auth/browser/readline seams: 16 controls pass, independently inspected and rerun by me, covering the late requests plus the repaired refusal, price, held-prompt and late-Enter cases. Scratch invocation: from review-artifacts/7085/r2/adversarial, /home/ubuntu/workspaces/hf-review-7085-r2-20261103/node_modules/.bin/vitest run --config vitest.config.mjs. No live browser/terminal, Stripe session, payment or account mutation was performed.

Need: yes · Size: bounded re-review delta · Reuse: shared AbortSignal and existing request deadlines · Simpler: one cancellation lifecycle across prompt, poll and HTTP · Structure: keep checkout preparation under that lifecycle
Risk: medium — late work can prepare payment after the command has already observed completion or timeout.

Verdict: REQUEST CHANGES
Reasoning: The previous refusal and prompt-only cases are repaired; cancellation during an in-flight offer still permits a late checkout and delays completion.

— hf-review

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed #7085 at 20eaced0e858e6a68e7f79a25e74408b3baa0a15. The previous explicit cases are fixed: null/mock initial and Enter-time plans and missing offers do not create checkout; an attended partial top-up recomputes the deficit and asks again; the Pro offer is re-read on Enter and a changed displayed price asks again; a browser payment while readline is merely waiting can now abort the question and reach polling. One shared async lifecycle blocker remains, independently confirmed at source; Rames's review has the detailed held-response reproductions, so I am not duplicating inline comments. payOrShowTheNewPrice checks stop.aborted after readPlan but not after awaiting readOffer; if polling observes a browser payment or timeout during that await, line 64 still POSTs a new checkout, only suppressing the subsequent browser open. In the unattended branch, the initial verified-but-unmet plan can become covered while readOffer waits, yet line 105 still POSTs/prints a stale checkout link after polling has ended. The command then awaits paying, so --timeout does not bound this outstanding work. These are checkout-session creation and command-liveness defects, not proof of an automatic charge. Recheck coverage/cancellation immediately before side effects and have the offer/POST path honor the lifecycle deadline; test completion/timeout while offer reads are held. At review time the exact-head pull_request run 41675796866 was still in progress; its CLI shard/lint/typecheck/build had success statuses, but I did not count uncompleted-run logs as executed evidence. No payment, checkout request, local test, merge, or release action by me.

— Jerrai

…s once the wait ends

Paying on the page or the timeout aborts the pending requests, so no
checkout is created or link printed after the command has seen payment,
and the command ends without waiting for a held response.
@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

Thanks @jrusso1020 and Jerrai, fixed in f243f86.

readPlan, readOffer and createCheckout take the shared stop signal, combined with their own request deadlines (AbortSignal.any). Billing passes it into every Enter-time and initial read and into the checkout POST, and checks it after each await, including right before createCheckout. So once the poll sees payment or times out, the pending reads abort. No checkout is created, no link is printed or opened, and await paying returns at once instead of waiting for a held response.

New tests use an abort-aware readOffer seam that holds until its signal fires (then answers {} like the real one), with no manual release. They cover the unattended initial offer with payment winning, the Enter-time offer with payment winning, and the Enter-time offer with timeout winning. Each asserts zero checkouts and that the command completes. All 3 hang and fail on 20eaced0e; 24 billing + plan tests pass now.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at f243f860c8ca670de98b26adaf24f53e0f7fd083, delta from my reviewed 20eaced0e.

The remaining HTTP cancellation finding is fixed. Billing passes its stop signal to Enter-time plan/offer reads, the initial offer read and both checkout paths; the helpers combine it with their existing request deadlines (packages/cli/src/heygen/plan.ts:28–36,88–93,146–173). Post-await guards suppress checkout preparation after a held read, and both link-print/browser-open paths suppress late output (commands/billing.ts:52–66,91–122). Caller tracing confirms existing auth/status and reverse plan reads retain their optional-signal behavior.

The held initial offer aborts promptly and held Enter offer aborts promptly controls complete on payment or timeout without manually releasing the response and create no subsequent checkout. They use --timeout 1 and verify settlement at the first two-second polling tick under controlled timers. Held Enter plan reads also drain. A POST already sent before stop is aborted and produces no late link; this does not establish that server-side checkout creation was undone. The prior unknown/mock-plan, missing-offer, repricing, unanswered-prompt and late-Enter controls were rerun in this round and remain satisfied.

Validation: full root build passed. bun run --cwd packages/cli test src/commands/billing.test.ts src/heygen/plan.test.ts src/commands/reverse reports 40 passed. A fresh independent pass supplies 24 actual-module offline controls, inspected and rerun by me from review-artifacts/7085/r3/adversarial with /home/ubuntu/workspaces/hf-review-7085-r3-20261103/node_modules/.bin/vitest run --config vitest.config.mjs. HTTP seams honor cancellation. Two of those 24 controls use native Node fetch against a loopback server: pre-aborted checkout reaches zero server requests, and aborting a held offer response body returns promptly.

Limits: browser/readline/auth are mocked in lifecycle controls; no live HeyGen/Stripe call, purchase, account mutation or real terminal interaction. Credential resolution/refresh and the existing initial/poll plan reads are not hard-cancelled by this change; held-auth seams still await release. Shared refresh deduplication and unsignalled polling predate this PR, so this review does not claim a universal hard deadline for authentication. No new Important regression found in the delta.

Need: yes · Size: bounded cancellation fix · Reuse: shared AbortSignal and existing request deadlines · Simpler: existing helpers carry the lifecycle signal · Structure: fine
Risk: medium — payment-session preparation, now guarded across the exercised HTTP cancellation boundaries.

Verdict: APPROVE
Reasoning: The previously reproduced late-request and late-link paths are repaired, while refusal, price confirmation and page-payment completion still pass the focused and independent controls.

— hf-review

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At f243f86, the previous held-readOffer cancellation window is repaired and covered by the new tests. One downstream unbounded OAuth-refresh path remains: the new wait for the payment task can still outlive a payment or CLI deadline before the abortable fetch starts, as described inline. The exact-head CLI test shards and lint have since executed successfully on the rerun; the prior lint cancellation happened before lint execution, not as a lint finding. I did not perform a live checkout, local tests or a merge. — Jerrai

now = await readPlan();
}
stop.abort();
await paying;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new stop signal reaches the plan/offer/checkout fetch, but each packages/cli/src/heygen/plan.ts request first awaits resolveCloudAuthHeaders() while constructing headers. On an expired OAuth token, cloud/auth.ts::refreshIfNeeded awaits refreshTokens, whose token-endpoint fetch (auth/oauth.ts:384–391) has no abort signal or timeout. If that refresh stalls during an offer read or checkout, payment or the deadline calls stop.abort(), yet this await paying still waits indefinitely, so the command cannot report completion/timeout. The held-offer tests replace readOffer and bypass credential resolution. Please bound/cancel the auth-refresh path as part of this wait, and add a regression that parks credential resolution while the timeout/payment event arrives. This is distinct from server-side checkout creation after a POST is sent, which abort cannot undo.

…stop

The token refresh request gets a 20 s deadline, for every caller, and the
billing reads race the sign-in against the stop signal, so payment or the
timeout ends the command even while the token is still refreshing.
@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

Thanks Jerrai, fixed in 728bccb.

  • The refresh itself is bounded: refreshOnce's token-endpoint POST now has a 20 s AbortSignal.timeout, for every caller. I didn't tie it to billing's stop signal, because overlapping callers share that one in-flight refresh.
  • Billing doesn't wait on it: plan.ts races resolveCloudAuthHeaders() against the stop signal for all three requests. Once payment or the deadline aborts, readPlan/readOffer return at once (null/{}), createCheckout rejects before any POST, and await paying finishes.

Regressions: plan.test.ts parks credential resolution, aborts, and expects all three to settle at once. oauth.test.ts checks that the refresh POST carries a signal. Both fail on f243f860c. Billing, plan, auth and cloud suites pass, except offload.undecodableTrack.test.ts, which fails the same way on clean code here (local ffmpeg).

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 728bccb99a688b7abc5341e7ce7a9bbef8ed6c03, delta from f243f860c plus the complete billing/plan and auth-refresh contract.

Jerrai's credential-resolution finding is fixed: all three billing helpers race authentication against the shared stop signal (packages/cli/src/heygen/plan.ts:31–46), while shared token refresh has its own 20-second HTTP deadline (packages/cli/src/auth/oauth.ts:359,385–392). A stopped billing task no longer waits for credential refresh or submits a late checkout. Other callers can continue sharing that refresh; native Node tests verify rotation persistence and retry after both held headers and a held response body.

The earlier unreadable/mock-plan and missing-price refusals, Enter-time coverage check, partial-credit/Pro repricing and reconfirmation, held prompt payment/timeout, late Enter, and held plan/offer/checkout cancellation controls remain satisfied.

Validation: full root build passed. Focused billing/plan/reverse/auth/cloud/commands-auth suites: 439 tests passed. Independent actual-module controls: 27 passed, including native Node fetch against a loopback token server for the 20-second deadline and shared-refresh cancellation. No live HeyGen/Stripe call, purchase, account mutation or real browser/readline interaction.

Existing initial/poll plan reads remain unsignalled, so this is not a claim that --timeout bounds every authentication/network wait to an exact wall-clock deadline. Aborting a POST also does not establish that server-side checkout creation was undone.

Need: yes · Size: bounded re-review delta · Reuse: existing shared token refresh and billing stop signal · Simpler: authentication race plus refresh deadline · Structure: fine
Risk: medium — payment-session creation and shared authentication cancellation.

Verdict: APPROVE
Reasoning: The outstanding authentication wait now drains on billing cancellation and shared refresh is bounded, while earlier refusal, consent and completion cases remain verified.

— hf-review

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at 728bccb99a688b7abc5341e7ce7a9bbef8ed6c03. readPlan, readOffer, and createCheckout now race credential resolution against the billing stop signal; OAuth refresh itself is bounded to 20 seconds. A parked sign-in can no longer hold the offer/checkout task after payment or deadline, and the regression exercises that boundary rather than only mocking readOffer. The original plan read and polling reads are outside this stop controller and may add up to the refresh bound to the command’s nominal timeout; this remains a disclosed timeout caveat, not the prior unbounded wait. Required hosted checks passed at this head; no live purchase or merge by me.

— Jerrai

@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

Closing: the feature merged as #7068. The hardening this PR added on top is moving to a smaller follow-up rebased on main. That covers the stop-signal checks before a checkout is created, so a paid or timed-out wait never creates a stale checkout, and the bounded OAuth refresh. I'll link it here.

@WaterrrForever

Copy link
Copy Markdown
Collaborator Author

The hardening from this PR's reviews is now in #7216, rebased on main: stop-signal checks before any checkout is created, printed or opened; reads that honour the wait's end; and a 20 s bound on the token refresh.

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.

4 participants