fix(cli): harden dictionary iteration and HITL fallback handling#1151
Conversation
Wrap dictionary .items() and .values() calls with list() to prevent 'dictionary changed size during iteration' errors when parallel tool calls modify the dictionaries concurrently. This fixes a race condition in textual_adapter.py where multiple concurrent tool calls (e.g., 8 parallel SQL queries) would cause the application to crash because dictionaries were being modified while being iterated over. The fix applies list() wrapper to 6 unprotected iterations: - _build_interrupted_ai_message: current_tool_messages.items() - execute_task_textual: pending_interrupts.items() - execute_task_textual: adapter._current_tool_messages.values() (4 locations) This follows the existing safe pattern already used elsewhere in the same file (lines 568, 705, 739). Fixes langchain-ai#956
Extract list() calls to local variables to stay within 88 char limit.
|
Saakshi Gupta (@saakshigupta2002) could you add a test that fails without these changes? |
sure! will do that soon |
…chain-ai#956) Add TestDictIterationSafety class with 6 tests that verify: - Direct dict iteration fails with RuntimeError when the dict is modified concurrently (simulated via MutatingDict subclasses) - list() snapshot protects against concurrent modification - _build_interrupted_ai_message handles dict mutation safely These tests would fail if the list() wrappers added in the prior commit were removed, proving the fix is both necessary and sufficient.
Just pushed a commit with regression tests. I added a The key idea: I created Two of the tests ( Let me know if you'd like any changes! :) |
🤖 I have created a release *beep* *boop* --- ## [0.0.20](deepagents-cli==0.0.19...deepagents-cli==0.0.20) (2026-02-10) ### Features * **cli:** `--quiet` flag to suppress non-agent output w/ `-n` ([#1201](#1201)) ([3e96792](3e96792)) * **cli:** add docs link to `/help` ([#1098](#1098)) ([8f8fc98](8f8fc98)) * **cli:** built-in skills, ship `skill-creator` as first ([#1191](#1191)) ([42823a8](42823a8)) * **cli:** enrich built-in skill metadata with license and compatibility info ([#1193](#1193)) ([b8179c2](b8179c2)) * **cli:** implement message queue for CLI ([#1197](#1197)) ([c4678d7](c4678d7)) * **cli:** model switcher & arbitrary chat model support ([#1127](#1127)) ([28fc311](28fc311)) * **cli:** non-interactive mode w/ shell allow-listing ([#909](#909)) ([433bd2c](433bd2c)) * **cli:** support custom working directories and LangSmith sandbox templates ([#1099](#1099)) ([21e7150](21e7150)) ### Bug Fixes * **cli:** `-m` initial prompt submission ([#1184](#1184)) ([a702e82](a702e82)) * **cli:** align skill-creator example scripts with agent skills spec ([#1177](#1177)) ([199d176](199d176)) * **cli:** harden dictionary iteration and HITL fallback handling ([#1151](#1151)) ([8b21fc6](8b21fc6)) * **cli:** per-subcommand help screens, short flags, and skills enhancements ([#1190](#1190)) ([3da1e8b](3da1e8b)) * **cli:** port skills behavior from SDK ([#1192](#1192)) ([ad9241d](ad9241d)), closes [#1189](#1189) * **cli:** rewrite skills create template to match spec guidance ([#1178](#1178)) ([f08ad52](f08ad52)) * **cli:** terminal virtualize scrolling to stop perf issues ([#965](#965)) ([5633c82](5633c82)) * **cli:** update splash thread ID on `/clear` ([#1204](#1204)) ([23651ed](23651ed)) * **deepagents:** refactor summarization middleware ([#1138](#1138)) ([e87001e](e87001e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…gchain-ai#1151) Fixes langchain-ai#956 Defensive hardening of dictionary iterations in `textual_adapter.py`, plus fixes for incomplete cleanup in HITL fallback branches. **Dictionary iteration:** Wraps all `_current_tool_messages` and `pending_interrupts` iterations with `list()` to snapshot before iterating. **HITL fallback branches:** The `else` branches for unexpected decision types previously fell through to reject without updating tool widget state or clearing `_current_tool_messages`. These now properly call `set_rejected()` and `.clear()`, matching the explicit reject branch. **Logging:** Replaces `with suppress(Exception)` with `try/except` + `logger.debug` so cancellation-cleanup failures are visible. Adds `logger.warning` for unexpected HITL decision types that previously failed silently. --------- Co-authored-by: Mason Daugherty <mason@langchain.dev> Co-authored-by: Mason Daugherty <github@mdrxy.com>
🤖 I have created a release *beep* *boop* --- ## [0.0.20](langchain-ai/deepagents@deepagents-cli==0.0.19...deepagents-cli==0.0.20) (2026-02-10) ### Features * **cli:** `--quiet` flag to suppress non-agent output w/ `-n` ([langchain-ai#1201](langchain-ai#1201)) ([3e96792](langchain-ai@3e96792)) * **cli:** add docs link to `/help` ([langchain-ai#1098](langchain-ai#1098)) ([8f8fc98](langchain-ai@8f8fc98)) * **cli:** built-in skills, ship `skill-creator` as first ([langchain-ai#1191](langchain-ai#1191)) ([42823a8](langchain-ai@42823a8)) * **cli:** enrich built-in skill metadata with license and compatibility info ([langchain-ai#1193](langchain-ai#1193)) ([b8179c2](langchain-ai@b8179c2)) * **cli:** implement message queue for CLI ([langchain-ai#1197](langchain-ai#1197)) ([c4678d7](langchain-ai@c4678d7)) * **cli:** model switcher & arbitrary chat model support ([langchain-ai#1127](langchain-ai#1127)) ([28fc311](langchain-ai@28fc311)) * **cli:** non-interactive mode w/ shell allow-listing ([langchain-ai#909](langchain-ai#909)) ([433bd2c](langchain-ai@433bd2c)) * **cli:** support custom working directories and LangSmith sandbox templates ([langchain-ai#1099](langchain-ai#1099)) ([21e7150](langchain-ai@21e7150)) ### Bug Fixes * **cli:** `-m` initial prompt submission ([langchain-ai#1184](langchain-ai#1184)) ([a702e82](langchain-ai@a702e82)) * **cli:** align skill-creator example scripts with agent skills spec ([langchain-ai#1177](langchain-ai#1177)) ([199d176](langchain-ai@199d176)) * **cli:** harden dictionary iteration and HITL fallback handling ([langchain-ai#1151](langchain-ai#1151)) ([8b21fc6](langchain-ai@8b21fc6)) * **cli:** per-subcommand help screens, short flags, and skills enhancements ([langchain-ai#1190](langchain-ai#1190)) ([3da1e8b](langchain-ai@3da1e8b)) * **cli:** port skills behavior from SDK ([langchain-ai#1192](langchain-ai#1192)) ([ad9241d](langchain-ai@ad9241d)), closes [langchain-ai#1189](langchain-ai#1189) * **cli:** rewrite skills create template to match spec guidance ([langchain-ai#1178](langchain-ai#1178)) ([f08ad52](langchain-ai@f08ad52)) * **cli:** terminal virtualize scrolling to stop perf issues ([langchain-ai#965](langchain-ai#965)) ([5633c82](langchain-ai@5633c82)) * **cli:** update splash thread ID on `/clear` ([langchain-ai#1204](langchain-ai#1204)) ([23651ed](langchain-ai@23651ed)) * **deepagents:** refactor summarization middleware ([langchain-ai#1138](langchain-ai#1138)) ([e87001e](langchain-ai@e87001e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes #956
Defensive hardening of dictionary iterations in
textual_adapter.py, plus fixes for incomplete cleanup in HITL fallback branches.Dictionary iteration: Wraps all
_current_tool_messagesandpending_interruptsiterations withlist()to snapshot before iterating.HITL fallback branches: The
elsebranches for unexpected decision types previously fell through to reject without updating tool widget state or clearing_current_tool_messages. These now properly callset_rejected()and.clear(), matching the explicit reject branch.Logging: Replaces
with suppress(Exception)withtry/except+logger.debugso cancellation-cleanup failures are visible. Addslogger.warningfor unexpected HITL decision types that previously failed silently.