Skip to content

Fix Cloud paid team limits for ID-only selected teams - #16318

Merged
teamleaderleo merged 6 commits into
mainfrom
fix/cloud-team-entitlement-resolution
Oct 1, 2026
Merged

teamleaderleo merged 6 commits into
mainfrom
fix/cloud-team-entitlement-resolution

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

When Stack Auth returns a selected team with only an ID, Cloud falls back to the user's free plan even when that team has a paid subscription. The VM list can then report maxActiveVms: 0 and a free-access expiry for a paid member.

Hydrate the selected team's metadata before resolving billing. The bounded auth path uses one exact-ID lookup, and populated selected teams retain the existing fast path. Only details with the selected team's ID can replace it.

Fixes #16143.

Testing

  • Regression commit 82012fd6fce: the focused command below executed five tests; four failed with missing paid entitlements and the fallback case passed. Fix commit 515aabf871c: all five passed. Follow-up commit 96b8816aae5 covers bounded auth requesting a different team and paginates complete membership lookup.
    cd web && bun run test tests/vm-route-auth.test.ts -t "ID-only selected team|does not borrow another paid"
  • bun run test tests/vm-route-auth.test.ts tests/billing-team-resolution.test.ts tests/vm-pro-gate.test.ts: 119 passed. Coverage includes cookie/native list limits, bounded lookups for both requested and selected teams, paid seats, complete-list pagination, existing populated-team behavior, and missing matching details.
  • TypeScript, web complexity, and diff checks passed. Scoped ESLint passed with the existing _snapshotRoute unused-variable warning. Portable repository verification passed.
  • No live Cloud session was exercised; API behavior was verified through the route tests.

Changelog

Fixed: Cloud preserves paid team limits when the selected team's billing details need loading.


Summary by cubic

Fixes Cloud falling back to the user's free plan when the selected Stack team comes back with only an ID, which could report maxActiveVms: 0 and a free-access expiry for paid members. Fixes #16143.

  • Resolves an ID-only selected team's billing details before choosing a plan, using bounded exact-ID lookups that only replace the selected team when the ID matches.
  • Falls back to the selected team's user plan when pagination is incomplete (repeated cursor or page limit), keeps partial pages out of identity snapshots, and preserves strict failure for snapshot refresh paths.
  • Adds regression tests covering cookie and native auth, bounded lookups of both requested and selected teams, complete-list pagination, incomplete pagination, and the case where the selected team is absent from the lookup result.

Written for commit 5daf1e6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • VM authentication now looks up missing details for the selected team, allowing requests to use that team’s plan, seat information, and VM limit when available.
    • If the selected team can’t be found or team listing is incomplete, authentication retains the selected team ID and uses the user’s plan rather than another team’s billing details.
    • Requests for a different team continue to resolve that team while checking the selected team’s details when needed.

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (2)
.github/review-bot-rules/test-determinism.md — configured
.github/review-bot-rules/source-control-artifacts.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 68b4c18c-9934-4039-8ef7-8e4305dd0a64

📥 Commits

Reviewing files that changed from the base of the PR and between bb7fe8d and 5daf1e6.

📒 Files selected for processing (2)
  • web/services/vms/auth.ts
  • web/tests/vm-route-auth.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Team membership resolution now looks up details when the selected team lacks clientReadOnlyMetadata. Pagination reports whether the team list is complete. Tests cover billing hydration for VM requests and fallback behavior when lookup results are incomplete or omit the selected team.

Changes

Team billing resolution

Layer / File(s) Summary
Resolve selected team details and retain pagination status
web/services/vms/auth.ts, web/tests/vm-route-auth.test.ts
The resolver looks up teams by ID when needed and uses matching details to hydrate selected-team billing. Pagination returns accumulated teams with a completeness status. Tests cover paid-plan VM limits, bounded lookups, multiple pages, and free-plan fallback when results are incomplete or omit the selected team.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: lawrencecchen

Merge Risk: ⚪ Minimal · up to 5daf1

The change addresses paid-team billing for ID-only selected teams while preserving safe fallback behavior. No concrete merge-blocking issue remains in the supplied evidence; merge after normal checks pass.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5daf1

Team-ID matching and membership checks remain intact, and complete-membership refreshes reject incomplete pagination. The main residual risk is increased dependence on team lookups during authentication and temporary reuse of degraded billing results. No cross-team privilege expansion was demonstrated.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected change affects an authenticated user's team-scoped VM access and provisioning entitlements. Its authority remains bounded by provider-returned membership and downstream team validation; no broader tenant or provider-credential authority was demonstrated.

Trust Boundaries and Controls

  • observed — A differing caller-supplied team ID triggers authenticated membership lookup, not an unrestricted team lookup. Exact-ID filtering and entitlement membership validation remain independent controls before VM creation.

Resilience and Maintainability Implications

  • observed — The native authentication cache can retain pagination-fault results until expiry and concurrent writes are last-writer-wins. However, caching incomplete selected-only membership and fallback billing predates this PR. Fresh-membership requests bypass this cache, and durable snapshots remain completeness-gated.

