Skip to content

fix: allow concurrent workspace exports - #159

Merged
delete-cloud merged 1 commit into
mainfrom
fix/concurrent-workspace-exports
May 14, 2026
Merged

delete-cloud merged 1 commit into
mainfrom
fix/concurrent-workspace-exports

Conversation

@delete-cloud

@delete-cloud delete-cloud commented May 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • allow idle workspace export operations such as result archive/diff/patch to run concurrently
  • keep exports blocked while a turn is running, and block new turns while an export is active
  • add regression coverage for concurrent idle exports and export-vs-turn exclusion

Dogfood context

Concurrent remote result/diff/patch calls exposed a 409 turn already in progress on patch. The patch endpoint was contending with diff/archive through the session turn lock even though both are read-only workspace export operations.

Validation

  • uv run pytest tests/ui/test_session_manager_runtime.py -k "run_agent or export_workspace_archive or active_workspace_export" -q
  • uv run pytest tests/ui/test_session_manager_public_api.py -k "export_workspace_archive or capture_checkpoint or restore_checkpoint" -q
  • uv run pytest tests/ui/test_http_server.py -k "workspace_diff or workspace_patch or session_result or prompt_sets_turn_in_progress or turn_in_progress" -q
  • uv run pytest tests/ui/test_http_server_workspace_transfer.py -k "workspace_archive" -q
  • uv run basedpyright --level error src/coding_agent/ui/session_manager.py tests/ui/test_session_manager_public_api.py tests/ui/test_session_manager_runtime.py
  • uv run ruff format --check src/coding_agent/ui/session_manager.py tests/ui/test_session_manager_public_api.py tests/ui/test_session_manager_runtime.py
  • git diff --check

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved workspace export reliability by preventing concurrent session turns during active exports
    • Enhanced session state validation and guaranteed resource cleanup even when export operations fail
    • Added protection against conflicting session operations when workspace export is in progress

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 14, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

SessionManager now enforces mutual exclusion between workspace export and active turns via a per-session reference-counted export-in-progress flag. New guards in prepare_session_turn and run_agent reject turn starts when export is active, while export_workspace_archive manages the counter safely within a try/finally block. Tests verify the concurrency behavior under both idle and active-export conditions.

Changes

Export Concurrency Control

Layer / File(s) Summary
Export counter and turn guards
coding-agent/src/coding_agent/ui/session_manager.py
SessionManager.__init__ initializes per-session export counter; new helper methods increment/decrement and check export activity; prepare_session_turn and run_agent both raise "turn already in progress" when workspace export is active.
Export operation with counter management
coding-agent/src/coding_agent/ui/session_manager.py
export_workspace_archive asserts ownership/readiness, requires cloud execution_binding, increments counter before spawning export thread, and guarantees decrement in finally block.
Concurrency test coverage
coding-agent/tests/ui/test_session_manager_public_api.py, coding-agent/tests/ui/test_session_manager_runtime.py
Public API tests verify two exports can run concurrently during idle sessions; runtime tests confirm prepare_session_turn and run_agent reject concurrent work via RuntimeError when export is active; all tests use threading.Event to serialize blocking behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through concurrency maze,
Counting exports with a gentle gaze,
While turns must wait when export's in flight,
The finally block keeps cleanup bright! ✨
No race conditions here tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main objective: enabling concurrent workspace exports, which is the core change across the codebase modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/concurrent-workspace-exports

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 `@coding-agent/src/coding_agent/ui/session_manager.py`:
- Line 508: The _session_workspace_export_counts dict is not cleaned when
sessions are removed, causing memory leaks; update all session-removal paths to
remove the session's entry (use pop(session_id, None) or conditional del) for
_session_workspace_export_counts. Specifically, in _remove_session_async_no_lock
add cleanup alongside the existing _session_turn_locks cleanup (referencing
_session_turn_locks and _remove_session_async_no_lock), and likewise remove the
session key in remove_session and clear_sessions after their existing cleanup
logic (referencing remove_session and clear_sessions) so the export-count entry
is always cleared when a session is deleted.
🪄 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 Plus

Run ID: 3be62c4b-e069-4209-ab52-fcbaa0670442

📥 Commits

Reviewing files that changed from the base of the PR and between 3916976 and 47797d1.

📒 Files selected for processing (3)
  • coding-agent/src/coding_agent/ui/session_manager.py
  • coding-agent/tests/ui/test_session_manager_public_api.py
  • coding-agent/tests/ui/test_session_manager_runtime.py

self._lock = asyncio.Lock()
self._store_io_guard = asyncio.Lock()
self._session_turn_locks: dict[str, asyncio.Lock] = {}
self._session_workspace_export_counts: dict[str, int] = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clean up export count dict when sessions are removed.

The _session_workspace_export_counts dictionary is never cleaned up when sessions are removed via _remove_session_async_no_lock (line 994 cleans _session_turn_locks but not this dict), remove_session (line 1344), or clear_sessions (line 1360). This will leak memory if sessions are repeatedly created and destroyed.

🧹 Proposed fix to add cleanup in all session removal paths

Add cleanup in _remove_session_async_no_lock after line 994:

         await self._release_owner_lease_for_session(session_id)
         self._approval_stores.pop(session_id, None)
         self._session_turn_locks.pop(session_id, None)
+        self._session_workspace_export_counts.pop(session_id, None)

Add cleanup in remove_session after line 1344:

         self._approval_stores.pop(session_id, None)
         self._session_turn_locks.pop(session_id, None)
+        self._session_workspace_export_counts.pop(session_id, None)

Add cleanup in clear_sessions after line 1360:

         self._session_cache.clear()
         self._approval_stores.clear()
         self._session_turn_locks.clear()
+        self._session_workspace_export_counts.clear()
🤖 Prompt for 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.

In `@coding-agent/src/coding_agent/ui/session_manager.py` at line 508, The
_session_workspace_export_counts dict is not cleaned when sessions are removed,
causing memory leaks; update all session-removal paths to remove the session's
entry (use pop(session_id, None) or conditional del) for
_session_workspace_export_counts. Specifically, in _remove_session_async_no_lock
add cleanup alongside the existing _session_turn_locks cleanup (referencing
_session_turn_locks and _remove_session_async_no_lock), and likewise remove the
session key in remove_session and clear_sessions after their existing cleanup
logic (referencing remove_session and clear_sessions) so the export-count entry
is always cleared when a session is deleted.

@delete-cloud
delete-cloud merged commit 9b34856 into main May 14, 2026
1 check passed
@delete-cloud
delete-cloud deleted the fix/concurrent-workspace-exports branch May 14, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant