fix(code): keep /update and /install --package prompts responsive - #5127
Merged
Mason Daugherty (mdrxy) merged 7 commits intoJul 30, 2026
Merged
Conversation
Slash commands are dispatched from `on_chat_input_submitted`, which is awaited inline on the App message pump. `/update` and `/install --package` awaited their confirmation modals in that chain, so the pump stayed blocked while the modal was open, the modal never received the Enter/Esc key events it needs to resolve, and the UI looked frozen until the 600s watchdog fired. Hand those confirmations to a new `_schedule_off_message_pump` so the command handler returns first, mirroring `_schedule_restart_offer`. Continuations are keyed so a second copy of the same command is refused while its prompt is open, and app teardown cancels any still parked on a modal. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
marked this pull request as ready for review
July 29, 2026 19:10
`/install <pkg> --package` and `/update` run their confirmation modals as detached tasks so the Textual message pump stays free. Nothing in the command handler's call chain wraps that work, so a failure reached only `_log_task_exception` — a `logger.warning` the interactive user never sees — leaving a mounted "Installing package..." line as the last thing shown while the install was already dead. `_confirm_then_install_package` now catches its own exceptions and mounts an `ErrorMessage` via a new `_mount_install_failure`, matching the two `/update` continuations. `_perform_package_install_unlocked` regains an `ImportError` guard around its lazy imports: `/install --package` rewrites dcode's own package tree, so a second install in one session can import a half-written module, and an escape there killed the app on the `--force` path. App teardown requested cancellation of these continuations without awaiting it. A continuation past its confirmation may be mid-`uv tool install`, where cancellation kills the process group and can leave the tool environment partially updated, so `exit()` now logs which continuation it abandons and awaits the unwind in a bounded teardown phase alongside server shutdown. The queue drain scheduled when a continuation finishes gains a done-callback, since a raise in it would otherwise silently stop the queue advancing. Failure logs name the continuation and the `/update` stage rather than repeating one string three times, and `_perform_app_upgrade`'s parameter docs no longer credit `upgrade_include_prereleases` for a decision `include_prereleases` makes. Tests share `drain_modal_commands` and `wait_for_modal` fixtures that re-raise and assert on timeout instead of swallowing. The three pump-responsiveness regression tests shorten the modal watchdog so a regression fails on an assertion rather than a bare 30s timeout, and new coverage lands on the install and update failure paths, `exit()`'s cancellation wiring, and `_perform_app_upgrade`'s upgrade-failure and debug-skip branches.
Mason Daugherty (mdrxy)
deleted the
mdrxy/code/pump-blocking-confirm-modals
branch
July 30, 2026 15:32
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Jul 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50) (2026-07-30) ### Highlights - Added project hooks workspace trust and expanded Hooks v2 support with client and server lifecycle events plus runtime feedback ([#5105](#5105), [#5104](#5104), [#4997](#4997), [#5045](#5045)). - Added an option to mute the “YOLO is active” toast ([#5103](#5103)). - Made the splash screen `thread` ID clickable to copy it ([#5173](#5173)). - Show `ask_user` answers directly on the answered tool row ([#5100](#5100)). - Show a toast when submitting an empty required `ask_user` answer ([#5095](#5095)). - Added thread message counts to the Debug Console ([#5117](#5117)). ### Fixes and improvements - Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook resume stability across identity and Command tool results ([#5146](#5146), [#5176](#5176)). - Kept server hook state out of task results ([#5164](#5164)). - Stopped duplicate Auto transcript events during interrupt replay ([#5157](#5157)). - Kept `/update` and `/install --package` prompts responsive ([#5127](#5127)). - Refreshed the `/threads` cache after each turn ([#5174](#5174)). - Anchored toasts above the chat input and added a toast when media is dropped into a free-text question ([#5101](#5101), [#5099](#5099)). - Improved thread status message styling and links ([#5118](#5118)). - Made resume hints echo the launched command name ([#5119](#5119)). - Scoped selection copy to the clicked screen ([#5140](#5140)). - Ignored mouse hits on detached widgets ([#5114](#5114)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Jul 30, 2026
Related #5086, #5127 `/goal <objective>` and `/goal amend <feedback>` no longer freeze the terminal the first time Auto mode asks how to handle generated goal criteria — Enter and Esc now resolve that prompt. --- Slash commands are dispatched from the App's `on_chat_input_submitted` handler, which is awaited inline on the Textual message pump. The `/goal` create and amend branches awaited the one-time "How should Auto mode handle goal criteria?" modal in that chain, so the pump stayed blocked for as long as the modal was open. The modal therefore never received the Enter/Esc key events it needs to resolve, and because the modal watchdog is ten minutes, the app looked permanently frozen — not even the quit binding got through, so users had to kill the terminal. This is the same failure #5086 fixed for the post-install restart prompt and #5127 fixes for the `/update` and `/install --package` prompts. Only the first Auto-mode `/goal` in a fresh install is affected: once the preference is answered (or set in `config.toml` / `DEEPAGENTS_CODE_GOAL_AUTO_ACCEPT_CRITERIA`) no modal opens and the flow never blocks. Launching with `dcode --goal ...` was already fine, because the startup sequence runs off the pump. Notes for review: - The shared tail of both branches moves into `_start_goal_proposal`, which detaches the flow **only** when the preference prompt is actually owed; otherwise it stays inline, so ordinary `/goal` behavior and its queue interaction are unchanged. The proposal coroutine is now built by a callable so a preflight failure cannot strand it un-awaited. - Because the handler returns while the prompt is up, a second `/goal` (an external caller, or a queue drain) is refused with a toast instead of stacking a modal over an unanswered one, and app teardown cancels a flow still parked on the prompt. - Fail-closed behavior is unchanged: a timed-out or unmountable prompt still persists "review before applying" and records the one-time marker. - Tests submit through the real `ChatInput.Submitted` path and press a key; both regression tests wedge the app (pump blocked until the pytest timeout) against the previous code. If #5127 lands first, this detach could be folded into its `_schedule_off_message_pump` helper. Made by [Open SWE](https://openswe.vercel.app/agents/b0872246-9522-6d5b-8f18-aff84ee46dfb) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Jul 30, 2026
…#5127) Related #5086 Three confirmation prompts froze the terminal instead of taking an answer: the `/update` dependency-refresh prompt, the `/update --deps` app-update prompt, and the `/install <pkg> --package` prompt. Enter and Esc did nothing, the quit key did nothing, and the only way out was killing the terminal. They work now. ## Why it happened Textual delivers keypresses on a single loop, and slash commands are handled on that same loop. These three commands sat there waiting for the user to answer the prompt — so the loop was blocked waiting for a keypress that it was itself responsible for delivering. The prompt could never resolve. Its internal timeout is ten minutes, so the app looked permanently dead rather than slow. #5086 fixed this same deadlock for the post-install restart prompt. These three prompts were missed. ## The fix `_schedule_off_message_pump` runs the prompt, and whatever depends on the answer, as a separate task. The command handler returns right away, the loop is free, and keypresses reach the prompt. Two blocks of code moved so they could be reached from the new task: `_handle_update_command`'s upgrade tail became `_perform_app_upgrade`, and the `--package` install body became `_perform_package_install`. ## What that broke Blocking the loop was also doing two useful things by accident. Both are now done deliberately, and this is most of the diff. **It prevented two installs at once.** `/install` and `/update` both run `uv tool install --reinstall` against the same environment, and each rebuilds that environment from what is already recorded in it. Run two at once and both read the old state first, so whichever finishes last silently discards the other's package. Only one install or update can now be in progress at a time; a second command waits in the queue rather than starting alongside. **It kept failures visible.** Code in a separate task is outside the command handler's error handling, so a crash would only reach a log file the user never sees — leaving `Installing package '<pkg>'...` on screen as the last word, with nothing actually running. Each prompt now reports its own failures on screen. There is also a new case to handle: quitting while an install is running cancels it partway through `uv`, which can leave the environment half-updated. That now gets written to the log, and the install is given a moment to finish cleaning up before the app exits so it cannot leave a stray process behind. Package-name validation and the editable-install refusal still run *before* the hand-off, so the prompt remains the only gate on installing third-party code. ## Tests Each of the three prompts gets a test that submits the command the way the input widget does, then presses a key. Against the old code the keypress never arrives and the prompt never resolves, so each test fails. `libs/code/AGENTS.md` already warned against waiting on a modal inside a slash-command handler. It now names the helper to use instead, and what a caller owes it: report your own errors, expect to be cancelled partway, and take the lock before touching the environment. Made by [Open SWE](https://openswe.vercel.app/agents/7b6ba24a-0f53-fd92-09d2-f9e20aca229a) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Jul 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50) (2026-07-30) ### Highlights - Added project hooks workspace trust and expanded Hooks v2 support with client and server lifecycle events plus runtime feedback ([#5105](#5105), [#5104](#5104), [#4997](#4997), [#5045](#5045)). - Added an option to mute the “YOLO is active” toast ([#5103](#5103)). - Made the splash screen `thread` ID clickable to copy it ([#5173](#5173)). - Show `ask_user` answers directly on the answered tool row ([#5100](#5100)). - Show a toast when submitting an empty required `ask_user` answer ([#5095](#5095)). - Added thread message counts to the Debug Console ([#5117](#5117)). ### Fixes and improvements - Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook resume stability across identity and Command tool results ([#5146](#5146), [#5176](#5176)). - Kept server hook state out of task results ([#5164](#5164)). - Stopped duplicate Auto transcript events during interrupt replay ([#5157](#5157)). - Kept `/update` and `/install --package` prompts responsive ([#5127](#5127)). - Refreshed the `/threads` cache after each turn ([#5174](#5174)). - Anchored toasts above the chat input and added a toast when media is dropped into a free-text question ([#5101](#5101), [#5099](#5099)). - Improved thread status message styling and links ([#5118](#5118)). - Made resume hints echo the launched command name ([#5119](#5119)). - Scoped selection copy to the clicked screen ([#5140](#5140)). - Ignored mouse hits on detached widgets ([#5114](#5114)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
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.
Related #5086
Three confirmation prompts froze the terminal instead of taking an answer: the
/updatedependency-refresh prompt, the/update --depsapp-update prompt, and the/install <pkg> --packageprompt. Enter and Esc did nothing, the quit key did nothing, and the only way out was killing the terminal. They work now.Why it happened
Textual delivers keypresses on a single loop, and slash commands are handled on that same loop. These three commands sat there waiting for the user to answer the prompt — so the loop was blocked waiting for a keypress that it was itself responsible for delivering. The prompt could never resolve. Its internal timeout is ten minutes, so the app looked permanently dead rather than slow.
#5086 fixed this same deadlock for the post-install restart prompt. These three prompts were missed.
The fix
_schedule_off_message_pumpruns the prompt, and whatever depends on the answer, as a separate task. The command handler returns right away, the loop is free, and keypresses reach the prompt. Two blocks of code moved so they could be reached from the new task:_handle_update_command's upgrade tail became_perform_app_upgrade, and the--packageinstall body became_perform_package_install.What that broke
Blocking the loop was also doing two useful things by accident. Both are now done deliberately, and this is most of the diff.
It prevented two installs at once.
/installand/updateboth runuv tool install --reinstallagainst the same environment, and each rebuilds that environment from what is already recorded in it. Run two at once and both read the old state first, so whichever finishes last silently discards the other's package. Only one install or update can now be in progress at a time; a second command waits in the queue rather than starting alongside.It kept failures visible. Code in a separate task is outside the command handler's error handling, so a crash would only reach a log file the user never sees — leaving
Installing package '<pkg>'...on screen as the last word, with nothing actually running. Each prompt now reports its own failures on screen.There is also a new case to handle: quitting while an install is running cancels it partway through
uv, which can leave the environment half-updated. That now gets written to the log, and the install is given a moment to finish cleaning up before the app exits so it cannot leave a stray process behind.Package-name validation and the editable-install refusal still run before the hand-off, so the prompt remains the only gate on installing third-party code.
Tests
Each of the three prompts gets a test that submits the command the way the input widget does, then presses a key. Against the old code the keypress never arrives and the prompt never resolves, so each test fails.
libs/code/AGENTS.mdalready warned against waiting on a modal inside a slash-command handler. It now names the helper to use instead, and what a caller owes it: report your own errors, expect to be cancelled partway, and take the lock before touching the environment.Made by Open SWE