fix(code): reap langgraph dev server when startup is cancelled - #4629
Merged
Conversation
The langgraph dev server starts in a background worker so the TUI paints immediately. `ServerProcess.start()` spawns the subprocess before returning, but the app only records the `ServerProcess` on success. If the user quits (Ctrl+D) before the health check completes, the worker is cancelled mid-`start()`. `start_server_and_get_agent` guarded its cleanup with `except Exception`, which does not catch `asyncio.CancelledError` (a `BaseException`), so `server.stop()` was skipped and the `langgraph dev` subprocess was orphaned. Catch `BaseException` so a cancelled startup also stops the half-started server, then re-raise to preserve cancellation/shutdown semantics.
Will Bradley (wbbradley)
force-pushed
the
wbbradley/process-leak
branch
from
July 10, 2026 06:49
bbb3264 to
54e608b
Compare
Alexander Olsen (aolsenjazz)
requested review from
Alexander Olsen (aolsenjazz) and
Mason Daugherty (mdrxy)
July 10, 2026 13:41
Contributor
|
LGTM, Mason Daugherty (@mdrxy) would you confirm that widening this exception type is the most-correct solution? |
Mason Daugherty (mdrxy)
approved these changes
Jul 10, 2026
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Jul 13, 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`. The published GitHub release body is extracted from the merged `CHANGELOG.md` by `release.yml`, not from this PR description._ --- ## [0.1.37](deepagents-code==0.1.36...deepagents-code==0.1.37) (2026-07-13) ### Features * Add Meta model provider ([#4650](#4650)) ([70829c5](70829c5)) * Set `prompt_cache_key` for OpenAI models ([#4632](#4632)) ([8cf57ac](8cf57ac)) * Support Fireworks `/routers` model ids ([#4591](#4591)) ([1c08d27](1c08d27)) * `/model` Ctrl+N toggle for names vs raw specs ([#4592](#4592)) ([518c322](518c322)) * `/tools` slash command ([#4649](#4649)) ([b1600a8](b1600a8)) * Add `-s` alias for `--skill` ([#4620](#4620)) ([c9b7ac2](c9b7ac2)) * Configurable chat cursor style ([#4687](#4687)) ([a22484b](a22484b)) * Expand environment variables in MCP config ([#4681](#4681)) ([4f5d7be](4f5d7be)) * Hide diff widget for credential files ([#4593](#4593)) ([4c49a24](4c49a24)) * In-app Debug Console ([#4564](#4564)) ([4f94a30](4f94a30)) * Offer abort in `/threads` cwd-switch prompt ([#4583](#4583)) ([aaeac99](aaeac99)) * Resume threads in-TUI with `/threads -r [ID]` ([#4609](#4609)) ([d442673](d442673)) * Show `(debug enabled)` on splash when `DEEPAGENTS_CODE_DEBUG` is set ([#4584](#4584)) ([f10b877](f10b877)) ### Bug Fixes * Avoid repeated startup auto-update stalls ([#4648](#4648)) ([12a9c9d](12a9c9d)) * Infer Fireworks provider from qualified model IDs ([#4594](#4594)) ([4d2aa8a](4d2aa8a)) * Capture input typed before TUI startup ([#4684](#4684)) ([ef9a4a8](ef9a4a8)) * Detach owned `langgraph dev` server from terminal ([#4642](#4642)) ([d1f3afe](d1f3afe)) * Infer additional model providers ([#4675](#4675)) ([4ceed24](4ceed24)) * Preserve `Ctrl+D` deletion in non-empty input ([#4626](#4626)) ([306bd89](306bd89)) * Quit with `Ctrl+D` at end of prompt ([#4678](#4678)) ([2f8c111](2f8c111)) * Reap langgraph dev server when startup is cancelled ([#4629](#4629)) ([904ff05](904ff05)) * Reject `--auto-approve` in headless mode ([#4617](#4617)) ([997be16](997be16)) * Route explicit `--stdin` + `--skill` to headless path ([#4611](#4611)) ([724e24a](724e24a)) * Skip Esc prompt-restore once output generation begins ([#4582](#4582)) ([14f384f](14f384f)) * Remove misleading agent names from help ([#4671](#4671)) ([ac15732](ac15732)) * Support plain `exit` quit command ([#4543](#4543)) ([e6f10a1](e6f10a1)) * Sync `ask_user` active-question highlight with focus ([#4599](#4599)) ([e4c29b5](e4c29b5)) * Wrap MCP viewer navigation ([#4677](#4677)) ([cffc732](cffc732)) ### Performance Improvements * Load MCP servers concurrently during graph build ([#4659](#4659)) ([c5345cc](c5345cc)) --- _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: Mason Daugherty <github@mdrxy.com>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
…hain-ai#4629) ## Problem Pressing `Ctrl+D` shortly after launching `dcode` returns you to the terminal but leaves an orphaned `python -m langgraph_cli dev …` process running: ``` $ ps aux | grep deepagent wbbradley 69643 ... python3 -m langgraph_cli dev --host 127.0.0.1 --port 59934 --no-browser --no-reload --config .../langgraph.json ``` This is not intentional — it's a process leak. ## Root cause To make the splash screen appear instantly, the LangGraph server is started in a background Textual worker (`_start_server_background`) rather than blocking launch. That worker calls `start_server_and_get_agent`, which spawns the `langgraph dev` subprocess early (inside `ServerProcess.start()`) and then spends a few seconds polling `/ok` for health + graph readiness. If the user quits during that window: 1. `super().exit()` tears down Textual, which **cancels all workers**, injecting `asyncio.CancelledError` into the in-flight `server.start()`. 2. `start_server_and_get_agent` guarded cleanup with `except Exception:` — but `CancelledError` is a `BaseException`, **not** an `Exception`, so `server.stop()` was skipped and the exception re-raised. 3. Because the function never returned, `DeepAgentsApp._server_proc` was never assigned, so the belt-and-suspenders `finally` in `run_textual_app` also saw `None` and skipped cleanup. Net result: the `langgraph dev` subprocess is orphaned. When the server has fully finished starting before the quit, `stop()` runs correctly and there is no leak — the leak is specific to quitting *during* startup. ## Fix Catch `BaseException` (which includes `asyncio.CancelledError`) around `server.start()` / `wait_for_graph_ready()` so a cancelled startup still stops the half-started subprocess, then re-raise to preserve cancellation/shutdown semantics. This matches the existing cleanup-on-cancel-and-reraise pattern already used in `skills/trust.py`. --------- Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
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`. The published GitHub release body is extracted from the merged `CHANGELOG.md` by `release.yml`, not from this PR description._ --- ## [0.1.37](langchain-ai/deepagents@deepagents-code==0.1.36...deepagents-code==0.1.37) (2026-07-13) ### Features * Add Meta model provider ([langchain-ai#4650](langchain-ai#4650)) ([70829c5](langchain-ai@70829c5)) * Set `prompt_cache_key` for OpenAI models ([langchain-ai#4632](langchain-ai#4632)) ([8cf57ac](langchain-ai@8cf57ac)) * Support Fireworks `/routers` model ids ([langchain-ai#4591](langchain-ai#4591)) ([1c08d27](langchain-ai@1c08d27)) * `/model` Ctrl+N toggle for names vs raw specs ([langchain-ai#4592](langchain-ai#4592)) ([518c322](langchain-ai@518c322)) * `/tools` slash command ([langchain-ai#4649](langchain-ai#4649)) ([b1600a8](langchain-ai@b1600a8)) * Add `-s` alias for `--skill` ([langchain-ai#4620](langchain-ai#4620)) ([c9b7ac2](langchain-ai@c9b7ac2)) * Configurable chat cursor style ([langchain-ai#4687](langchain-ai#4687)) ([a22484b](langchain-ai@a22484b)) * Expand environment variables in MCP config ([langchain-ai#4681](langchain-ai#4681)) ([4f5d7be](langchain-ai@4f5d7be)) * Hide diff widget for credential files ([langchain-ai#4593](langchain-ai#4593)) ([4c49a24](langchain-ai@4c49a24)) * In-app Debug Console ([langchain-ai#4564](langchain-ai#4564)) ([4f94a30](langchain-ai@4f94a30)) * Offer abort in `/threads` cwd-switch prompt ([langchain-ai#4583](langchain-ai#4583)) ([aaeac99](langchain-ai@aaeac99)) * Resume threads in-TUI with `/threads -r [ID]` ([langchain-ai#4609](langchain-ai#4609)) ([d442673](langchain-ai@d442673)) * Show `(debug enabled)` on splash when `DEEPAGENTS_CODE_DEBUG` is set ([langchain-ai#4584](langchain-ai#4584)) ([f10b877](langchain-ai@f10b877)) ### Bug Fixes * Avoid repeated startup auto-update stalls ([langchain-ai#4648](langchain-ai#4648)) ([12a9c9d](langchain-ai@12a9c9d)) * Infer Fireworks provider from qualified model IDs ([langchain-ai#4594](langchain-ai#4594)) ([4d2aa8a](langchain-ai@4d2aa8a)) * Capture input typed before TUI startup ([langchain-ai#4684](langchain-ai#4684)) ([ef9a4a8](langchain-ai@ef9a4a8)) * Detach owned `langgraph dev` server from terminal ([langchain-ai#4642](langchain-ai#4642)) ([d1f3afe](langchain-ai@d1f3afe)) * Infer additional model providers ([langchain-ai#4675](langchain-ai#4675)) ([4ceed24](langchain-ai@4ceed24)) * Preserve `Ctrl+D` deletion in non-empty input ([langchain-ai#4626](langchain-ai#4626)) ([306bd89](langchain-ai@306bd89)) * Quit with `Ctrl+D` at end of prompt ([langchain-ai#4678](langchain-ai#4678)) ([2f8c111](langchain-ai@2f8c111)) * Reap langgraph dev server when startup is cancelled ([langchain-ai#4629](langchain-ai#4629)) ([904ff05](langchain-ai@904ff05)) * Reject `--auto-approve` in headless mode ([langchain-ai#4617](langchain-ai#4617)) ([997be16](langchain-ai@997be16)) * Route explicit `--stdin` + `--skill` to headless path ([langchain-ai#4611](langchain-ai#4611)) ([724e24a](langchain-ai@724e24a)) * Skip Esc prompt-restore once output generation begins ([langchain-ai#4582](langchain-ai#4582)) ([14f384f](langchain-ai@14f384f)) * Remove misleading agent names from help ([langchain-ai#4671](langchain-ai#4671)) ([ac15732](langchain-ai@ac15732)) * Support plain `exit` quit command ([langchain-ai#4543](langchain-ai#4543)) ([e6f10a1](langchain-ai@e6f10a1)) * Sync `ask_user` active-question highlight with focus ([langchain-ai#4599](langchain-ai#4599)) ([e4c29b5](langchain-ai@e4c29b5)) * Wrap MCP viewer navigation ([langchain-ai#4677](langchain-ai#4677)) ([cffc732](langchain-ai@cffc732)) ### Performance Improvements * Load MCP servers concurrently during graph build ([langchain-ai#4659](langchain-ai#4659)) ([c5345cc](langchain-ai@c5345cc)) --- _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: Mason Daugherty <github@mdrxy.com>
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.
Problem
Pressing
Ctrl+Dshortly after launchingdcodereturns you to the terminal but leaves an orphanedpython -m langgraph_cli dev …process running:This is not intentional — it's a process leak.
Root cause
To make the splash screen appear instantly, the LangGraph server is started in a background Textual worker (
_start_server_background) rather than blocking launch. That worker callsstart_server_and_get_agent, which spawns thelanggraph devsubprocess early (insideServerProcess.start()) and then spends a few seconds polling/okfor health + graph readiness.If the user quits during that window:
super().exit()tears down Textual, which cancels all workers, injectingasyncio.CancelledErrorinto the in-flightserver.start().start_server_and_get_agentguarded cleanup withexcept Exception:— butCancelledErroris aBaseException, not anException, soserver.stop()was skipped and the exception re-raised.DeepAgentsApp._server_procwas never assigned, so the belt-and-suspendersfinallyinrun_textual_appalso sawNoneand skipped cleanup.Net result: the
langgraph devsubprocess is orphaned. When the server has fully finished starting before the quit,stop()runs correctly and there is no leak — the leak is specific to quitting during startup.Fix
Catch
BaseException(which includesasyncio.CancelledError) aroundserver.start()/wait_for_graph_ready()so a cancelled startup still stops the half-started subprocess, then re-raise to preserve cancellation/shutdown semantics. This matches the existing cleanup-on-cancel-and-reraise pattern already used inskills/trust.py.