Skip to content

perplexity-user-mcp 0.8.42 — Headed Bootstrap reads cookies as anonymous despite successful login (regression from 0.8.41 fix) #5

Closed

Description

@luvher

Product

VS Code extension (perplexity-vscode)

Version

perplexity-user-mcp 0.8.42-universal

MCP Client (if applicable)

Other

Perplexity tier

Pro

OS

Windows 11 Pro 10.0.26200, Node.js: v22.21.1

Browser used by the MCP server

Google Chrome 147.0.7727.139

What happened?

IDE: VS Code with Antigravity extension (Claude Code as MCP client)
Account tier: Pro
Transport: stdio — daemon proxy (http-loopback)

What happened
After updating from 0.8.39 to 0.8.42 (following the fix announced for PR #4 / 0.8.41), performing a clean reinstall, purging the old personal profile, and logging in fresh via Perplexity: Login from the VS Code dashboard (email one-time code, browser window opened and closed correctly after successful authentication), the daemon still reports anonymous mode on every request. perplexity_reason and perplexity_research remain unavailable.

Summary
The vault decrypt fix from PR #4 shipped correctly in 0.8.42 — unseal-verify passes. However, the downstream step (loading the decrypted cookies into the headed bootstrap browser context) still does not work. The daemon's headed Chrome session navigates to perplexity.ai, resolves Cloudflare, checks auth — and consistently sees no session, despite the vault containing valid cookies from a successful Pro login 30 seconds prior.

The PERPLEXITY_HEADLESS_ONLY=1 entries in the log suggest a secondary spawn path where headed sessions are skipped entirely, which may be a separate regression or an incomplete guard in configureDaemonRuntime.

Expected: After a successful Perplexity: Login, the daemon's next reinit should load the session cookies and report tier: Pro.
Actual: Every reinit reports Not authenticated (anonymous mode), regardless of how many times login is repeated.

Doctor output (optional, recommended)

## [OK] vault -- pass
- [OK] encryption -- AES-256-GCM (vault.enc)
- [OK] unseal-path -- env var (keychain unavailable — expected on headless Linux)
- [OK] unseal-verify -- vault.enc decrypts cleanly with the active unseal material

## [X] probe -- fail
- [X] probe-search -- probe returned no sources (latency 3977ms)
  - Hint: Session may be anonymous — run login, then --probe again.

Relevant logs or errors

1. Login was genuinely successful — meta.json confirms Pro tier and timestamp:
{
  "name": "default",
  "displayName": "default",
  "createdAt": "2026-05-11T05:39:13.636Z",
  "loginMode": "manual",
  "tier": "Pro",
  "lastLogin": "2026-05-11T05:42:18.926Z"
}
vault.enc and meta.json share the same write timestamp 07:42:18 — cookies were saved by the login runner.

2. Vault decrypts cleanly (fix from PR #4 works for this part):
[OK] unseal-verify -- vault.enc decrypts cleanly with the active unseal material

3. But every headed bootstrap immediately after reports anonymous:
[perplexity-mcp] Reinit requested — closing current context and reloading cookies.
[perplexity-mcp] Starting headed bootstrap (solving CF + loading account info)...
[perplexity-mcp] Cloudflare resolved in 1s.
[perplexity-mcp] Not authenticated (anonymous mode).   ← always, every reinit
[perplexity-mcp] Headed bootstrap complete.
[perplexity-mcp] Launching headless browser...
[perplexity-mcp] No authenticated session (anonymous mode)
This sequence repeats identically across multiple reinit cycles — Cloudflare resolves fine, but the session check always returns anonymous. The vault was decryptable, the cookies were written, but the daemon's headed bootstrap doesn't load them.

4. PERPLEXITY_HEADLESS_ONLY=1 is set during some spawns:
[perplexity-mcp] Skipping headed session (PERPLEXITY_HEADLESS_ONLY=1).
[perplexity-mcp] No cached account info found.
[perplexity-mcp] Launching headless browser...
[perplexity-mcp] No authenticated session (anonymous mode)
This indicates the daemon was spawned without the ability to run a headed session at all during those cycles — suggesting the passphrase/env injection from buildDaemonEnv still does not reliably reach the daemon on all spawn paths.

5. audit.log confirms all tool calls carry clientId: anon throughout — no authenticated session was ever established, including after the successful login at 05:42.

6. Daemon was running with two different PIDs during this session:
Attached to daemon pid=5116 port=65509   ← first instance
Attached to daemon pid=18172 port=61963  ← second instance after VS Code reload
Port drift still occurs across restarts, which creates stale loopback configs (known issue, tracked for 0.8.43) — but the auth failure is independent and reproducible on both daemon instances.

Steps to reproduce
Fresh install of 0.8.42 on Windows 11 with Antigravity (Claude Code MCP client), stdio-daemon-proxy transport
Run Perplexity: Login from VS Code command palette
Browser opens → enter email one-time code → browser closes (login confirmed successful)
Call perplexity_reason or perplexity_models from Claude Code → Account tier: Anonymous
Perplexity: Login repeated → same result

Activity

  1. ARHAEEM commented on May 11, 2026

    @ARHAEEM
    Member

    Thanks for the thorough writeup, and you're right that PR #4 missed a path. Reading 0.8.42's code with your symptoms in hand, there are two distinct bugs stacked on top of each other — both worth a 0.8.43 release. Before we ship, I want to confirm which one is biting in your scenario A vs. scenario B because some of the data below distinguishes them.

    Bugs identified (high confidence)

    Bug 1 — PERPLEXITY_HEADLESS_ONLY=1 leaks from the launcher into the daemon's spawn env

    This is the cause of your Skipping headed session (PERPLEXITY_HEADLESS_ONLY=1) line.

    • packages/extension/src/auto-config/transports/stdio-daemon-proxy.ts writes PERPLEXITY_HEADLESS_ONLY: "1" into the launcher's IDE-config env block.
    • In daemon-proxy mode the launcher itself doesn't run a PerplexityClient — it just relays JSON-RPC over HTTP — so the flag is dead weight on the launcher.
    • But when the daemon isn't already running, attachToDaemon → ensureDaemon → spawnDetachedDaemon inherits ...process.env directly. HEADLESS_ONLY=1 leaks straight into the daemon. The daemon's PerplexityClient.init() then skips the headed bootstrap entirely — which is exactly what needs to refresh cf_clearance for the headless phase.
    • The buildDaemonEnv overlay from PR fix(vault): unseal hardening for external MCP clients (issue #3) #4 is appended after ...process.env in spawnBundledDaemon too, so even when the extension is the spawner it doesn't strip launcher-scoped flags. It happens to work in the extension-spawner case only because the extension host's process.env doesn't have HEADLESS_ONLY=1 to begin with — your scenario B is the launcher-spawner case, which does.

    Bug 2 — Phase 2 (headless) discards Phase 1's fresh cf_clearance

    This would explain your scenario A (Headed bootstrap complete → No authenticated session).

    • The doc-comment on PerplexityClient.init() says Phase 2 "launches headless with the same persistent profile (now carrying fresh cf_clearance)" — client.ts:892.
    • The actual code at client.ts:918-929 does chromium.launch(...) + getOrCreateContext(...) — non-persistent. So the cf_clearance flushed to browserData/ by Phase 1 is never loaded by Phase 2.
    • Vault cookies are then injected via addCookies — but the cf_clearance in the vault is from login time. If your login was even ~30s ago, that clearance can already be invalid (CF rotates aggressively on UA/IP/fingerprint shifts), and the page navigation in Phase 2 hits the CF challenge and falls back to anonymous before checkAuth runs.
    • The H1 integration test in login-tier-end-to-end.integration.test.js passes because the mock server has no Cloudflare. In production this is a real handoff gap.

    Diagnostic gap

    Separately, getSavedCookies() returns [] silently when either (a) vault.enc doesn't exist for the daemon's resolved profile or (b) the cookies key is missing from a decryptable vault. Combined with the doctor's unseal-verify passing, this gives users no signal which of four failure modes they're hitting. We'll add explicit logging.

    What I need to confirm before writing the fix

    I want to be sure scenario A is the cf_clearance handoff and not a separate profile-mismatch issue that would survive Bug 2's fix. Could you attach the following to this issue?

    1. Full ~/.perplexity-mcp/daemon.log (it's the most informative artifact — please redact tokens if you want, the [trace] http lines aren't sensitive but tokens/cookies are).
    2. Get-Content $env:USERPROFILE\.perplexity-mcp\active — one-line file containing the active profile name.
    3. Get-ChildItem $env:USERPROFILE\.perplexity-mcp\profiles -Recurse -Name — confirms whether there's still a stale personal/vault.enc next to the new default/vault.enc.
    4. echo "%PERPLEXITY_PROFILE%" "%PERPLEXITY_HEADLESS_ONLY%" in a fresh cmd (System+User env vars) so I can see whether either is set system-wide.
    5. npx perplexity-user-mcp@0.8.42 doctor --json --probe — the full JSON output, not just the failing categories. The full output includes activeProfile and per-category checks that the truncated version in the issue body doesn't.

    A fresh Perplexity: Capture Diagnostics Bundle zip covers 1-4 in one shot and is the easiest way to send everything.

    What's coming in 0.8.43 once we confirm

    Fix 1 (HEADLESS_ONLY leak) — belt-and-suspenders:

    • Strip PERPLEXITY_HEADLESS_ONLY from stdio-daemon-proxy.ts auto-config env (it's redundant in proxy mode anyway).
    • Auto-regen existing IDE configs on next activation so your already-written Antigravity/Claude Code config gets rewritten without the dead flag.
    • Defensively strip both PERPLEXITY_HEADLESS_ONLY and PERPLEXITY_NO_DAEMON from spawnDetachedDaemon AND spawnBundledDaemon envs, so the daemon can never inherit these launcher-scoped flags from any future leak path.

    Fix 2 (Phase 2 cookie handoff) — persistent context + headed cookie injection:

    • Inject vault cookies in the headed phase too, so it authenticates from the start, fetches real account info, and the dashboard shows tier=Pro on first init rather than waiting for the headless recovery path.
    • Switch Phase 2 to chromium.launchPersistentContext(browserData, ...) so the fresh cf_clearance from Phase 1 carries over to the headless context.

    Diagnostic gap — add explicit log lines in getSavedCookies() for both "vault.enc missing" and "cookies key missing" so the next bug report can distinguish silent-empty from decrypt-failure.

    Will ping back as soon as you drop the diagnostics. Thanks again for the precise repro — both bugs were findable from your logs alone but the daemon.log will tell us whether there's a third thing hiding underneath.

  2. ARHAEEM commented on May 11, 2026

    @ARHAEEM
    Member

    Both bugs are fixed and in PR #7 — no need to wait for the diagnostics at this point, the root causes were clear enough from the original report.

    What was shipped in 0.8.43:

    • Bug 1 (HEADLESS_ONLY leak) — stripped from daemon spawn env at all three spawn sites (daemon/launcher.ts, extension/src/daemon/runtime.ts, stdio-daemon-proxy.ts). The auto-config for your existing IDE configs will be rewritten on next activation to drop the dead flag.
    • Bug 2 (Phase 2 discards cf_clearance) — Phase 2 now uses launchPersistentContext(browserData, ...), same directory as Phase 1, so the fresh clearance is on disk when Phase 2 starts. Vault cookies are injected selectively so a freshly-written cf_clearance is never overwritten by a stale vault copy.
    • Diagnostic gap — getSavedCookies() now logs a specific message for each silent-empty path (no vault.enc, cookies key absent, JSON parse failure, unseal error) so future bug reports will be much easier to triage.

    Additionally, as a bonus for this release: the dashboard now shows a live daemon auth status indicator in the hero panel (green when the daemon is authenticated, yellow warning when the daemon sees an anonymous session despite stored credentials). Clicking "Refresh state" in the dashboard will automatically touch the .reinit sentinel to force a daemon reinit — so if the mismatch happens again after a cold start you can recover without restarting the whole extension.

    PR: #7 — will close this issue on merge. The VSIX will be attached to the release once smoke passes on Win11 + macOS + Ubuntu.

  3. added a commit that references this issue on May 11, 2026
    0860ed9
  4. added a commit that references this issue on Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions