Repository navigation
Description
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.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Sep 10, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
python/packages/core/agent_framework/_workflows/_checkpoint.py::FileCheckpointStorage.deleteat lines 405-423 leaksFileNotFoundErrorwhen concurrent callers both passPath.exists()before either callsunlink(). Minimal repro: save one checkpoint, synchronize two delete calls atPath.exists()with a thread barrier, then await both calls and assert the results are oneTrueand oneFalse.python/packages/core/tests/workflow/test_checkpoint.py::test_file_checkpoint_storage_concurrent_deletesource checkout d7823b21.18.0