diff --git a/integrations/agent-plugin-core/python/telemetry.py b/integrations/agent-plugin-core/python/telemetry.py index 991c18c51..c1fc0084c 100644 --- a/integrations/agent-plugin-core/python/telemetry.py +++ b/integrations/agent-plugin-core/python/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 03434177a..accd631a6 100644 --- a/integrations/agent-plugin-core/tests/test_spool_delivery.py +++ b/integrations/agent-plugin-core/tests/test_spool_delivery.py @@ -430,3 +430,17 @@ def test_quarantined_batches_are_eventually_collected(telemetry): 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" + + +def test_temp_files_orphaned_by_a_kill_are_collected(telemetry): + """_write_identity and _install_salt unlink in a finally, which SIGKILL skips.""" + directory = telemetry.memory_core.data_dir() + directory.mkdir(parents=True, exist_ok=True) + orphan = directory / "telemetry-salt.999.tmp" + orphan.write_text("abandoned", encoding="utf-8") + stale = time.time() - (telemetry.CLAIM_STALE_SECONDS + 60) + os.utime(orphan, (stale, stale)) + + telemetry._sweep_debris(directory) + + assert not orphan.exists(), "a killed process left a temp file on disk forever" diff --git a/integrations/antigravity-plugin/core/telemetry.py b/integrations/antigravity-plugin/core/telemetry.py index 991c18c51..c1fc0084c 100644 --- a/integrations/antigravity-plugin/core/telemetry.py +++ b/integrations/antigravity-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 991c18c51..c1fc0084c 100644 --- a/integrations/claude-code-plugin/core/telemetry.py +++ b/integrations/claude-code-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 991c18c51..c1fc0084c 100644 --- a/integrations/codex-plugin/core/telemetry.py +++ b/integrations/codex-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 991c18c51..c1fc0084c 100644 --- a/integrations/cursor-plugin/core/telemetry.py +++ b/integrations/cursor-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 991c18c51..c1fc0084c 100644 --- a/integrations/kimi-plugin/core/telemetry.py +++ b/integrations/kimi-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.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 991c18c51..c1fc0084c 100644 --- a/integrations/mem0-agent-plugin/core/telemetry.py +++ b/integrations/mem0-agent-plugin/core/telemetry.py @@ -653,6 +653,15 @@ def _sweep_debris(directory: Path) -> None: quarantined.unlink() except OSError: continue + # The same reasoning covers *.tmp. _write_identity and _install_salt both + # create one and unlink it in a finally, which a SIGKILL skips, and no glob + # in this module matches the leftovers either. + for temporary in directory.glob("telemetry-*.tmp"): + try: + if now - temporary.stat().st_mtime > CLAIM_STALE_SECONDS: + temporary.unlink() + except OSError: + continue def _claim_parked(directory: Path) -> Path | None: