Skip to content

Python: [Bug]: FileCheckpointStorage.delete leaks FileNotFoundError during concurrent deletion #8255

Closed

Description

@fzfzzfzzzfzzzz

Description

FileCheckpointStorage.delete() performs an exists() check followed by unlink(). Those operations are not atomic. If two callers delete the same checkpoint concurrently, both can observe that the file exists; one removes it and the other leaks a raw FileNotFoundError.

The CheckpointStorage.delete() contract says the method returns True when a checkpoint was deleted and False when it does not exist. A concurrent disappearance should therefore produce False, not an undocumented filesystem exception.

Expected behavior: concurrent deletes complete with one True and one False, without leaking FileNotFoundError.

I intend to submit a small fix that attempts unlink() directly, catches FileNotFoundError, and adds a deterministic concurrency regression test.

Code Sample

import asyncio
import tempfile
import threading
from pathlib import Path
from unittest.mock import patch

from agent_framework import FileCheckpointStorage, WorkflowCheckpoint


async def main() -> None:
    with tempfile.TemporaryDirectory() as directory:
        storage = FileCheckpointStorage(directory)
        checkpoint = WorkflowCheckpoint(
            workflow_name="wf",
            graph_signature_hash="hash",
            checkpoint_id="same",
        )
        await storage.save(checkpoint)

        target = (Path(directory) / "same.json").resolve()
        original_exists = Path.exists
        barrier = threading.Barrier(2)

        def synchronized_exists(path: Path) -> bool:
            result = original_exists(path)
            if path.resolve() == target:
                barrier.wait()
            return result

        with patch.object(Path, "exists", synchronized_exists):
            results = await asyncio.gather(
                storage.delete("same"),
                storage.delete("same"),
                return_exceptions=True,
            )

        print(results)  # [True, FileNotFoundError(...)] in either order


asyncio.run(main())

Error Messages / Stack Traces

FileNotFoundError: [WinError 2] The system cannot find the file specified: '.../same.json'

Package Versions

agent-framework-core: source checkout of main at d7823b2

Python Version

Python 3.11.9

Additional Context

This is separate from #7748/#7757, which address concurrent save() operations and shared temporary files. This report concerns only the delete() check-then-unlink race.

AI assistance was used to audit the implementation and prepare the deterministic reproduction; the behavior was verified against the referenced source checkout.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 10, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 10, 2026
  3. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    python/packages/core/agent_framework/_workflows/_checkpoint.py::FileCheckpointStorage.delete at lines 405-423 leaks FileNotFoundError when concurrent callers both pass Path.exists() before either calls unlink(). Minimal repro: save one checkpoint, synchronize two delete calls at Path.exists() with a thread barrier, then await both calls and assert the results are one True and one False.

    • Failing test: python/packages/core/tests/workflow/test_checkpoint.py::test_file_checkpoint_storage_concurrent_delete
    • Files examined: python/packages/core/agent_framework/_workflows/_checkpoint.py, python/packages/core/tests/workflow/test_checkpoint.py, python/packages/core/tests/workflow/test_checkpoint_storage_conformance.py, python/packages/core/pyproject.toml
    • Tests run: python/packages/core/tests/workflow/test_checkpoint.py::test_file_checkpoint_storage_concurrent_delete
    • Reported version: source checkout d7823b2
    • Current version: 1.18.0
  4. added
    workflowsUsage: [Issues, PRs], Target: Workflows
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

pythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowworkflowsUsage: [Issues, PRs], Target: Workflows

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions