fix(core): use unique probe filename in check_credentials_directory_permissions to avoid concurrent race#805
Conversation
…redentials dir When two or more MCP server processes share a credentials directory (WORKSPACE_MCP_CREDENTIALS_DIR), they collide on the literal ".permission_test" filename used by check_credentials_directory_permissions(). The interleaving: P1 open(.permission_test, "w") -> ok P1 write/os.remove -> ok P2 open(.permission_test, "w") -> ok P1's os.remove already gone -> P2 sees ENOENT on write/remove P2 (PermissionError, OSError) -> re-raised as PermissionError P2 subprocess exits -> MCP client raises "Connection closed" Replace the fixed-name probe with tempfile.mkstemp(prefix=".permission_test_"), which gives each call a unique random suffix. Extracted into a private helper _probe_credentials_directory_writable() to deduplicate the two probe sites (existing-dir vs newly-created-dir branches). mkstemp is preferred over NamedTemporaryFile because the latter holds the file open with O_TEMPORARY on Windows (Python <=3.11), which can fail to delete cleanly when antivirus or indexing services hold a transient handle. Adds tests/test_credentials_dir_permissions.py with happy-path, missing-dir, no-leak, read-only-dir, and a multiprocessing regression test (8 workers x 50 iterations) that fails on pre-fix HEAD and passes after the change.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughReplaced fixed-path credential-directory write tests with a tempfile-based probe ( ChangesCredentials Directory Writability Probe
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_credentials_dir_permissions.py`:
- Around line 58-96: The test docstring for
test_concurrent_callers_do_not_race_on_probe_filename incorrectly states the fix
uses tempfile.NamedTemporaryFile; update the docstring to reflect the actual
implementation in core/utils.py which uses tempfile.mkstemp in
_probe_credentials_directory_writable (i.e., mention mkstemp or “creating a
unique probe file via mkstemp” instead of NamedTemporaryFile) so the docstring
matches the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dfe48d86-23cb-4cc2-a288-dd5788b0f325
📒 Files selected for processing (2)
core/utils.pytests/test_credentials_dir_permissions.py
CodeRabbit caught the docstring lying about the implementation.
|
@taylorwilsdon — friendly nudge on this one when you get a chance. All bot checks are green and CodeRabbit's one nit (docstring/impl mismatch) was addressed in 40db1fe. Happy to rebase if anything has drifted on main. Thanks! |
Description
core/utils.check_credentials_directory_permissions()writes a probe file at the fixed path{credentials_dir}/.permission_testto test write+remove permission. When two or more processes share a credentials directory (WORKSPACE_MCP_CREDENTIALS_DIR), they collide on that literal filename and one observes ENOENT during the write/remove sequence. The OSError is caught and re-raised asPermissionError("Cannot write to existing credentials directory ..."), the subprocess exits, and the MCP client seesConnection closed.The interleaving:
Empirically, in a deployment where six launchd services spawn
workspace-mcpsubprocesses against the sameWORKSPACE_MCP_CREDENTIALS_DIR, this produced ~109[ERROR]log entries over an 8-day window, correlating ~1:1 with downstream tool-call failures.Fix
Replace the fixed-name probe with
tempfile.mkstemp(prefix=".permission_test_", dir=credentials_dir), which gives each call a unique random suffix — concurrent callers never collide. Extracted into a private helper_probe_credentials_directory_writable()to deduplicate the two probe sites (existing-dir branch and newly-created-dir branch).mkstempis preferred overtempfile.NamedTemporaryFilebecause the latter holds the file open withO_TEMPORARYon Windows for Python ≤3.11, which can fail to delete cleanly when antivirus or indexing services hold a transient handle on the file. Sincerequires-python = ">=3.10"is the declared floor,NamedTemporaryFile(delete=True)would be a regression risk on Windows.Existing
PermissionError/OSErrorexception wrapping and messages are preserved.Reproducer
A multiprocessing regression test is included (
tests/test_credentials_dir_permissions.py::test_concurrent_callers_do_not_race_on_probe_filename): 8 worker processes × 50 iterations each, all callingcheck_credentials_directory_permissions()against the same directory. Fails onmainat0d1475c, passes after this patch.Type of Change
Testing
pytest tests/test_credentials_dir_permissions.py tests/test_main_permissions_tier.py— 13 passed)main, passes after patch;ruff checkclean)Checklist
Additional Notes
@pytest.mark.asyncio, which is correct given they exercise blocking I/O — flagging here pre-emptively given the repo'sasyncio_mode = "strict"config.tests/test_main_permissions_tier.pythat monkeypatchcheck_credentials_directory_permissionscontinue to pass — the function's public signature and exception types are unchanged.Summary by CodeRabbit
Bug Fixes
Tests