Skip to content

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

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

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