Skip to content

fix(suno): derive current plan from subscription_type + plans[] instead of legacy data.plan#1706

Open
Benjamin-eecs wants to merge 3 commits into
jackwener:mainfrom
Benjamin-eecs:fix/suno-status-missing-plan-id
Open

fix(suno): derive current plan from subscription_type + plans[] instead of legacy data.plan#1706
Benjamin-eecs wants to merge 3 commits into
jackwener:mainfrom
Benjamin-eecs:fix/suno-status-missing-plan-id

Conversation

@Benjamin-eecs
Copy link
Copy Markdown
Contributor

Description

Suno's billing/info response no longer returns a top-level plan object. The current shape is:

{
  subscription_type: <false | "pro" | "premier" | ...>,
  plans: [{ id, name, plan_key, level, ... }, ...],
  total_credits_left, monthly_limit, monthly_usage, credit_packs, ...
}

ensureSunoSession parsed data.plan.id and data.plan.plan_key, both of which are gone. Every command that called the helper threw Suno billing/info returned no plan id ... cannot construct user_tier for generation request on free-tier accounts. The throw also lived in the shared helper, so status / list / download all failed even though only generate actually consumes planId (as userTier).

This PR:

  • Derives currentPlan by matching subscription_type against plans[]. Free-tier accounts (subscription_type === false) resolve to the plans[] entry whose plan_key === "free", so planId / planKey / planName are always populated for any logged-in session.
  • Prefers data.total_credits_left when present (the field the web UI uses for its "credits remaining" pill), with the legacy three-bucket sum as fallback.
  • Moves the planId presence check from ensureSunoSession into generate.js, where the value is actually consumed. status / list / download no longer need a paid plan to function.

Live-verified on a free-tier account: suno status now reports Plan: free, Credits: 40, Monthly: 40/50 instead of throwing, and suno list returns clip rows.

Related issue: Closes #1704.

Type of Change

  • 🐛 Bug fix

Checklist

  • I ran the checks relevant to this PR
  • I updated tests or docs if needed
  • I included output or screenshots when useful

Screenshots / Output

Before (opencli suno status on free-tier):

ok: false
error:
  code: COMMAND_EXEC
  message: Suno billing/info returned no plan id ... cannot construct user_tier for generation request.
  exitCode: 1

After:

$ opencli suno status
- Status: Connected
  Plan: free
  Credits: '40'
  Monthly: 40/50
  Captcha: Required (solve in UI)

$ opencli suno list --limit 3
- rank: 1
  clip: c491813b
  title: Spark Breaking Through
  status: complete
  ...

@Benjamin-eecs Benjamin-eecs marked this pull request as ready for review May 21, 2026 09:04
Copilot AI review requested due to automatic review settings May 21, 2026 09:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes Suno session bootstrapping after /api/billing/info/ removed the legacy top-level plan object, restoring functionality for free-tier accounts and preventing read-only commands from failing due to missing planId.

Changes:

  • Added a shared parseSunoBillingInfo parser that derives the current plan from subscription_type + plans[] (with legacy fallbacks) and prefers total_credits_left for credit totals.
  • Updated ensureSunoSession to use the shared parser and removed the global “planId required” throw so read commands can still run.
  • Moved the “planId required” validation to suno generate and added tests for the new behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
clis/suno/utils.js Adds parseSunoBillingInfo and reworks ensureSunoSession to parse the new billing/info schema.
clis/suno/utils.test.js Adds unit tests for billing-info parsing and ensures free-tier sessions no longer throw.
clis/suno/status.js Updates status messaging/comments to reflect planId may be null while planKey remains available.
clis/suno/generate.js Enforces planId requirement only for generation (where userTier is needed).
clis/suno/generate.test.js Adds a regression test ensuring generate refuses submission when planId is null.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread clis/suno/generate.js
const session = await ensureSunoSession(page);
if (!session.planId) {
throw new CommandExecutionError(
`Suno generation needs a resolved plan id for the user_tier field, but billing/info did not surface one for this account (subscription_type=${session.planKey}). Verify the account is active at ${SUNO_URL}/account, then retry.`,
@Benjamin-eecs Benjamin-eecs force-pushed the fix/suno-status-missing-plan-id branch from 011819b to 4b742c0 Compare May 21, 2026 09:33
…ad of legacy data.plan

Suno's billing/info response no longer returns a top-level `plan` object.
The current shape is:

  {
    subscription_type: <false | "pro" | "premier" | ...>,
    plans: [{ id, name, plan_key, level, ... }, ...],   // all available plans
    total_credits_left, monthly_limit, monthly_usage, credit_packs, ...
  }

ensureSunoSession parsed `data.plan.id` and `data.plan.plan_key`, both of
which are gone, so every command that called it threw "Suno billing/info
returned no plan id" on free-tier accounts. The throw was also placed in
the shared helper, so `status` / `list` / `download` all failed even
though only `generate` actually needs planId (for the user_tier field).

This change:

- derives currentPlan by matching subscription_type against plans[];
  free-tier accounts (subscription_type === false) resolve to the
  plans[] entry whose plan_key equals "free", so planId / planKey /
  planName are always populated for any logged-in session.
- prefers data.total_credits_left when present (the field the web UI
  uses for its "credits remaining" pill), and falls back to summing the
  three buckets when absent.
- moves the planId presence check from ensureSunoSession into
  generate.js, where the value is actually consumed. status / list /
  download no longer need a paid plan to function.

Live-verified on a free-tier account: `suno status` now reports
`Plan: free, Credits: 40, Monthly: 40/50` instead of throwing, and
`suno list` returns clip rows.

Closes jackwener#1704.
The billing/info parsing previously lived inside the in-page IIFE in
ensureSunoSession, which made it unreachable from Node-side tests
(createSessionPage mocks page.evaluate to return the already-parsed
session object, skipping the IIFE body). The new subscription_type vs
plans[] derivation had no direct coverage.

Pull the parser out as `parseSunoBillingInfo(data)` and inline it into
the IIFE via toString(), matching the codex/sidebar.js and
gov-policy/search.js pattern. Add direct unit tests covering free-tier,
paid-tier, unknown subscription_type, missing plans[], legacy plan
fallback, and credit_pack summing.

No behaviour change beyond test reach. Live re-verified on a free-tier
account: opencli suno status still reports Plan: free, Credits: 40.
JSDoc compresses to the sibling checkSunoCaptcha tone (two lines, the
WHY-this-exists hint); the API-shape narrative belongs in the prior
commit message, not on every adapter helper. Also drop the planName
field from the parser return value: it had no consumer in status / list
/ download / generate, so it would have been dead session metadata.
@Benjamin-eecs Benjamin-eecs force-pushed the fix/suno-status-missing-plan-id branch from 4b742c0 to 53782f4 Compare May 22, 2026 06:50
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.

[Bug]: suno — cannot construct user_tier for generation request

2 participants