From 349f77e556b8087cb82d18cbe4ada59a2d6ce1ec Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Tue, 15 Sep 2026 00:36:23 +0530 Subject: [PATCH 1/3] fix(plugins): stop the new spool tests depending on ambient state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs agent-plugin-core/tests and claude-code-plugin/tests in one pytest process. Two things only show up in that combined run, so the suites passed locally and failed on every push. claude-code-plugin/tests/conftest.py sets MEM0_TELEMETRY=false at import, which is process-wide. record() then returns early and every assertion in test_spool_delivery.py saw an empty spool — nine failures, all reported as "recorded nothing" rather than as a disabled feature. The fixture now pins MEM0_TELEMETRY rather than trusting whatever collected first. The fixture also dropped telemetry/memory_core/_harness_id from sys.modules on teardown. That conftest imports memory_core once at collection and calls configure_harness() on it, so a later re-import got a fresh module with default harness config and test_memory_core failed depending on collection order. The fixture now saves and restores those entries instead of deleting them. Verified with CI's exact command rather than the narrower path I had been running: 266 passed, 8 skipped. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb --- .../tests/test_spool_delivery.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/integrations/agent-plugin-core/tests/test_spool_delivery.py b/integrations/agent-plugin-core/tests/test_spool_delivery.py index 08266cc98..a51027904 100644 --- a/integrations/agent-plugin-core/tests/test_spool_delivery.py +++ b/integrations/agent-plugin-core/tests/test_spool_delivery.py @@ -25,15 +25,32 @@ pytestmark = pytest.mark.skipif(not HOST_CORE.exists(), reason="claude-code-plug @pytest.fixture() def telemetry(tmp_path, monkeypatch): + # CI runs this directory and claude-code-plugin/tests in ONE pytest process, + # and that suite's conftest sets MEM0_TELEMETRY=false at import, process-wide. + # Without this the whole file silently no-ops: record() returns early and + # every assertion sees an empty spool. Do not rely on ambient env. + monkeypatch.setenv("MEM0_TELEMETRY", "true") monkeypatch.setenv("MEM0_CODE_DATA_DIR", str(tmp_path / "data")) monkeypatch.syspath_prepend(str(HOST_CORE)) - for name in ("telemetry", "memory_core", "_harness_id"): + + # Save and RESTORE rather than delete. claude-code-plugin/tests/conftest.py + # imports memory_core once at collection and calls configure_harness() on it; + # dropping the module left a later re-import with default harness config, so + # tests in that suite failed depending on collection order. + names = ("telemetry", "memory_core", "_harness_id") + saved = {name: sys.modules.get(name) for name in names} + for name in names: sys.modules.pop(name, None) + module = importlib.import_module("telemetry") monkeypatch.setattr(module, "resolve_distinct_id", lambda: ("tester@example.com", "")) - yield module - for name in ("telemetry", "memory_core", "_harness_id"): - sys.modules.pop(name, None) + try: + yield module + finally: + for name in names: + sys.modules.pop(name, None) + if saved[name] is not None: + sys.modules[name] = saved[name] def _delivered(payloads): From aa770aa65291da0d1c7a0360223e09033683993c Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Tue, 15 Sep 2026 00:37:33 +0530 Subject: [PATCH 2/3] docs(plugins): finish the telemetry sweep across the remaining surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first pass fixed the plugin README and the module docstring but left the same claim standing everywhere else. - docs/integrations/deepseek-plugin.mdx still said "Anonymous usage events". The TS SDK's telemetryId is the raw account email, so it is not anonymous. - The pause skill told users a "minimal anonymous telemetry ping" fires while paused. Same ping, same email. Corrected in the template, which regenerates into all six hosts. - integrations/zapier-mem0/README.md advertised telemetry the app does not have: there is no telemetry code in it at all. It now says what is actually true, that its requests carry source="ZAPIER". - The data directory listing is presented as exhaustive and had gone stale against this stack's two new files, telemetry-salt and install-state.json. Also replaced the property enumeration in both the README and the docs page. Review pointed out it omitted the configured model name among others — writing a fresh exhaustive list in a PR whose whole purpose is making docs match code reproduces the defect being fixed. It now describes the shape and points at where the rule is actually enforced, so it cannot drift again. Deliberately unchanged: docs/integrations/openclaw.mdx. OpenClaw hashes the email rather than sending it, which is materially different from the plugin and the SDK, so its claim is not wrong in the same way. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb --- docs/integrations/claude-code.mdx | 11 ++++++++--- docs/integrations/deepseek-plugin.mdx | 2 +- .../agent-plugin-core/skills/pause/SKILL.md.tmpl | 4 ++-- integrations/antigravity-plugin/skills/pause/SKILL.md | 4 ++-- integrations/claude-code-plugin/README.md | 6 +++++- integrations/claude-code-plugin/skills/pause/SKILL.md | 4 ++-- integrations/codex-plugin/skills/pause/SKILL.md | 4 ++-- integrations/cursor-plugin/skills/pause/SKILL.md | 4 ++-- integrations/kimi-plugin/skills/pause/SKILL.md | 4 ++-- integrations/mem0-agent-plugin/skills/pause/SKILL.md | 4 ++-- integrations/zapier-mem0/README.md | 2 +- 11 files changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/integrations/claude-code.mdx b/docs/integrations/claude-code.mdx index c4b5a2c35..4277bb90e 100644 --- a/docs/integrations/claude-code.mdx +++ b/docs/integrations/claude-code.mdx @@ -183,11 +183,16 @@ the same way the Python SDK and the CLI attribute theirs. Without a key they are sent under a random per-machine id. Each event carries the event name, the plugin version, the harness it ran in, -your OS and Python version, timings, counts, and a coarse failure label. -Repository and session identifiers are hashed with a random salt generated on -your machine and never sent, so they cannot be linked back to a repository name +your OS and Python version, and per-event properties describing what happened: +timings, counts, coarse outcome and failure labels, and which model was +configured. Repository and session identifiers are hashed with a random salt +generated on your machine, so they cannot be linked back to a repository name or path. +The exact set is enforced in code rather than by this list: every property is +filtered through a denylist of sensitive keys and credential-shaped values are +redacted before anything is sent. + Prompts, memory text, queries, file paths, repository names, and API keys are never sent. diff --git a/docs/integrations/deepseek-plugin.mdx b/docs/integrations/deepseek-plugin.mdx index 16b8badaf..0242496e1 100644 --- a/docs/integrations/deepseek-plugin.mdx +++ b/docs/integrations/deepseek-plugin.mdx @@ -114,7 +114,7 @@ Both tools also accept optional per-call `userId`, `agentId`, and `runId` params ## Telemetry -Writes are tagged `source="DEEPSEEK_HARNESS"` so Mem0 can attribute usage to this integration. Anonymous usage events include operation names, durations, result counts, and coarse failure kinds. Queries, memory text, entity IDs, and API keys are never included. Set `MEM0_TELEMETRY=false` to opt out. +Writes are tagged `source="DEEPSEEK_HARNESS"` so Mem0 can attribute usage to this integration. Usage events include operation names, durations, result counts, and coarse failure kinds. They are **not anonymous**: when an API key is configured they are sent under your Mem0 account email, the same way the SDK attributes its own. Queries, memory text, entity IDs, and API keys are never included. Set `MEM0_TELEMETRY=false` to opt out. This plugin is a developer preview and tracks the evolving DeepSeek Harness plugin API. diff --git a/integrations/agent-plugin-core/skills/pause/SKILL.md.tmpl b/integrations/agent-plugin-core/skills/pause/SKILL.md.tmpl index 9cc720c7a..569305ec7 100644 --- a/integrations/agent-plugin-core/skills/pause/SKILL.md.tmpl +++ b/integrations/agent-plugin-core/skills/pause/SKILL.md.tmpl @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "{{PLUGIN_ROOT}}/core/memory_cli.py" --harness "{{HARNESS_ID}}" {{PLUGIN_DATA_ARG}} pause diff --git a/integrations/antigravity-plugin/skills/pause/SKILL.md b/integrations/antigravity-plugin/skills/pause/SKILL.md index e88f7d315..77b4146b6 100644 --- a/integrations/antigravity-plugin/skills/pause/SKILL.md +++ b/integrations/antigravity-plugin/skills/pause/SKILL.md @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${ANTIGRAVITY_PLUGIN_ROOT}/core/memory_cli.py" --harness "antigravity" pause diff --git a/integrations/claude-code-plugin/README.md b/integrations/claude-code-plugin/README.md index 872071abe..b58f54c79 100644 --- a/integrations/claude-code-plugin/README.md +++ b/integrations/claude-code-plugin/README.md @@ -137,6 +137,8 @@ Local data lives in `${CLAUDE_PLUGIN_DATA}`: - `flush-worker.log`: whether memory creation succeeded - `plugin-errors.log`: hook errors (no credentials) - `telemetry.jsonl` / `telemetry-identity.json`: usage events and the id they are sent under +- `telemetry-salt`: random per-install salt for the repo and session hashes +- `install-state.json`: records that install has been counted once on this machine Mem0 receives captured user messages, Claude's answers, sidekick assignments and completed responses, and changed file paths. When a failed command is recorded, extraction can also include bounded command details and results. Complete files and general tool output stay on your machine. Values that look like credentials are redacted before anything is sent. @@ -146,7 +148,9 @@ Usage events (which hook ran, timing, result counts, failure types) so Mem0 can **These events are not anonymous.** When an API key is configured — which installing the plugin requires — events are sent under your Mem0 account email, the same way the Python SDK and the CLI attribute theirs. Without a key they are sent under a random per-machine id. -What each event carries: the event name, the plugin version, the harness it ran in, your OS and Python version, timings, counts, and a coarse failure label. Repository and session identifiers are hashed with a random salt generated on your machine and never sent, so they cannot be linked back to a repository name or path. +What each event carries: the event name, the plugin version, the harness it ran in, your OS and Python version, and per-event properties describing what happened — timings, counts, coarse outcome and failure labels, and which model was configured. Repository and session identifiers are hashed with a random salt generated on your machine, so they cannot be linked back to a repository name or path. + +Rather than restate a list that drifts, the exact set is enforced in code: `telemetry.record` filters every property through a denylist of sensitive keys and redacts credential-shaped values. See `_PRIVATE_KEYS` in `core/telemetry.py`. Prompts, memory text, queries, file paths, repository names, and API keys are never sent. diff --git a/integrations/claude-code-plugin/skills/pause/SKILL.md b/integrations/claude-code-plugin/skills/pause/SKILL.md index 5aef04f39..ce69d13d2 100644 --- a/integrations/claude-code-plugin/skills/pause/SKILL.md +++ b/integrations/claude-code-plugin/skills/pause/SKILL.md @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${CLAUDE_PLUGIN_ROOT}/core/memory_cli.py" --harness "claude-code" --plugin-data-dir "${CLAUDE_PLUGIN_DATA}" pause diff --git a/integrations/codex-plugin/skills/pause/SKILL.md b/integrations/codex-plugin/skills/pause/SKILL.md index d8a52f47b..c8f8ddd5f 100644 --- a/integrations/codex-plugin/skills/pause/SKILL.md +++ b/integrations/codex-plugin/skills/pause/SKILL.md @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${PLUGIN_ROOT}/core/memory_cli.py" --harness "codex" --plugin-data-dir "${PLUGIN_DATA}" pause diff --git a/integrations/cursor-plugin/skills/pause/SKILL.md b/integrations/cursor-plugin/skills/pause/SKILL.md index 634385145..17384c368 100644 --- a/integrations/cursor-plugin/skills/pause/SKILL.md +++ b/integrations/cursor-plugin/skills/pause/SKILL.md @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${CURSOR_PLUGIN_ROOT}/core/memory_cli.py" --harness "cursor" pause diff --git a/integrations/kimi-plugin/skills/pause/SKILL.md b/integrations/kimi-plugin/skills/pause/SKILL.md index 9f217a468..8f93e3bf7 100644 --- a/integrations/kimi-plugin/skills/pause/SKILL.md +++ b/integrations/kimi-plugin/skills/pause/SKILL.md @@ -7,8 +7,8 @@ disable-model-invocation: true # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${KIMI_PLUGIN_ROOT}/core/memory_cli.py" --harness "kimi" pause diff --git a/integrations/mem0-agent-plugin/skills/pause/SKILL.md b/integrations/mem0-agent-plugin/skills/pause/SKILL.md index c6316dd0b..a87d6c3ee 100644 --- a/integrations/mem0-agent-plugin/skills/pause/SKILL.md +++ b/integrations/mem0-agent-plugin/skills/pause/SKILL.md @@ -6,8 +6,8 @@ description: Pause Mem0 memory capture on this machine. Use when the user wants # Pause memory capture To pause (hooks stop capturing and sending session content; a minimal -anonymous telemetry ping still fires at session start unless -`MEM0_TELEMETRY=false`): +telemetry ping still fires at session start, under your Mem0 account email, +unless `MEM0_TELEMETRY=false`): ```bash python3 "${PLUGIN_ROOT}/core/memory_cli.py" --harness "coding-agent" pause diff --git a/integrations/zapier-mem0/README.md b/integrations/zapier-mem0/README.md index 7b3f5ed0f..dab21d3f3 100644 --- a/integrations/zapier-mem0/README.md +++ b/integrations/zapier-mem0/README.md @@ -32,7 +32,7 @@ pnpm test:unit # offline unit tests (mocked, no network) MEM0_API_KEY=m0-... pnpm test # unit + live E2E against api.mem0.ai ``` -Anonymous usage telemetry is sent to Mem0; opt out with `MEM0_TELEMETRY=false`. +This app sends no telemetry of its own. Its API requests carry `source: "ZAPIER"` so Mem0 can see aggregate usage of the integration. To deploy (maintainers): `pnpm build && zapier push`. From f8a9d524be5e1db738c6514ae07ab61d8b3a0bef Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Tue, 15 Sep 2026 22:44:17 +0530 Subject: [PATCH 3/3] docs(openclaw): correct the anonymity claim to match how it identifies events The sweep in aa770aa6 deliberately left this page alone, reasoning that hashing the email is materially different from sending it. Reading integrations/openclaw/telemetry.ts does not support that: distinctId() is an unsalted sha256 of the account email, and Mem0 holds the emails it is derived from, so recovering the account is a table join. resolveEmail() also rewrites already-queued events onto that id, and identifyAnonymous() fires a PostHog $identify that merges the prior random id into it for good. That is pseudonymous, not anonymous, and it is the same mismatch between the stated privacy posture and the wire format that this stack exists to close. The opt-out is unchanged and still correct. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb --- docs/integrations/openclaw.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/integrations/openclaw.mdx b/docs/integrations/openclaw.mdx index a9642d444..fd7d449d9 100644 --- a/docs/integrations/openclaw.mdx +++ b/docs/integrations/openclaw.mdx @@ -481,7 +481,9 @@ Plugin config is stored in `~/.openclaw/openclaw.json` with file permissions `0o ### Telemetry -Anonymous usage telemetry (PostHog) is enabled by default to help improve the plugin. No conversation content or memory values are included, only event counts (recall, capture, tool usage, CLI commands). +Usage telemetry (PostHog) is enabled by default to help improve the plugin. No conversation content or memory values are included, only event counts (recall, capture, tool usage, CLI commands). + +These events are **not anonymous**. OpenClaw does not send your account email the way the SDK does, but it does send an unsalted SHA-256 hash of it, falling back to a hash of the API key and then to a random per-machine id. Mem0 holds the email the hash is derived from, so the hash identifies your account rather than concealing it. The first run that resolves an account also emits a PostHog `$identify`, which permanently merges any earlier random id into that identity. To opt out, set the environment variable: