Postmortem: We Copied the Web Usage Gate Into the Extension and It Was Wrong for Every User
TLDR
We originally copy-pasted our web application's 2-use guest limit into our VS Code extension host, storing execution counts incontext.secrets. In production, serious developers who installed the extension were locked out on their third test run during their very first session. We refactored the extension host: stripping out all usage counters and replacing blocking checks with a non-blocking tier resolution model that always returns allowed: true.
| Component Architecture | Implementation Details | User Experience Impact | Conversion Outcome |
|---|---|---|---|
| Legacy Extension Gate | context.secrets counter (Blocked at run 3) | Hostile (Locks out active users) | High uninstalls |
| Refactored Tier Model | Non-blocking API check (allowed: true) | Uninterrupted (Unrestricted tool use) | High engagement & organic upgrades |
Copy-pasted web limits break developer workflows in IDEs
When we shipped our VS Code extension suite, we carried over the web product's monetization model:
// Legacy Extension Host Handler (WRONG) if (!token) { const count = parseInt(await secrets.get('guest_count') ?? '0', 10); if (count < 2) { await secrets.store('guest_count', String(count + 1)); allowed = true; } else { allowed = false; // Blocked after 2 uses! } }
The assumption: anonymous extension users behave like anonymous web visitors.
Broken redirect buttons lock users inside sandboxed webviews
When a user hit the third analysis run inside VS Code, the extension host responded with allowed: false.
Because the webview was isolated inside VS Code's webview sandbox:
- It could not open web OAuth popups.
- It had no inline sign-in modal available.
- It rendered a broken "Sign In" button that produced zero response when clicked inside the webview panel.
Legacy Hostile Extension Gate:
[User Runs Tool] -> [Read guest_count] -> [Count >= 2? Yes] -> [Return allowed: false] -> [Webview Locked] -> [User Uninstalls]
Refactored Non-Blocking Tier Surface: [User Runs Tool] -> [Fetch Tier Non-Blockingly] -> [Return allowed: true (ALWAYS)] -> [Tool Runs 100% Uninterrupted]
Removing guest counters simplifies state and restores trust
We deleted the entire guest tracking system from the extension host:
- Removed
secrets.get('guest_count')andsecrets.store()counter calls. - Deleted
GUEST_LIMITconstants and counter API endpoint checks (/api/pdf-analyze-check). - Deprecated the
gx_analyze_guest_countkey incontext.secrets.
Non-blocking tier resolution response
The extension host now returnsallowed: true for all analysis requests:
// Refactored Extension Host Handler (CORRECT)
export async function handleAnalyzeCheck(panel: vscode.WebviewPanel, authProvider: AuthProvider) {
let tier = 'free';
const token = await authProvider.getToken().catch(() => null);
if (token) { tier = await fetchUserTier(token); }
// Core functionality is ALWAYS allowed panel.webview.postMessage({ type: 'analyze-response', payload: { allowed: true, tier } }); }
Rule of thumb: Never apply guest usage limits inside IDE extensions. Installing an extension demonstrates maximum user intent; reserve authentication strictly for unlocking cloud features and displaying Pro tier indicators.