1515
1616import asyncio
1717import collections
18+ import json
1819import logging
1920import sys
2021from unittest import mock
@@ -996,6 +997,8 @@ async def test_execute_script_shell_success(mock_skill1):
996997 code_input = call_args [0 ][1 ]
997998 assert "subprocess.run" in code_input .code
998999 assert "bash" in code_input .code
1000+ assert "encoding='utf-8'" in code_input .code
1001+ assert "errors='replace'" in code_input .code
9991002 assert "__shell_result__" in code_input .code
10001003
10011004
@@ -1583,7 +1586,6 @@ async def test_integration_shell_stderr_only():
15831586@pytest .mark .asyncio
15841587async def test_shell_json_envelope_parsed (mock_skill1 ):
15851588 """Shell JSON envelope is correctly unpacked by run_async."""
1586- import json
15871589
15881590 envelope = json .dumps ({
15891591 "__shell_result__" : True ,
@@ -1607,7 +1609,6 @@ async def test_shell_json_envelope_parsed(mock_skill1):
16071609@pytest .mark .asyncio
16081610async def test_shell_json_envelope_nonzero_returncode (mock_skill1 ):
16091611 """Non-zero returncode in shell envelope sets stderr."""
1610- import json
16111612
16121613 envelope = json .dumps ({
16131614 "__shell_result__" : True ,
@@ -1627,10 +1628,31 @@ async def test_shell_json_envelope_nonzero_returncode(mock_skill1):
16271628 assert "Exit code 2" in result ["stderr" ]
16281629
16291630
1631+ @pytest .mark .asyncio
1632+ async def test_shell_json_envelope_nonzero_returncode_with_stderr (mock_skill1 ):
1633+ """Non-zero returncode in shell envelope appends exit code to stderr."""
1634+
1635+ envelope = json .dumps ({
1636+ "__shell_result__" : True ,
1637+ "stdout" : "" ,
1638+ "stderr" : "some error occurred" ,
1639+ "returncode" : 2 ,
1640+ })
1641+ executor = _make_mock_executor (stdout = envelope )
1642+ toolset = skill_toolset .SkillToolset ([mock_skill1 ], code_executor = executor )
1643+ tool = skill_toolset .RunSkillScriptTool (toolset )
1644+ ctx = _make_tool_context_with_agent ()
1645+ result = await tool .run_async (
1646+ args = {"skill_name" : "skill1" , "file_path" : "setup.sh" },
1647+ tool_context = ctx ,
1648+ )
1649+ assert result ["status" ] == "error"
1650+ assert result ["stderr" ] == "some error occurred\n Exit code 2"
1651+
1652+
16301653@pytest .mark .asyncio
16311654async def test_shell_json_envelope_with_stderr (mock_skill1 ):
16321655 """Shell envelope with both stdout and stderr reports warning."""
1633- import json
16341656
16351657 envelope = json .dumps ({
16361658 "__shell_result__" : True ,
@@ -1654,13 +1676,13 @@ async def test_shell_json_envelope_with_stderr(mock_skill1):
16541676@pytest .mark .asyncio
16551677async def test_shell_json_envelope_timeout (mock_skill1 ):
16561678 """Shell envelope from TimeoutExpired reports error status."""
1657- import json
16581679
16591680 envelope = json .dumps ({
16601681 "__shell_result__" : True ,
16611682 "stdout" : "partial output\n " ,
16621683 "stderr" : "Timed out after 300s" ,
16631684 "returncode" : - 1 ,
1685+ "timeout" : True ,
16641686 })
16651687 executor = _make_mock_executor (stdout = envelope )
16661688 toolset = skill_toolset .SkillToolset ([mock_skill1 ], code_executor = executor )
@@ -1673,6 +1695,7 @@ async def test_shell_json_envelope_timeout(mock_skill1):
16731695 assert result ["status" ] == "error"
16741696 assert result ["stdout" ] == "partial output\n "
16751697 assert "Timed out" in result ["stderr" ]
1698+ assert "Exit code" not in result ["stderr" ]
16761699
16771700
16781701@pytest .mark .asyncio
0 commit comments