claude-code - 💡(How to fix) Fix Cowork Desktop: ${CLAUDE_PLUGIN_DATA} is not persistent across conversations (MCP plugin tokens lost on every new conversation)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

${CLAUDE_PLUGIN_DATA} — the environment variable documented as the persistent, per-plugin storage location for MCP plugins — resolves to a per-conversation session-scoped path in Cowork desktop. Any file a plugin writes there is discarded when the user starts a new conversation, even though Cowork itself has not restarted and the plugin is re-loaded from the marketplace cleanly.

For any MCP plugin that performs an OAuth flow and caches the resulting token under ${CLAUDE_PLUGIN_DATA}, this means the user is forced to re-authenticate on every new conversation, which is contrary to the documented purpose of the variable and breaks the "stay signed in" expectation users bring from every other Claude product.

Reporter: Greg Baden, SVP & General Counsel / AI Initiative Lead, NetBrain Technologies
Contact: [email protected]
Anthropic org: NetBrain Technologies (Enterprise)
Date observed: 2026-04-20
Severity: Blocks 400-user rollout of our custom Salesforce MCP plugin
Product area: Cowork desktop, MCP plugin runtime, plugin-data storage


Error Message

The first row is the surprising one. Everywhere else under mnt/ that we expect to be persistent (auto-memory, workspace folders) is persistent. .claude/plugins/data/ is the only exception — and it is the one location a plugin is explicitly directed to use.

Root Cause

${CLAUDE_PLUGIN_DATA} — the environment variable documented as the persistent, per-plugin storage location for MCP plugins — resolves to a per-conversation session-scoped path in Cowork desktop. Any file a plugin writes there is discarded when the user starts a new conversation, even though Cowork itself has not restarted and the plugin is re-loaded from the marketplace cleanly.

For any MCP plugin that performs an OAuth flow and caches the resulting token under ${CLAUDE_PLUGIN_DATA}, this means the user is forced to re-authenticate on every new conversation, which is contrary to the documented purpose of the variable and breaks the "stay signed in" expectation users bring from every other Claude product.

Reporter: Greg Baden, SVP & General Counsel / AI Initiative Lead, NetBrain Technologies
Contact: [email protected]
Anthropic org: NetBrain Technologies (Enterprise)
Date observed: 2026-04-20
Severity: Blocks 400-user rollout of our custom Salesforce MCP plugin
Product area: Cowork desktop, MCP plugin runtime, plugin-data storage


Fix Action

Fix / Workaround

We are also tracking a separate Cowork bug observed the same day: the admin "Allow network egress" domain allowlist does not actually enforce entries when set to "Package managers only" with additional specific domains added. Wildcard entries (e.g., *.salesforce.com) do not resolve hostnames (e.g., login.salesforce.com) inside the plugin runtime — DNS fails with EAI_AGAIN. Switching the dropdown to "All domains" is the only workaround. Happy to bundle that into a separate ticket or include here — let us know which is preferred.

Suggested workaround for plugin authors in the meantime

Code Example

status: authenticated
instance_url: https://nbtech.my.salesforce.com
token_path: /sessions/vigilant-laughing-pascal/mnt/.claude/plugins/data/salesforce-mcp-inline/salesforce-token.json
has_refresh_token: true

---

token_path: /sessions/wonderful-peaceful-newton/mnt/.claude/plugins/data/salesforce-mcp-inline/salesforce-token.json

---

$ ls -la /sessions/wonderful-peaceful-newton/mnt/.claude/plugins/data/salesforce-mcp-inline/
salesforce-mcp.log      <- log file present (newly created for this session)
                        <- no salesforce-token.json — directory is otherwise empty

---

$ ls -la /sessions/vigilant-laughing-pascal/
drwxr-x--- 6 nobody nogroup ...       <- owned by a different session user,
                                         locked out by Unix permissions

---

