Where a Claude Code plugin keeps Sume state: CLAUDE_PLUGIN_DATA
A plugin's data directory survives updates, while its root changes. Where to put a Sume call log or cache, and when uninstalling deletes it.

Write plugin state, such as a log of Sume calls, to ${CLAUDE_PLUGIN_DATA}, not to the plugin root. The plugin manifest reference says the data directory is ~/.claude/plugins/data/<id>/, is created on first reference, and is kept across plugin updates. ${CLAUDE_PLUGIN_ROOT} changes when the plugin updates, so anything written there can vanish.
By default Claude Code deletes the data directory when you uninstall the plugin from the last place it is installed, unless you use --keep-data.
Root versus data
The reference defines three path variables.
| Variable | Resolves to | Use for a Sume plugin |
|---|---|---|
| CLAUDE_PLUGIN_ROOT | The installed version of the plugin | Hook scripts and bundled files; do not write state here |
| CLAUDE_PLUGIN_DATA | ~/.claude/plugins/data/<id>/ | A call log, a cache of catalog lookups, saved job notes |
| CLAUDE_PROJECT_DIR | The project root | Project-local config the team commits |
A hook that records Sume calls
Hook commands receive CLAUDE_PLUGIN_DATA in their environment. This script appends the tool name and idempotency key of each call. Register it as a PreToolUse command hook in the plugin's hooks/hooks.json with a matcher for the Sume prefix.
import json
import os
import sys
def main() -> None:
data = json.load(sys.stdin)
folder = os.environ.get("CLAUDE_PLUGIN_DATA")
if not folder:
return
os.makedirs(folder, exist_ok=True)
args = data.get("tool_input") or {}
row = {"tool": data.get("tool_name"), "key": args.get("idempotency_key")}
with open(os.path.join(folder, "sume-calls.jsonl"), "a") as out:
out.write(json.dumps(row) + "\n")
main()Why the idempotency key is worth keeping
Sume's docs describe idempotency_key as a stable key for transport and dedup on write and paid tools. If a session dies after you submit, the log tells you which key you used, so you can resend the same call with the same key rather than starting a second paid job. Treat that as a habit, not a guarantee: read Sume's docs for how a repeated key behaves before you rely on it for billing.
- Do not log the API key or any bearer token.
- Use
--keep-datawhen you uninstall if the log matters. - Do not store state in the plugin root; an update replaces it.
Naming and cleaning up
The <id> in the path is the plugin identifier with every character other than a letter, digit, underscore or hyphen replaced by a hyphen, so a plugin named sume-media lands in a folder you can guess. Keep file names inside it stable and add a version field to any file format you write, because a plugin update keeps the folder but may change what the new hooks expect.
Rotate the log. A line per call is tiny, but a month of automated renders adds up, and a log that grows without limit is the sort of thing nobody notices until a disk fills.
A short test
Install the plugin, trigger one hook, and look for sume-calls.jsonl in the data folder. Update the plugin to a new version and confirm the file is still there. Uninstall without --keep-data, and confirm it is gone. Three checks, five minutes, and you will know exactly how the log behaves.
What Sume does not do
Sume does not read the local log, and the file is not a source of truth for spend. The source of truth for jobs is jobs_list on the Sume side, and a local file can fall behind it.
Sources
Related posts
More in Developers
- Ship a Sume Claude Code plugin off by default: defaultEnabled false
A manifest can set defaultEnabled to false, so a Sume plugin with paid tools starts disabled until someone turns it on. What persists after updates.
- Plugin headersHelper cannot read user_config: where the Sume key goes
Claude Code rejects ${user_config.*} inside an MCP headersHelper. Use the headers field for a Sume API key, or fetch the key inside the helper script yourself.
- Name your Sume plugin right: claude- and anthropic- prefixes fail
claude plugin validate errors on plugin names that pass as Anthropic's own. Pick a name for a Sume hosted MCP plugin that validates, and see what still loads.
- Plugin lists Sume twice? In Claude Code the later declaration wins
Claude Code loads a plugin's .mcp.json first, then mcpServers from plugin.json; a name declared later replaces an earlier one. Keep one Sume entry.
Written by Sume