fix(sessions): close four prompt-lifecycle holes - #851
Merged
Conversation
This was referenced Sep 10, 2026
Closed
POST /raw-key exempted escape from all prompt arbitration, so a card's Cancel was a blind \x1b. When the gate had already closed, that Escape landed at Claude's prompt and interrupted the turn the user was waiting on. Escape now gets the same binding as every other action whenever the caller names a promptId: a stale or unfocused prompt is refused with 409 raw_key_stale and nothing is written. A bound Escape retires its prompt as cancelled and clears its pending entry, so dismissing a card no longer leaves the composer refused. Escape without a promptId stays exactly as blind as before, for the raw keyboard and the interrupt action.
POST /input refuses text while a permission entry is pending, and two paths left an entry behind with nothing to retire it. The contract answer route returned prompt_cancelled from a failed freshness scrape without clearing the entry, unlike the legacy route's gateClosed(), so /input read the retained cancelled record as an open prompt for up to ten minutes. A gate whose OSC fired before its options painted was stored with no promptId, and isLive() treated that entry as live forever. Both now clear the entry when no gate is painted at all and keep refusing while one is, so text never lands on a gate the detector has not announced yet. Closes #849
After a question was answered, the menu that stayed painted, or a JSONL flush landing after the answer, minted a new open prompt for a question already settled. The contract answer and raw-key Enter paths deleted the question's content key along with its pending entry, so the next scrape of the still-painted menu no longer deduped; the legacy route keeps the key and never had this. Both paths now keep the key until the menu leaves the screen, and a new host occurrence with identical content still opens, matching the permission path's identity rule. processJsonlQuestions minted from any AskUserQuestion tool_use without checking whether it had been answered, and its 60-second timer is the lifetime the live probe measured; a flush matching an answered question's key is now skipped. Session exit clears the kept key so the same menu shows again on resume. Closes #724
permissionGateStillOpen checked only the Claude box scraper, but pty-manager also raises cards from detectShellPrompt (`read -p "[y/N]"`, "press Enter"), which is never a Claude box. On the Claude provider all three answer routes therefore refused every answer to such a card as closed, so a shell prompt's Continue could never write its \r. The check now accepts either detector's gate whose content key matches the pending one. The painted check behind the stale-entry cleanup had the same blindness, so it could clear an entry while a [y/N] prompt was still up and let composer text answer it; it now counts shell prompts too. Closes #850
RonenMars
force-pushed
the
fix/prompt-lifecycle-guards
branch
from
September 10, 2026 15:47
98c5340 to
b9d2dc6
Compare
Owner
Author
|
🎉 This PR is included in version 1.89.5 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four holes in how the streamer opens, answers and retires prompts, one commit each.
Escape binding — #848
POST /raw-keyexemptedescapefrom all prompt arbitration, so a card's Cancel was a blind\x1bthat could interrupt the running turn after the card's prompt had closed.An escape that names a
promptIdnow gets the same arbitration as every other action: a stale or unfocused prompt is refused with409 raw_key_staleand nothing is written.A bound escape retires its prompt as
cancelledand clears its pending entry, and an escape without apromptIdstays blind.Stale permission entry — #849
Two paths left a permission entry behind that kept
POST /inputrefusing composer text after the client had taken the card down.The contract answer route skipped the cleanup the legacy route's
gateClosed()does after a failed freshness scrape, and a gate whose OSC fired before its options painted was stored with nopromptId, whichisLive()reads as live forever.Both now clear the entry when no gate is painted and keep refusing while one is, so composer text never lands on a gate the detector has not announced yet and #703 stays intact.
Answered question re-opening — #724
The contract answer and raw-key Enter paths deleted a question's dedupe key along with its pending entry, so the still-painted menu re-minted as a fresh open prompt.
processJsonlQuestionsalso minted from anyAskUserQuestiontool_usewithout checking whether it had been answered, and its 60-second timer is the phantom's measured lifetime.Both paths now keep the key until the menu leaves the screen, a late JSONL flush of an answered question is skipped, and exit clears the key so the menu shows again on resume.
Shell-prompt gates — #850
permissionGateStillOpenread only the Claude box scraper, so all three answer routes refused every card raised bydetectShellPrompton the Claude provider.That refusal is what left the #849 entry behind in the RonenMars/threadbase-mobile#954 lockout.
The check now accepts either detector's gate whose content key matches, and the painted check behind #849 counts shell prompts too.
Verification
Client side
Closes #848
Closes #849
Closes #724
Closes #850