Repository navigation
Conversation
A user's first organization now starts on a hidden EX_TRIAL plan with Medium features for 7 days, then moves to the Free plan, which keeps the non-dismissible upgrade banner. Upgrading during the trial uses the existing Stripe flow and bills immediately; Stripe is not involved in the trial itself. - Remove user and project limits from every plan - Require a verified email and a daily limit to send invites - Hourly TrialExpirationJob sends a reminder email and ends trials - Exie on trials needs a verified email, a $1 monthly cap, and a shared daily cost cap across all trial organizations - Trial banner, post-trial Free banner, and trial-aware plan dialog - Billing:* settings, including TrialDays (0 disables trials) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 388b4f8f73
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Count only organizations a user owns for the one-free-organization rule and trial eligibility, so invited members can create their own organization (and get a trial) - Deny Exie for trial organizations whose trial has ended, without waiting for the expiration job - Count in-flight Exie reservations against the shared trial daily cap - Track free and paid invite allowances separately - Migrate existing organizations to unlimited users and projects - Show an ended-trial banner before the job moves the organization - Use an invariant date in the trial reminder email - Remove byte order marks added by earlier edits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The banner called toLocaleString() on max_events_per_month, which threw when an organization response omitted it and broke the app shell (caught by the navbar-layout E2E test, whose mocked organization has no limits). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a415af16f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An organization that converted from a trial keeps TrialEndsUtc, so a later downgrade to Free showed "Your trial has ended". Show that only when the organization has a trial date and never subscribed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23330d590a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Take a per-user lock around organization creation and reload the user inside it, so concurrent requests can't both pass the one-trial and one-free-organization checks - Add Organization.CreatedByUserId and use it for ownership, falling back to BillingChangedByUserId for existing organizations, so a member changing the plan doesn't transfer ownership Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33a2358a02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Query owned organizations by CreatedByUserId (now mapped), so leaving an organization doesn't free up its owner's free-organization slot - Check Free downgrades against the organization's owner, not the member making the change - Map created_by_user_id in migration 11 (renamed UpdateOrganizationsForTrials) - Document the invite endpoint's 403 and 429 responses and drop the removed 426 in the endpoint metadata and OpenAPI snapshot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a5e3e05bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Ask for exactly what each check needs instead of loading the first 100 created organizations: a count for "owns any organization", and a query filtered to the free and trial plans for the one-free-organization rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dee3aa9af1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- GetBillingPlanStatsAsync read only the first page of organizations, so every counter (including the new trial ones) undercounted on larger installs; page through all organizations - If adding the creator's membership fails after the organization is saved, remove the organization so it doesn't count against the user's free-organization limit while being unreachable Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Page through all organization members (user limits are gone, so members can exceed one page) - Record TrialReminderSentUtc only after every reminder is queued, so a queueing failure is retried on the next run instead of silently skipping the reminder Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a44851e611
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Downgrades to Free now take the same per-owner lock as organization creation, so two concurrent downgrades of one owner's paid organizations can't both pass the one-free-organization rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 912f680930
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Migration 11 now copies BillingChangedByUserId into CreatedByUserId for existing organizations, so ownership always uses the immutable creator. This removes the membership-based fallback, which read the mutable billing audit field and changed owner whenever a member changed the plan. Sample data records the creator too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c334ab1d7f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Admin plan changes preserve the billing price; applying EX_TRIAL reset it to zero, which a later admin change back to a paid plan didn't restore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 611e15787b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The shared daily counter is updated after durable usage is recorded; if that cache update threw, the provider lifecycle treated the request as unreconciled and recorded the estimate again. Log and continue instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9463ad644d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Moving a paying organization onto the no-card trial left its Stripe subscription running and then dropped it to Free when the trial ended, losing its price. The admin trial action is for starting or extending a trial, so restrict it to free and trial organizations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48b13524c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Admin plan changes now share the organization billing lock with user plan changes and the trial expiration job, and reload the organization inside it, so an admin extending a trial can't race the job ending it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba300caeab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Show the trial banner only in the last 3 days, dismissible until the next day, with a quiet days-left indicator in the sidebar. Replace the permanent Free banner with a one-time trial-ended notice recorded per user and organization. Add a dismissible warning at 80% of the monthly event limit, and send the trial reminder email 3 days before the trial ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d8644f109
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Save the trial-ended dismissal to the cached user right away and reload it if the save fails, skip the notice while impersonating so admins don't fill their own product tour records, and re-check day and month dismissals each minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check the shared daily total after reserving a provider request's estimate and reject requests that started at or past the limit, so a burst can exceed it by at most one estimate. Only reservations whose estimate was counted adjust the shared total later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00e2cd7571
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| public Task<ILock> AcquireOwnerFreeOrganizationLockAsync(string userId) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(userId); | ||
| return _lockProvider.AcquireAsync($"User:{userId}:free-organization", BillingLockDuration, TimeSpan.FromSeconds(30)); |
There was a problem hiding this comment.
shouldn't this be on the organization level since you could have multiple users? Or is this saying that an organization is only created by one user so the trial is by that person, and if anyone changes billing at all then this trial lock is cancelled. What if a user creates multiple free organizations? I guess this is a free trial for a given free organization, but what about all other premium features? Guess exie is an exclusion to that and that makes sense.
There was a problem hiding this comment.
It's per user on purpose. The rule it protects is "one Free or trial organization per owner", so it serializes one owner's creates and downgrades to Free; changes to a single organization still take that organization's billing lock. A trial applies to the user's first organization only, and any further organizations start on Free. The trial plan gives that organization Medium limits and premium features, Exie included, until it ends.
| } | ||
|
|
||
| private static string GetSharedDailyCostKey(string sharedDailyCostLimitKey, string dayId) | ||
| => $"shared:{sharedDailyCostLimitKey}:usage:{dayId}:cost-microdollars"; |
There was a problem hiding this comment.
what is a microdollar and why does that matter here?
There was a problem hiding this comment.
A microdollar is a millionth of a dollar. Exie cost is tracked with integer cache increments, so it's stored as long microdollars to avoid floating-point drift. It's the same unit as AssistantUsageInfo.CostInMicrodollars on main; ToMicrodollars converts the configured USD cap for the comparison.
- One trial per mailbox: store the normalized email (lowercase, no +tag, no Gmail dots) that claimed a trial and check it under a per-mailbox lock. - Disposable email domains get Free instead of a trial. System admins edit the blocked list on the system settings page; the built-in list applies until they save their own. - Trials are only for users who belong to no organization and created none. - Migration 11 no longer guesses creators from the last billing change. Organizations without a recorded creator fall back to the membership rule. - Invite verification and the daily limit apply only to Free and trial organizations; paid organizations invite as before.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88b1f43700
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
niemyjski
left a comment
There was a problem hiding this comment.
The alias normalization and legacy membership fallback address the original alias and guessed-creator cases, and paid invitations now retain their previous behavior. Local revalidation still reproduced account-deletion trial renewal and both verification issues below.
Validation: 217 focused backend tests and 31 frontend tests passed; local staging-mode API and browser checks used Mailpit and a simulated Exie provider. Actual Stripe checkout and live AI were not exercised.
The existing mailbox-claim review finding is confirmed: start a trial, delete its organization and user, register the identical address again, and create an organization; the replacement receives EX_TRIAL again. Keep the claim independently of the deletable user record.
Other open feedback remains relevant: TrialExpirationJob still catches cancellation inside the batch and can page/renew afterward; the migration test does not check preservation of unrelated mappings/settings; and the invitation cache key remains constructed inline. Paid invite compatibility is fixed, but Free/trial invites still introduce 403/429 for formerly accepted requests, so the compatibility decision remains open while the PR says there are no breaking changes.
There was a problem hiding this comment.
Changes requested for two reproduced blockers:
- Account deletion resets the mailbox trial claim, allowing the same address to receive another trial. Preserve that claim independently of the deletable user record.
- Verification in another tab leaves the original Exie panel locked. Refresh the separate assistant-access query through the existing account-change path.
Before merge, explicitly decide whether the new Free/trial invite 403/429 responses are accepted compatibility changes and describe that decision accurately. The anti-abuse restrictions may be intentional; this is not a request to remove them.
Follow-up, not release blockers: misleading success text when another account's verification link is used, expiration-job cancellation handling, and bounded-memory billing statistics (downgraded to P3). No broad refactor is requested.
Verification: reviewed head 88b1f4370. On November 6, 35 focused backend tests, 31 frontend tests, and diff whitespace checks passed. Browser/API reproductions are from November 4 on that same head; E2E, Stripe checkout, and live AI were not rerun.
- Keep mailbox trial claims in a separate trial-claims index (hashed), so deleting and recreating an account doesn't renew the trial - Refresh Exie access in every tab when the signed-in user changes, and say which account a verification link verified - Stop the trial expiration job before the next organization when cancelled; read organizations from cache - Move the free invite counter and billing lock keys into BillingManager - Use BillingPlan.Unlimited, require TimeProvider in AssistantAccessService, and share one usage date helper - Accumulate billing plan stats page by page - Test that migration 11 keeps existing mappings and settings; add Arrange/Act/Assert to the PR's frontend tests
|
Addressed the review in bf287f7:
Replied on every thread. The ones that were questions (lock scope, 426, microdollars, Mailer, invite limit setting) are left open for you. |
Persist mailbox claims before granting trials, reject unconfirmed shared cost reservations, patch only migrated organization limits, and refresh sidebar countdowns. Add regression coverage for failure recovery, usage reconciliation, concurrent writes, and elapsed time.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: daba9fb6d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| name: 'Exie upgrade to Medium' | ||
| }; | ||
|
|
||
| /** Trial org — shows days left, preselects Medium, and notes that billing starts today. */ |
There was a problem hiding this comment.
Spell out organization in the trial story comment
The new comment abbreviates organization as org, contrary to the explicit rule in root AGENTS.md, which also applies to comments. Replace “Trial org” with “Trial organization” to satisfy the documented convention.
AGENTS.md reference: AGENTS.md:L78-L78
Useful? React with 👍 / 👎.
What changed
New hosted signups get a 14-day free trial with Medium features (including Exie) for their first organization, with no card. When the trial ends, the organization moves to the Free plan. There's no permanent nag: users see a one-time notice when the trial ends, and every plan gets a dismissible warning at 80% of its monthly events.
Plans and trial
EX_TRIALplan (Medium limits, premium features, Exie).Organization.TrialEndsUtc/TrialReminderSentUtcandUser.TrialStartedUtcrecord trial state.+tag, no Gmail dots) is recorded in a newtrial-claimsindex and checked under a per-mailbox lock. The claim is separate from the user, so deleting the account and signing up again doesn't bring the trial back.CreatedByUserId; existing organizations keep none and fall back to the membership rule. Creation and downgrades to Free are serialized per owner.TrialExpirationJob(in-process cron + new k8s CronJob,TrialExpirationjob arg) sends a "trial ends soon" email 3 days before the trial ends to verified members once, then moves expired trials to Free.EX_TRIALto start or extend a trial; admin plan changes take the organization billing lock.max_users/max_projectsremain in the API as-1).Abuse and cost controls
paidorganization filter excludes trials.UI
trial-ended-<organizationId>with the existing product-tour record endpoint, so it doesn't come back for that user on any device. It's not shown while a global admin impersonates.usage, bonus events included). A dismissal lasts for the rest of the UTC month; the 100% overage banner is unchanged.Configuration (all optional):
Billing:TrialDays(14, 0 disables trials),Billing:TrialReminderDays(3),Billing:TrialAssistantDailyCostLimitUsd(25),Billing:FreeInviteDailyLimit(10).Affected APIs
Additive:
trial_ends_utcon organizations,email_verification_requiredon Exie access, trial counts on plan stats, andGET /api/v2/users/verify-email-address/{token}now returns{ "email_address" }for the account it verified. New admin endpoints:GET/PUT /api/v2/admin/trial-settings. Creating projects and inviting users no longer return 426 for plan limits.openapi.jsonand generated client models are updated.Deployment
trial-claimsindex, created on startup like the others.created_by_user_idand sets existing organizations to unlimited users and projects. It doesn't guess creators for existing organizations. No manual step is needed.Verification
dotnet test: 3613 total, 0 failed, 27 skipped. New tests cover trial start and one-trial rules, trial upgrade and downgrade, invite verification and daily limits, the expiration job lifecycle, the trial email, the Exie trial gate, and the shared daily cost cap.npm run validateandvitest: 1188 passed. New tests cover the 3-day trial banner and its dismissal, the one-time trial-ended notice (including another device, a failed save, and impersonation), the 80% usage warning and its priority, and the dismissal periods. Backend tests cover the 14-day and 3-day defaults.Breaking changes
Inviting from a Free or trial organization can now fail where it used to succeed:
The old 426 for the per-plan user limit is gone because plans no longer limit users. This was a deliberate decision to curb invite spam from throwaway signups.