{
  "mcpServers": {
    "netbrain-salesforce": {
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/servers/salesforce-mcp-server.js"],
      "env": {
        "TOKEN_CACHE_PATH": "${CLAUDE_PLUGIN_DATA}/salesforce-token.json"
      }
    }
  }
}
RAW_BUFFERClick to expand / collapse

Summary

${CLAUDE_PLUGIN_DATA} — the environment variable documented as the persistent, per-plugin storage location for MCP plugins — resolves to a per-conversation session-scoped path in Cowork desktop. Any file a plugin writes there is discarded when the user starts a new conversation, even though Cowork itself has not restarted and the plugin is re-loaded from the marketplace cleanly.

For any MCP plugin that performs an OAuth flow and caches the resulting token under ${CLAUDE_PLUGIN_DATA}, this means the user is forced to re-authenticate on every new conversation, which is contrary to the documented purpose of the variable and breaks the "stay signed in" expectation users bring from every other Claude product.

Reporter: Greg Baden, SVP & General Counsel / AI Initiative Lead, NetBrain Technologies
Contact: [email protected]
Anthropic org: NetBrain Technologies (Enterprise)
Date observed: 2026-04-20
Severity: Blocks 400-user rollout of our custom Salesforce MCP plugin
Product area: Cowork desktop, MCP plugin runtime, plugin-data storage


Expected behavior

Per Anthropic's MCP plugin documentation, ${CLAUDE_PLUGIN_DATA} should resolve to a directory that persists across conversations, sessions, and Cowork restarts, scoped to the plugin + user. A token written there on Monday should still be readable on Tuesday — the whole reason the variable exists.

Anthropic's own AI support agent confirmed this expectation verbatim when asked about the issue (2026-04-20):

"The ${CLAUDE_PLUGIN_DATA} directory should be persistent across conversations and plugin updates. It resolves to ~/.claude/plugins/data/{id}/ where {id} is your plugin identifier. This directory is specifically designed to survive updates and store things like node_modules, Python virtual environments, generated code, and caches that should persist across plugin versions. […] The data directory is only deleted when you uninstall the plugin from all scopes where it's installed. If it's not persisting between conversations, this suggests there may be a bug with how the directory is being handled."

On Cowork desktop we are observing precisely the "not persisting between conversations" behavior the agent flags as bug-shaped. Note also the key phrasing "resolves to ~/.claude/plugins/data/{id}/" — a host-filesystem path, not a sandboxed-VM path. Our observation is that Cowork is resolving it to a VM-internal location under /sessions/<session-id>/mnt/.claude/plugins/data/<plugin>-inline/ instead.

Actual behavior

${CLAUDE_PLUGIN_DATA} resolves to /sessions/<session-id>/mnt/.claude/plugins/data/<plugin-name>-inline/, where <session-id> is a random per-conversation name (e.g., vigilant-laughing-pascal, wonderful-peaceful-newton). The directory is part of a per-session VM user's home, is not readable by later sessions, and effectively no longer exists from a plugin's point of view once the conversation ends.


