Skip to content

fix(cli): harden dictionary iteration and HITL fallback handling#1151

Merged
Mason Daugherty (mdrxy) merged 10 commits intolangchain-ai:masterfrom
saakshigupta2002:fix/issue-956-dictionary-iteration
Feb 9, 2026
Merged

fix(cli): harden dictionary iteration and HITL fallback handling#1151
Mason Daugherty (mdrxy) merged 10 commits intolangchain-ai:masterfrom
saakshigupta2002:fix/issue-956-dictionary-iteration

Conversation

@saakshigupta2002
Copy link
Copy Markdown
Contributor

@saakshigupta2002 Saakshi Gupta (saakshigupta2002) commented Feb 5, 2026

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_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.

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
@github-actions github-actions Bot added cli Related to `deepagents-cli` external User is not a member of the `langchain-ai` GitHub organization fix A bug fix (PATCH) labels Feb 5, 2026
@mdrxy
Copy link
Copy Markdown
Member

Saakshi Gupta (@saakshigupta2002) could you add a test that fails without these changes?

@saakshigupta2002
Copy link
Copy Markdown
Contributor Author

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.
@github-actions github-actions Bot added fix A bug fix (PATCH) and removed fix A bug fix (PATCH) labels Feb 8, 2026
@saakshigupta2002
Copy link
Copy Markdown
Contributor Author

Saakshi Gupta (@saakshigupta2002) could you add a test that fails without these changes?

Hey Mason Daugherty (@mdrxy)!

Just pushed a commit with regression tests. I added a TestDictIterationSafety class with 6 tests that cover both .items() and .values() iteration patterns.

The key idea: I created _MutatingItemsDict and _MutatingValuesDict subclasses that delete a key mid-iteration, which deterministically triggers the same RuntimeError: dictionary changed size during iteration that happens in production when async tool callbacks fire during the HITL loop.

Two of the tests (test_items_iteration_fails_without_list and test_values_iteration_fails_without_list) prove that bare iteration crashes. The remaining tests prove that list() wrapping fixes it and that _build_interrupted_ai_message correctly uses the pattern.

Let me know if you'd like any changes! :)

@github-actions github-actions Bot added fix A bug fix (PATCH) and removed fix A bug fix (PATCH) labels Feb 9, 2026
@github-actions github-actions Bot added fix A bug fix (PATCH) and removed fix A bug fix (PATCH) labels Feb 9, 2026
@mdrxy Mason Daugherty (mdrxy) changed the title fix(cli): protect dictionary iterations from concurrent modification fix(cli): harden dictionary iteration and HITL fallback handling Feb 9, 2026
@github-actions github-actions Bot added fix A bug fix (PATCH) and removed fix A bug fix (PATCH) labels Feb 9, 2026
@mdrxy Mason Daugherty (mdrxy) merged commit 8b21fc6 into langchain-ai:master Feb 9, 2026
24 checks passed
Mason Daugherty (mdrxy) pushed a commit that referenced this pull request Feb 10, 2026
🤖 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>
james8814 pushed a commit to james8814/deepagents that referenced this pull request Mar 1, 2026
…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>
james8814 pushed a commit to james8814/deepagents that referenced this pull request Mar 1, 2026
🤖 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to `deepagents-cli` external User is not a member of the `langchain-ai` GitHub organization fix A bug fix (PATCH)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: 'dictionary changed size during iteration' during parallel tool calls in subagent

2 participants