Repository navigation
fix(acp): end a turn stopped at a permission prompt when its session closes - #178
setoelkahfi wants to merge 1 commit into
Conversation
…closes session/close signals the session's running turn and then waits for that turn's session lock. A turn waiting on a permission answer never looked at the signal, so a client that closed the thread without answering the request left the close waiting for good, and every later request for that thread behind it. session/cancel had the same problem with a client that does not send the cancelled outcome. The permission wait now ends on the cancellation too. The flag is read again once the turn has the workspace back, so an approval that crossed the cancellation on the wire runs nothing. resume_workspace cancels the turn when its session cannot be reinstalled. It used to log a warning and let the turn go on in whichever directory was current. Nothing makes the reinstall fail today, because close removes the session only after the turn has let go of the session lock, but the turn no longer depends on that. A SessionGuard replaces the lock and guard pair in the seven handlers that take a session's lock. On drop it removes the session's entry from session_locks when the session is gone and no other request is queued on the lock. The map used to keep an entry for every closed thread and for every id a request named without a session behind it. Tests: close while the turn is mid-stream (this passed before the change and is there to hold the lock ordering), close at a permission prompt (timed out before), the lock entry's lifetime with a request queued behind the close, and a turn whose session is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
This change addresses a deadlock where a session/close or session/cancel would hang indefinitely if a turn was waiting at a permission prompt and the client never answered. The fix ensures that the permission wait ends on cancellation as well as on a client answer, and that an approval arriving after cancellation does not run the tool. The session lock management is refactored: SessionGuard now cleans up session_locks entries when sessions are closed and no requests are queued, preventing resource leaks. resume_workspace now cancels the turn if the session cannot be reinstalled, preventing a turn from running in the wrong directory. Several new tests are added to cover these behaviors, including closing a session mid-stream, at a permission prompt, and ensuring session lock entries are cleaned up. All previous findings appear addressed, and the new logic is exercised by the tests.
Automated review by siGit Code Review · commit f2f22d4 · see the review dashboard
Follow-up to #176 and #172, which both shipped in 1.6.3.
What was wrong
session/closesignals the session's running turn, then waits for that turn's session lock. A turn waiting on a permission answer never looked at the signal. If the client closed the thread without answering the request, the close waited for good, and so did every later request for that thread.session/cancelhad the same problem with a client that does not send thecancelledoutcome.I found this while checking a different worry: that a turn could take its workspace back after
session/closehad removed its session, and then run a tool in the wrong directory. That one does not happen. The turn holds its session lock for its whole run and close removes the session only after getting that lock. The first new integration test closes a thread mid-stream and passed before any change here.Changes
resume_workspacecancels the turn when its session cannot be reinstalled. It used to log a warning and let the turn continue in whichever directory was current. Nothing makes this fail today. The turn just no longer depends on the lock ordering to stay out of another session's directory.SessionGuardreplaces the lock and guard pair in the seven handlers that take a session's lock. On drop it removes the session's entry fromsession_lockswhen the session is gone and nothing else is queued on the lock. Before, the map kept an entry for every closed thread, and for every id a request named with no session behind it.1.6.4heading. Rename it if the next release gets a different number.Tests
closing_a_session_mid_stream_ends_its_turn_and_runs_no_tool: passes with and without the fix, kept to hold the lock ordering.closing_a_session_at_a_permission_prompt_ends_its_turn_without_an_answer: timed out at 30 s before the fix.a_session_lock_entry_goes_once_its_session_and_its_waiters_are_gone: the entry stays while a request is queued behind the close, and goes after it.a_turn_whose_session_cannot_be_reinstalled_is_cancelled.cargo fmt -- --check,cargo clippy --tests -- -D warningsandcargo test --lockedpass locally on macOS.Not in this PR
install_sessiononly warns when the session's directory no longer exists, for example a worktree deleted during a turn. The process then stays in the previous session's directory and tools resolve relative paths there. Failing instead would change whatsession/loaddoes for a deleted directory, so it should get its own issue.