Reproduction steps

  1. Install any MCP plugin that caches state under ${CLAUDE_PLUGIN_DATA}. (Our plugin is salesforce-mcp v0.2.2, published to NetBrain's private marketplace — plugin manifest sets "TOKEN_CACHE_PATH": "${CLAUDE_PLUGIN_DATA}/salesforce-token.json".)
  2. In a Cowork conversation, complete the OAuth Device Flow. Plugin writes a token file to ${CLAUDE_PLUGIN_DATA}/salesforce-token.json and logs the absolute path.
  3. Confirm the plugin reports status: authenticated with has_refresh_token: true.
  4. Fully quit Cowork (Cmd+Q on macOS).
  5. Relaunch Cowork. Start a new conversation.
  6. In the new conversation, query the plugin's auth status (salesforce_check_auth).

Observed: Plugin reports "not connected — no token is cached for this session." User must re-run the entire OAuth flow, including MFA.


Evidence

In the authenticating conversation, salesforce_check_auth returned:

status: authenticated
instance_url: https://nbtech.my.salesforce.com
token_path: /sessions/vigilant-laughing-pascal/mnt/.claude/plugins/data/salesforce-mcp-inline/salesforce-token.json
has_refresh_token: true

In the next conversation (after Cowork quit+relaunch), the plugin's own startup log shows it resolving CLAUDE_PLUGIN_DATA to a different path:

token_path: /sessions/wonderful-peaceful-newton/mnt/.claude/plugins/data/salesforce-mcp-inline/salesforce-token.json

Filesystem state in the new session:

$ ls -la /sessions/wonderful-peaceful-newton/mnt/.claude/plugins/data/salesforce-mcp-inline/
salesforce-mcp.log      <- log file present (newly created for this session)
                        <- no salesforce-token.json — directory is otherwise empty

The previous session's directory still exists on the host but is unreadable from the new session:

$ ls -la /sessions/vigilant-laughing-pascal/
drwxr-x--- 6 nobody nogroup ...       <- owned by a different session user,
                                         locked out by Unix permissions

The wonderful-peaceful-newton session cannot read vigilant-laughing-pascal's directory, so even if the file survived on the host, the plugin in the new session has no way to reach it.

Plugin manifest (.mcp.json): — uses the documented pattern exactly:

{
  "mcpServers": {
    "netbrain-salesforce": {
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/servers/salesforce-mcp-server.js"],
      "env": {
        "TOKEN_CACHE_PATH": "${CLAUDE_PLUGIN_DATA}/salesforce-token.json"
      }
    }
  }
}

What does persist across conversations (for comparison)

We tested by writing sentinel files to six candidate locations in one conversation and checking each from a new conversation. Within the same plugin sandbox:

LocationPersists across new conversations?
/sessions/<id>/mnt/.claude/plugins/data/<plugin>/ (i.e. ${CLAUDE_PLUGIN_DATA})No
/sessions/<id>/mnt/.claude/ root(tested — result in attached sentinel notes)
/sessions/<id>/mnt/.auto-memory/Yes (auto-memory files from prior conversations are present)
/sessions/<id>/mnt/<user-selected-workspace-folder>/Yes (host-mounted)
/sessions/<id>/ (VM home)No — session-scoped
/sessions/<id>/tmp/No

The first row is the surprising one. Everywhere else under mnt/ that we expect to be persistent (auto-memory, workspace folders) is persistent. .claude/plugins/data/ is the only exception — and it is the one location a plugin is explicitly directed to use.


Impact

  • Blocks our planned rollout of the NetBrain Salesforce MCP plugin to ~400 users. Forcing every Salesforce user through an MFA challenge at the start of every Cowork conversation is not acceptable in a security-conscious enterprise.
  • Affects every OAuth-backed MCP plugin on Cowork desktop, not just ours — any plugin following the documented ${CLAUDE_PLUGIN_DATA} pattern has the same problem (Google Workspace, Slack, Zoom, Microsoft, Notion, etc.).
  • The problem is silent: plugins appear to work during initial QA (within one conversation) and only surface after users start a second conversation, so this is very easy to miss in pre-publish validation.

Related issue

We are also tracking a separate Cowork bug observed the same day: the admin "Allow network egress" domain allowlist does not actually enforce entries when set to "Package managers only" with additional specific domains added. Wildcard entries (e.g., *.salesforce.com) do not resolve hostnames (e.g., login.salesforce.com) inside the plugin runtime — DNS fails with EAI_AGAIN. Switching the dropdown to "All domains" is the only workaround. Happy to bundle that into a separate ticket or include here — let us know which is preferred.


Requested fix (one of)

  1. Preferred: make ${CLAUDE_PLUGIN_DATA} resolve to a stable, user-scoped, plugin-scoped path that survives new conversations and Cowork restarts — matching the documented behavior.
  2. Alternatively, expose a new, clearly-named environment variable for genuinely-persistent plugin storage (e.g., CLAUDE_PLUGIN_DATA_PERSISTENT or CLAUDE_USER_PLUGIN_DATA) and update documentation so plugin authors know to use it for credentials.
  3. If the session-scoped behavior is intentional for some reason, update the plugin documentation to call this out explicitly, and provide a supported path for OAuth token persistence.

Suggested workaround for plugin authors in the meantime

If we have to ship against the current behavior, the plugin could be modified to probe an ordered list of candidate paths on startup (e.g., $HOME/.salesforce-mcp-token.json, ${CLAUDE_PLUGIN_DATA}, or a known-persistent mount like /sessions/*/mnt/.auto-memory/) and migrate an existing token file into ${CLAUDE_PLUGIN_DATA} on first access. This is hacky and depends on Cowork's internal filesystem layout. A first-class fix from Anthropic is preferable.


Contact

Please reply to [email protected]. Able to provide:

  • Full plugin source (TypeScript, open to your engineers)
  • Plugin debug logs with full err.cause chains (added in v0.2.2 specifically to capture issues like this)
  • Screenshots of the successful auth in session A followed by the "not connected" state in session B
  • Access to a NetBrain Enterprise Cowork environment to reproduce live

extent analysis

TL;DR

The most likely fix is to update the ${CLAUDE_PLUGIN_DATA} environment variable to resolve to a stable, user-scoped, and plugin-scoped path that survives new conversations and Cowork restarts.

Guidance

  • Verify that the ${CLAUDE_PLUGIN_DATA} environment variable is being set correctly and is not being overridden by any other configuration or code.
  • Check the plugin's documentation and code to ensure that it is using the correct path for persistent storage, as suggested by the Anthropic support agent.
  • Consider implementing a workaround, such as probing an ordered list of candidate paths on startup, to migrate an existing token file into ${CLAUDE_PLUGIN_DATA} on first access.
  • Test the plugin with different conversation scenarios to ensure that the token is being persisted correctly.

Example

No code example is provided as the issue is related to environment variable configuration and plugin behavior, rather than a specific code snippet.

Notes

The issue seems to be related to the way Cowork desktop handles the ${CLAUDE_PLUGIN_DATA} environment variable, and it's not clear if this is a bug or an intended behavior. The suggested workaround may help mitigate the issue, but a first-class fix from Anthropic is preferable.

Recommendation

Apply a workaround, such as probing an ordered list of candidate paths on startup, to migrate an existing token file into ${CLAUDE_PLUGIN_DATA} on first access, until a permanent fix is provided by Anthropic. This will allow the plugin to function as expected, although it may not be the most elegant solution.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Per Anthropic's MCP plugin documentation, ${CLAUDE_PLUGIN_DATA} should resolve to a directory that persists across conversations, sessions, and Cowork restarts, scoped to the plugin + user. A token written there on Monday should still be readable on Tuesday — the whole reason the variable exists.

Anthropic's own AI support agent confirmed this expectation verbatim when asked about the issue (2026-04-20):

"The ${CLAUDE_PLUGIN_DATA} directory should be persistent across conversations and plugin updates. It resolves to ~/.claude/plugins/data/{id}/ where {id} is your plugin identifier. This directory is specifically designed to survive updates and store things like node_modules, Python virtual environments, generated code, and caches that should persist across plugin versions. […] The data directory is only deleted when you uninstall the plugin from all scopes where it's installed. If it's not persisting between conversations, this suggests there may be a bug with how the directory is being handled."

On Cowork desktop we are observing precisely the "not persisting between conversations" behavior the agent flags as bug-shaped. Note also the key phrasing "resolves to ~/.claude/plugins/data/{id}/" — a host-filesystem path, not a sandboxed-VM path. Our observation is that Cowork is resolving it to a VM-internal location under /sessions/<session-id>/mnt/.claude/plugins/data/<plugin>-inline/ instead.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Cowork Desktop: ${CLAUDE_PLUGIN_DATA} is not persistent across conversations (MCP plugin tokens lost on every new conversation)