Conversation
…lists
When a hooks/tools/providers list contains string-shorthand entries
(e.g. tools: ["bash"] instead of tools: [{"module": "bash"}]),
merge_module_lists previously crashed with AttributeError: 'str' object
has no attribute 'get' because it called .get() directly on each list
element.
Add _normalize_module_entry() helper that converts strings to
{key_field: <string>} dict-form before processing. Apply to both base
and overlay loops for symmetry. Non-string, non-dict entries (None, int,
etc.) are silently dropped rather than raising.
Discovered during reality-check capability E2E (Amplifier Resolve
platform) where step 2 (terminal-validation) consistently failed in
~11ms with no agent fork. Path C reproduction with traceback capture
revealed the actual exception line (merge_utils.py:62) and the call
path (executor.py -> session_spawner -> merge_configs -> merge_agent_dicts
-> merge_module_lists).
Root cause: reality-check/agents/terminal-tester.md declares
tools: [terminal_inspector] as bare-string YAML shorthand (intentional
author convention used across multiple bundles). The YAML parser returns
["terminal_inspector"], which merge_module_lists then iterates without
normalization.
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.
Summary
Fixes
AttributeError: 'str' object has no attribute 'get'raised atmerge_utils.py:62(and symmetrically in thebaseloop) when ahooks/tools/providerslist contains string-shorthand entriesinstead of dict-shape entries.
Root cause
String-shorthand is intentional — agent
.mdfiles declare moduledependencies using a bare YAML list of strings, e.g.:
rather than the full dict form:
The YAML parser returns
["terminal_inspector"](alist[str]). Whenthis agent config is overlaid onto the parent session config,
merge_agent_dictsroutes thetoolskey tomerge_module_lists,which unconditionally called
.get()on each list element — crashingimmediately for strings.
This pattern is used across multiple bundles:
reality-check/agents/terminal-tester.md→tools: [terminal_inspector]terminal-tester/agents/terminal-operator.md→tools: [terminal_inspector]terminal-tester/agents/terminal-visual-tester.md→tools: [terminal_inspector]terminal-tester/agents/terminal-debugger.md→tools: [terminal_inspector]The fix belongs in
merge_module_lists: normalize at the merge boundary,not in every caller or every YAML author. No upstream caller change is
needed because all authors are writing intentional shorthand, not bugs.
Fix
Added
_normalize_module_entry()helper that:str→{key_field: <string>}(e.g."bash"→{"module": "bash"})dictthrough unchangedNonefor anything else (silently dropped, rather than raising)merge_module_listsnow applies this normalization to bothbaseand
overlaybefore any.get()calls, via a single comprehension pass.The type signature is widened from
list[dict[str, Any]]tolist[Any]on both parameters (return type remains
list[dict[str, Any]]).Why this is hard to find
The exception is caught at
_execute_recipe's broadexcept Exception as e:in
amplifier-bundle-recipes/__init__.py:464, which converts it to a stringwithout the traceback. Resolving this required reproducing in a manual
interactive
amplifier tool invoke recipesinvocation with the outer exceptpatched to print
traceback.format_exc(), in order to find the actual linein
merge_utils.py:62.PR
microsoft/amplifier-bundle-recipes#71(isinstance(agent_cfg, dict)guards) addressed a similar class of bug but in
executor.py:1785/1803—two-and-a-half stack frames upstream of this one. The defense-in-depth
pattern needs to be applied throughout the merge chain.
Test
Adds
TestMergeModuleListsStringShorthandwith 13 cases covering:test_string_overlay_single_entryoverlay=["bash"]→[{"module": "bash"}]test_string_overlay_multiple_entriestest_string_overlay_merges_into_existing_basetest_string_overlay_appends_new_entrytest_mixed_overlay_strings_and_dictstest_string_base_single_entrybase=["bash"]→[{"module": "bash"}]test_string_base_multiple_entriestest_mixed_base_strings_and_dictstest_string_base_merged_with_dict_overlaytest_real_world_terminal_inspector["terminal_inspector"]test_key_field_respected_for_string_normalizationkey_fieldparam propagatestest_empty_liststest_garbage_entries_silently_droppedNone,intdropped gracefullyUpstream fix
No upstream fix was needed — the strings come from intentional YAML authoring
conventions, not from a serialization bug. The normalization at the merge
boundary is the correct and complete fix.
Discovered while
Running the
amplifier-bundle-reality-checkrecipe inside an Amplifier-Resolvereality-check runner sub-container. Step 2 (terminal-validation) consistently
failed in ~11ms because spawning the
reality-check:terminal-testeragent(which declares
tools: [terminal_inspector]) crashed inmerge_module_listsbefore the agent session could start.
🤖 Generated with Amplifier