Skip to content

fix(acp): end a turn stopped at a permission prompt when its session closes - #178

Open
setoelkahfi wants to merge 1 commit into
developmentfrom
fix/close-session-during-turn
Open

setoelkahfi wants to merge 1 commit into
developmentfrom
fix/close-session-during-turn

Conversation

@setoelkahfi

Copy link
Copy Markdown
Collaborator

Follow-up to #176 and #172, which both shipped in 1.6.3.

What was wrong

session/close signals 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/cancel had the same problem with a client that does not send the cancelled outcome.

I found this while checking a different worry: that a turn could take its workspace back after session/close had 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

  • The permission wait ends on the cancellation as well as on the client's answer. The flag is read again once the turn has the workspace back, so an approval that crossed a cancel or a close 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 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.
  • 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 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.
  • Changelog entries under a new 1.6.4 heading. 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 warnings and cargo test --locked pass locally on macOS.

Not in this PR

install_session only 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 what session/load does for a deleted directory, so it should get its own issue.

…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>

@sigit-code-review sigit-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread src/main.rs

@sigit-code-review sigit-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. Ship it.

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