Hardening Proposals

  • proposed — Consider distinguishing pagination-fault results from intentional selected-team-only resolutions before native caching, so recovery from degraded membership or billing does not require waiting for cache expiry.
🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: correcting Cloud paid-team limits for teams represented only by an ID.
Description check ✅ Passed The description includes a clear problem statement, implementation summary, issue reference, detailed test results, changelog entry, and verification limitations. The omitted demo and checklist sectio…
Linked Issues check ✅ Passed The changes address [#16143]. resolveStackTeamMembership hydrates an ID-only selected team with an exact-ID lookup before billing resolution. Matching metadata supplies the selected team’s paid plan…
Out of Scope Changes check ✅ Passed The production changes stay in the VM authentication and team-resolution path. The tests cover paid-plan hydration and safe incomplete-pagination behavior. These changes directly support [#16143]. No …
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The PR changes only Stack team membership hydration, billing resolution, pagination handling, and VM auth tests in web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It does not c…
Cmux Swift Actor Isolation ✅ Passed PASS: The authoritative PR diff changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. Both are TypeScript files, and the patch introduces no production Swift changes or Swift …
Cmux Swift Blocking Runtime ✅ Passed PASS: The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It introduces no Swift changes and no Swift blocking or timing-based synchronization.
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. The browser automation rule applies to Swift browser socket automation in Sources/TerminalController.swift…
Cmux Expensive Synchronous Load ✅ Passed The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It adds no production Swift changes, so the expensive synchronous Swift load check is not applicable.
Cmux Cache Substitution Correctness ✅ Passed No cache substitution is introduced. The changed paths still read Stack through user.listTeams, either with pagination or an exact-ID query. Incomplete pagination retains partial teams only for the …
Cmux No Hacky Sleeps ✅ Passed PASS. The PR changes only team lookup and pagination logic plus tests. The added lines contain no sleep, delay, timer, polling, or wall-clock synchronization. The only setTimeout in `web/services/vm…
Cmux Algorithmic Complexity ✅ Passed The production changes do not introduce a prohibited algorithm. Team pagination is a linear pass with explicit bounds of 100 pages and 100 teams per page, and cursor detection uses a Set. Bounded hydr…
Cmux Swift Concurrency ✅ Passed PASS: The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. The authoritative Swift diff is empty, so it introduces no cmux-owned Swift concurrency pattern co…
Cmux Swift @Concurrent ✅ Passed The reviewed pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It contains no Swift changes, so the @concurrent annotation check does not apply.
Cmux Swift Package Boundaries ✅ Passed The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It contains no production Swift or Swift package changes, so the Swift package boundary rule does not ap…
Cmux Swiftpm Lockfiles ✅ Passed PASS. The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It does not change a SwiftPM package, Xcode project, .gitignore, workflow, dependency declaratio…
Cmux Swift Logging ✅ Passed The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It changes no Swift or Objective-C source, and added lines contain no prohibited logging APIs or diagnos…
Cmux User-Facing Error Privacy ✅ Passed PASS. The production diff changes team hydration and pagination behavior but does not add user-facing error copy, alerts, API error bodies, or recovery text. The new pagination reason strings contain …
Cmux Full Internationalization ✅ Passed PASS. The PR changes internal team hydration, billing resolution, pagination handling, and tests. It adds no Swift text, web message keys, locale files, or user-facing copy. The API changes correct en…
Cmux Swiftui State Layout ✅ Passed The PR changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. Both are TypeScript files. The diff contains no Swift or SwiftUI changes, so the SwiftUI state-layout failure cond…
Cmux Architecture Rethink ✅ Passed PASS: The pull request changes only TypeScript files (web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts). It introduces no Swift architecture changes, so the Swift-specific failure cond…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The reviewed pull request changes only two TypeScript files: web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It adds no Swift, AppKit, or SwiftUI window code, so the auxiliary-…
Cmux Source Artifacts ✅ Passed The PR changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. These are intentional TypeScript source and regression-test files. No artifact-like paths, generated outputs, logs…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: The pull request changes only web/services/vms/auth.ts and web/tests/vm-route-auth.test.ts. It contains no Swift file under a production Sources/ path, so the custom check does not apply.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/services/vms/auth.ts Outdated
Comment thread web/services/vms/auth.ts Outdated
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI passes on 5daf1e6eaa (run 36822423297 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread web/services/vms/auth.ts Outdated
Keep partial Stack team pages out of identity snapshots and fall back to the selected team user plan when pagination repeats a cursor or reaches its bound. Preserve strict behavior for fresh and snapshot refresh paths.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/services/vms/auth.ts">

<violation number="1" location="web/services/vms/auth.ts:844">
P2: An incomplete page walk can now determine the fallback billing team: with no `selectedTeam`, `resolveBillingTeam` treats a partial one-team result as the sole team or chooses a paid team from an incomplete set. Keep incomplete results from driving fallback billing selection; otherwise VM entitlements may use the wrong team's scope.</violation>

<violation number="2" location="web/services/vms/auth.ts:845">
P2: Native auth caches this incomplete result even though `completeTeamList` is false, so retries with the same tokens can keep using partial membership or the user-plan fallback for the cache TTL after pagination recovers. Skip native-cache writes when `completeTeamList` is false.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread web/services/vms/auth.ts
// mark the partial result complete, so it is never snapshotted.
const listed = await listStackTeams(user, undefined);
listedTeamRaw = listed.teams;
completeTeamList = listed.complete;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Native auth caches this incomplete result even though completeTeamList is false, so retries with the same tokens can keep using partial membership or the user-plan fallback for the cache TTL after pagination recovers. Skip native-cache writes when completeTeamList is false.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At web/services/vms/auth.ts, line 845:

<comment>Native auth caches this incomplete result even though `completeTeamList` is false, so retries with the same tokens can keep using partial membership or the user-plan fallback for the cache TTL after pagination recovers. Skip native-cache writes when `completeTeamList` is false.</comment>

<file context>
@@ -825,22 +825,36 @@ async function resolveStackTeamMembership(
+      // mark the partial result complete, so it is never snapshotted.
+      const listed = await listStackTeams(user, undefined);
+      listedTeamRaw = listed.teams;
+      completeTeamList = listed.complete;
+    }
+  } else if (options.listAllTeams === true) {
</file context>

Comment thread web/services/vms/auth.ts
// Stack returns a broken or unexpectedly large pagination chain. Do not
// mark the partial result complete, so it is never snapshotted.
const listed = await listStackTeams(user, undefined);
listedTeamRaw = listed.teams;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: An incomplete page walk can now determine the fallback billing team: with no selectedTeam, resolveBillingTeam treats a partial one-team result as the sole team or chooses a paid team from an incomplete set. Keep incomplete results from driving fallback billing selection; otherwise VM entitlements may use the wrong team's scope.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At web/services/vms/auth.ts, line 844:

<comment>An incomplete page walk can now determine the fallback billing team: with no `selectedTeam`, `resolveBillingTeam` treats a partial one-team result as the sole team or chooses a paid team from an incomplete set. Keep incomplete results from driving fallback billing selection; otherwise VM entitlements may use the wrong team's scope.</comment>

<file context>
@@ -825,22 +825,36 @@ async function resolveStackTeamMembership(
+      // Stack returns a broken or unexpectedly large pagination chain. Do not
+      // mark the partial result complete, so it is never snapshotted.
+      const listed = await listStackTeams(user, undefined);
+      listedTeamRaw = listed.teams;
+      completeTeamList = listed.complete;
+    }
</file context>

@teamleaderleo
teamleaderleo enabled auto-merge (squash) October 1, 2026 22:24
@teamleaderleo
teamleaderleo merged commit 4a46320 into main Oct 1, 2026
71 checks passed
@teamleaderleo
teamleaderleo deleted the fix/cloud-team-entitlement-resolution branch October 1, 2026 22:25
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Merge receipt for 5daf1e6eaa: every check was green at merge (18 verified; 19 skipped by policy). Full suite runs on main after merge.

rustybret pushed a commit to rustybret/bmux that referenced this pull request Oct 1, 2026
0906bcb fix: make main's full test suite pass again (manaflow-ai#16429)
11bfe00 Restore custom sidebar preview gallery (manaflow-ai#16535)
343dd1b web: sync all Hexclave webhooks into a validated, order-independent mirror (manaflow-ai#16339)
00547d5 ci: avoid blaming unrelated merges for compile failures (manaflow-ai#16533)
b782440 fix(ci): provision Go for every iOS Release archive (manaflow-ai#16534)
3555618 Add a Jump to Bottom button to terminal panes (manaflow-ai#15382)
79febcf fix: tolerate delayed App Store Connect processing (manaflow-ai#16527)
fcbf13c fix: export Foundation for remote paste policy (manaflow-ai#16525)
6d86537 Add What's New recap with an off / quiet / sheet setting (manaflow-ai#14876)
256d964 fix(xcstrings): keep conflict resolutions valid JSON (manaflow-ai#16071)
8473bdc fix: upload pasted images into private SSH directories (manaflow-ai#16523)
53c705c Show opt-in model, context %, and estimated cost next to agent status in the sidebar (manaflow-ai#14855)
eba3c42 remote relay: permit scoped terminal paste (manaflow-ai#14915)
e447665 fix: stop update relaunch prompts from looping (manaflow-ai#15702)
4a46320 Fix Cloud paid team limits for ID-only selected teams (manaflow-ai#16318)
c266af9 test(cloud): pin the CLI tree's link error message through the bundled CLI (manaflow-ai#16515)
0059066 Calmer focus feedback: one short pulse, no flash while typing (manaflow-ai#14894)
65930fc fix(remote): preserve tmux split metadata (manaflow-ai#16398)
512817d docs: fill missing unreleased user-facing changes (manaflow-ai#16519)
f204ade ci: nightly 120 Hz fling bench for the cmux-next agent pane (manaflow-ai#16511)
2be3b26 Remove generated custom sidebar preview art (manaflow-ai#16518)
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.

Cloud reports the free zero-cap plan for a team member's account

1 participant