From c043e97673c3ba6cba52375d94ce8fa14a14115f Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Thu, 17 Sep 2026 19:27:39 +0530 Subject: [PATCH 1/2] fix(plugins): read the salt before minting one, and stop overclaiming backend support Two findings from an independent review of this branch. _install_salt went straight to create, fsync, link, unlink on every call. All but the first process finds the salt already published, so each hook paid an fsync to discover that, on a path documented as appending a line and returning. Hooks are separate processes firing on every tool call inside a few-second budget. Measured: cold process one fsync, warm process zero, same salt. The deepseek README said the backend recognizes DEEPSEEK_HARNESS so usage surfaces by name. It does not yet. That value, along with STRANDS, ZAPIER, MEM0_PLUGIN, PI_AGENT and VERCEL_AI_SDK, buckets into OTHERS until mem0ai/platform#3602 ships, so the README now states the dependency and links it. The neighbouring comment in mem0-strands was already accurate and is unchanged: it says recognized values live in the allowlist without claiming this one is in it. 265 passed, 8 skipped. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb --- integrations/agent-plugin-core/python/telemetry.py | 12 ++++++++++++ integrations/antigravity-plugin/core/telemetry.py | 12 ++++++++++++ integrations/claude-code-plugin/core/telemetry.py | 12 ++++++++++++ integrations/codex-plugin/core/telemetry.py | 12 ++++++++++++ integrations/cursor-plugin/core/telemetry.py | 12 ++++++++++++ integrations/deepseek-plugin/README.md | 2 +- integrations/kimi-plugin/core/telemetry.py | 12 ++++++++++++ integrations/mem0-agent-plugin/core/telemetry.py | 12 ++++++++++++ 8 files changed, 85 insertions(+), 1 deletion(-) diff --git a/integrations/agent-plugin-core/python/telemetry.py b/integrations/agent-plugin-core/python/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/agent-plugin-core/python/telemetry.py +++ b/integrations/agent-plugin-core/python/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/antigravity-plugin/core/telemetry.py b/integrations/antigravity-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/antigravity-plugin/core/telemetry.py +++ b/integrations/antigravity-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/claude-code-plugin/core/telemetry.py b/integrations/claude-code-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/claude-code-plugin/core/telemetry.py +++ b/integrations/claude-code-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/codex-plugin/core/telemetry.py b/integrations/codex-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/codex-plugin/core/telemetry.py +++ b/integrations/codex-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/cursor-plugin/core/telemetry.py b/integrations/cursor-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/cursor-plugin/core/telemetry.py +++ b/integrations/cursor-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/deepseek-plugin/README.md b/integrations/deepseek-plugin/README.md index bcd333311..d5cb3345a 100644 --- a/integrations/deepseek-plugin/README.md +++ b/integrations/deepseek-plugin/README.md @@ -86,7 +86,7 @@ Per-call `userId` overrides are rejected unless the operator enables `allowUserO ## Telemetry -Writes are tagged `source="DEEPSEEK_HARNESS"`, which the Mem0 backend recognizes so usage surfaces by name rather than bucketing into `OTHERS`. +Writes are tagged `source="DEEPSEEK_HARNESS"`. That value has to exist in the backend's `EventSource` enum for usage to surface by name; until it does, these writes read as `OTHERS`. It is added by [mem0ai/platform#3602](https://github.com/mem0ai/platform/pull/3602), which has to ship before this claim is true. The plugin also sends usage events (which tool ran, duration, result counts, coarse failure kind) so Mem0 can tell how the plugin is used and where it breaks. These 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, and entity ids are never sent. Turn it off with `MEM0_TELEMETRY=false`. diff --git a/integrations/kimi-plugin/core/telemetry.py b/integrations/kimi-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/kimi-plugin/core/telemetry.py +++ b/integrations/kimi-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) diff --git a/integrations/mem0-agent-plugin/core/telemetry.py b/integrations/mem0-agent-plugin/core/telemetry.py index d86873d3d..132de5b0c 100644 --- a/integrations/mem0-agent-plugin/core/telemetry.py +++ b/integrations/mem0-agent-plugin/core/telemetry.py @@ -133,6 +133,18 @@ def _install_salt() -> str: return _salt_cache path = _salt_path() + # Read before writing. Hooks are separate processes firing on every tool + # call, so all but the first find the salt already published; going straight + # to create-fsync-link-unlink meant every one of them paid an fsync to + # discover that, on a path whose whole promise is appending a line and + # returning. + try: + _salt_cache = path.read_text(encoding="utf-8").strip() + if _salt_cache: + return _salt_cache + except OSError: + pass + temporary = path.with_name(f"{path.name}.{os.getpid()}.tmp") try: path.parent.mkdir(parents=True, exist_ok=True) From 4edfaabc0604637a4c44b22aeea2f83212eeff97 Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Thu, 17 Sep 2026 19:28:08 +0530 Subject: [PATCH 2/2] fix(plugins): collect quarantined batches instead of leaving them on disk forever Review finding. _sweep_debris globbed only *.partial. The *.corrupt files this PR writes when a batch cannot be decoded are matched by no glob in the module, so they accumulated for the life of the install. Collected on the expiry window rather than the stale window, deliberately: a quarantined batch is the only remaining evidence of events that could not be delivered, so someone chasing a report of missing telemetry has to be able to find a recent one. Debris keeps the short window; it carries nothing. One test, asserting both halves: a recent quarantine survives and an expired one does not. Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb --- .../agent-plugin-core/python/telemetry.py | 18 +++++++++++++--- .../tests/test_spool_delivery.py | 21 +++++++++++++++++++ .../antigravity-plugin/core/telemetry.py | 18 +++++++++++++--- .../claude-code-plugin/core/telemetry.py | 18 +++++++++++++--- integrations/codex-plugin/core/telemetry.py | 18 +++++++++++++--- integrations/cursor-plugin/core/telemetry.py | 18 +++++++++++++--- integrations/kimi-plugin/core/telemetry.py | 18 +++++++++++++--- .../mem0-agent-plugin/core/telemetry.py | 18 +++++++++++++--- 8 files changed, 126 insertions(+), 21 deletions(-) diff --git a/integrations/agent-plugin-core/python/telemetry.py b/integrations/agent-plugin-core/python/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/agent-plugin-core/python/telemetry.py +++ b/integrations/agent-plugin-core/python/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/agent-plugin-core/tests/test_spool_delivery.py b/integrations/agent-plugin-core/tests/test_spool_delivery.py index ac0bf6b22..03434177a 100644 --- a/integrations/agent-plugin-core/tests/test_spool_delivery.py +++ b/integrations/agent-plugin-core/tests/test_spool_delivery.py @@ -409,3 +409,24 @@ def test_partial_files_are_swept(telemetry): telemetry.flush() assert not debris.exists() + + +def test_quarantined_batches_are_eventually_collected(telemetry): + """Nothing re-globs .corrupt, so without a sweep they live on disk forever. + + Kept much longer than .partial debris on purpose: a quarantined batch is the + only remaining evidence of events that could not be delivered. + """ + directory = telemetry.memory_core.data_dir() + directory.mkdir(parents=True, exist_ok=True) + fresh = directory / "telemetry-1-aaaaaaaa-a0.corrupt" + old = directory / "telemetry-2-bbbbbbbb-a0.corrupt" + for path in (fresh, old): + path.write_text("torn", encoding="utf-8") + expired = time.time() - (telemetry.CLAIM_EXPIRY_SECONDS + 60) + os.utime(old, (expired, expired)) + + telemetry._sweep_debris(directory) + + assert fresh.exists(), "a recent quarantine was discarded before anyone could look at it" + assert not old.exists(), "an expired quarantine was left on disk forever" diff --git a/integrations/antigravity-plugin/core/telemetry.py b/integrations/antigravity-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/antigravity-plugin/core/telemetry.py +++ b/integrations/antigravity-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/claude-code-plugin/core/telemetry.py b/integrations/claude-code-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/claude-code-plugin/core/telemetry.py +++ b/integrations/claude-code-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/codex-plugin/core/telemetry.py b/integrations/codex-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/codex-plugin/core/telemetry.py +++ b/integrations/codex-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/cursor-plugin/core/telemetry.py b/integrations/cursor-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/cursor-plugin/core/telemetry.py +++ b/integrations/cursor-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/kimi-plugin/core/telemetry.py b/integrations/kimi-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/kimi-plugin/core/telemetry.py +++ b/integrations/kimi-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: diff --git a/integrations/mem0-agent-plugin/core/telemetry.py b/integrations/mem0-agent-plugin/core/telemetry.py index f46e1bf55..9084c8a24 100644 --- a/integrations/mem0-agent-plugin/core/telemetry.py +++ b/integrations/mem0-agent-plugin/core/telemetry.py @@ -462,10 +462,16 @@ def _claim_spool() -> Path | None: def _sweep_debris(directory: Path) -> None: - """Remove temp files orphaned by a crash between write and rename. + """Remove files nothing else will ever pick up again. - Neither glob in this module matches *.partial, so nothing else would ever - clean them up. + *.partial is a temp file orphaned by a crash between write and rename. + *.corrupt is a batch quarantined for undecodable content. No glob in this + module matches either, so without this they accumulate on disk for the life + of the install. + + Quarantined batches are kept far longer than debris: they are the only + evidence left of events that could not be delivered, and someone diagnosing + a report of missing telemetry has to be able to find one. """ now = time.time() for debris in directory.glob("telemetry-*.partial"): @@ -474,6 +480,12 @@ def _sweep_debris(directory: Path) -> None: debris.unlink() except OSError: continue + for quarantined in directory.glob("telemetry-*.corrupt"): + try: + if now - quarantined.stat().st_mtime > CLAIM_EXPIRY_SECONDS: + quarantined.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: