Skip to content

fix(code): keep /update and /install --package prompts responsive - #5127

Merged
Mason Daugherty (mdrxy) merged 7 commits into
mainfrom
mdrxy/code/pump-blocking-confirm-modals
Jul 30, 2026
Merged

fix(code): keep /update and /install --package prompts responsive#5127
Mason Daugherty (mdrxy) merged 7 commits into
mainfrom
mdrxy/code/pump-blocking-confirm-modals

Conversation

@mdrxy

@mdrxy Mason Daugherty (mdrxy) commented Jul 28, 2026

Copy link
Copy Markdown
Member

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

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>
@github-actions github-actions Bot added dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: L 500-999 LOC labels Jul 28, 2026
@mdrxy
Mason Daugherty (mdrxy) marked this pull request as ready for review July 29, 2026 19:10
Mason Daugherty (mdrxy) and others added 5 commits July 29, 2026 15: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.
@github-actions github-actions Bot added size: XL 1000+ LOC and removed size: L 500-999 LOC labels Jul 30, 2026
@mdrxy
Mason Daugherty (mdrxy) merged commit 5726872 into main Jul 30, 2026
69 checks passed
@mdrxy
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant