fix(license): stop losing trials, and repair the ones already lost - #635
Merged
Merged
Conversation
131 verified users on cloud have no licence at all. The licence API rate-limits anonymous callers to three trial registrations per hour per IP, and this backend calls it server to server for every user who verifies an email, from one IP. So the whole cloud shared a bucket of three an hour: on any busy hour, everyone past the third silently ended up on the licence wall instead of onboarding. Activation is best-effort by design, so nothing ever surfaced. Four changes, each of which alone would have prevented it: - Present LICENSE_SERVICE_TOKEN as x-amcp-service-token, so the licence API can tell its own cloud apart from the public and give it a bucket that fits. Unset in self-hosted installs, which keep the public limit. - Retry the trial request three times with exponential backoff and jitter, for throttling, upstream faults and no answer at all. A 4xx on the merits is not retried. - activate-trial is idempotent. The client calls it right after verification already activated the trial, so it used to answer 400 on every single signup: an error shown to a user whose trial was fine, and a wasted call against the rate limit. An org that already holds a licence gets it back, with a success. - A repair pass in the onboarding cron hands a trial to any verified user whose workspace has none, bounded and paced. Whatever the reason a trial goes missing, the next run picks it up and the backlog drains itself. Also fixes the MOTIS healthcheck, which had reported unhealthy 2300 times in a row while serving traffic perfectly: it probed localhost, which resolves to ::1 inside the container where MOTIS binds IPv4 only, and / which MOTIS answers 404. Trial paths now covered by 19 tests.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
What was happening
131 verified cloud users have no licence at all. Not an expired trial, none: they verified their email and landed on the licence wall.
/api/license/trialon anythingmcp.com rate-limits 3 registrations per hour per IP. That is a sane number for a human starting trials by hand. It is not a sane number for this backend, which calls that route server to server for every verified email, always from the same droplet. The whole cloud shared one bucket of three an hour, and since activation is best-effort by design (verification must succeed even when the licence API is down), every loss was a warning line nobody read.Measured on 2026-09-16: 11 trials created, 9 rejected with 429, and
romanpravnyk1992@gmail.comsigned up at 14:51 and still had no licence 19 hours later.What changes
LICENSE_SERVICE_TOKENgoes out asx-amcp-service-tokenso the licence API can tell its own cloud from the public. Unset self-hosted, which keeps the public limit. Needs the matching website change.activate-trialAny one of the four would have prevented the bug. Together they also make the retry safe, because the endpoint becomes idempotent on the website side.
Also here
The MOTIS healthcheck, which had failed 2300 times in a row on a container that was serving traffic perfectly: it probed
localhost(→::1, where MOTIS does not bind) and/(which MOTIS answers 404). Now127.0.0.1and/metrics. Verified against the live container before and after.Verified
npx jestwhole backend: 4204 passed, 0 failedDeploy order
Website PR HelpCode-ai/anythingmcp-website#198 goes first (it teaches the licence API about the token and makes the route idempotent), then this, with
LICENSE_SERVICE_TOKENset on both sides.