Engineering Journal
Ginexys
Ginexys

Postmortem: We Copied the Web Usage Gate Into the Extension and It Was Wrong for Every User

2026-06-03

TLDR

A 2-use guest limit that drove web sign-ups correctly was copy-pasted into the VS Code extension host. Every serious user hit the limit within their first session. The assumption that a limit which converts web visitors also converts extension users was false because the two populations have fundamentally different intent profiles.


The Assumption That Seemed Reasonable

Usage limits work on the web. A free tier with 2 analysis runs per session gets anonymous users to sign up before they leave. The conversion data supported it: users who hit the limit were more likely to create accounts than users who did not.

When the same feature shipped in the VS Code extension, the natural instinct was to apply the same gate. One monetization model, consistent behavior across surfaces, less code to reason about. The extension host stored the count in context.secrets (the keychain equivalent) and blocked after 2 uses.

The assumption: anonymous extension users behave like anonymous web visitors.


When It Failed

Within the first session of real use, the extension blocked on the third analysis run. The user had installed the extension, opened a PDF, run analysis twice to understand the tool, and was blocked on the third attempt to do actual work.

The error response from the extension host was allowed: false with no recovery path inside the webview -- no sign-in modal, no explanation that was actionable in the VS Code context. The webview received a denied response and showed a gate UI that was designed for the web, where "sign in" meant clicking a button that opened a browser modal. In the extension, that flow did not exist. The user was stuck.


What Was Actually Wrong

The web guest counter works because anonymous web users are a mixed population. Some are exploring, some are serious. The limit separates them. Serious users sign up; explorers bounce.

Anonymous extension users are not a mixed population. Installing a VS Code extension requires opening the Marketplace, searching, evaluating, clicking install, and restarting the editor. That sequence selects for serious users almost exclusively. An anonymous extension user is overwhelmingly likely to be a committed user who has not yet signed in, not an explorer trying the product.

Applying a 2-run limit to this population means the limit hits the exact users most valuable to convert. They have already expressed the highest possible intent signal -- they installed the tool -- and the response is to block them.


What Got Deleted

The entire ginexys:analyze-check guest path: the secrets.get and secrets.store calls, the GUEST_LIMIT constant, the count < GUEST_LIMIT branch, and the counter increment. Also deleted: the counter endpoint call for free-tier web users (/api/pdf-analyze-check) inside the extension host. The extension host no longer makes any blocking decisions about analysis permission.

The secrets key gx_analyze_guest_count is now unused. Any value stored under it in existing installs is silently ignored.


What Replaced It

The extension host always responds allowed: true. If the user is signed in, their tier is resolved non-blocking from /api/me and returned alongside allowed. If the call fails or the user is not signed in, tier defaults to free. The webview uses tier to decide which features are visible, not whether the core tool works.

The sign-in flow in the extension uses a separate browser-based OAuth handshake through the system browser and the OS keychain. It is opt-in and discoverable, not triggered by a block.


The Lesson

A conversion mechanism that works on one surface will not automatically work on another surface with a different user intent profile. Audit the intent signal of each surface before applying limits. The install is a stronger intent signal than any behavior inside the tool.

Read this post in the full Engineering Journal →