From 065fa60f1b6a7b71447ee184c326a7e4158fff9d Mon Sep 17 00:00:00 2001 From: Vishali Date: Tue, 7 Apr 2026 16:21:21 -0400 Subject: [PATCH 1/2] raise gemini cli stream buffer limit to 10 MB the default asyncio StreamReader limit of 64 KB causes crashes when an MCP tool returns a large response (e.g. kubernetes events list). closes #1126 Signed-off-by: Vishali --- .../bridges/gemini_cli/session.py | 1 + .../tests/test_gemini_session.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/components/runners/ambient-runner/ambient_runner/bridges/gemini_cli/session.py b/components/runners/ambient-runner/ambient_runner/bridges/gemini_cli/session.py index c8ee67534..2b420ec3a 100644 --- a/components/runners/ambient-runner/ambient_runner/bridges/gemini_cli/session.py +++ b/components/runners/ambient-runner/ambient_runner/bridges/gemini_cli/session.py @@ -152,6 +152,7 @@ async def query( stderr=asyncio.subprocess.PIPE, cwd=self._cwd, env=env, + limit=10 * 1024 * 1024, # 10 MB — default 64 KB is too small for large MCP tool responses ) # Start concurrent stderr streaming diff --git a/components/runners/ambient-runner/tests/test_gemini_session.py b/components/runners/ambient-runner/tests/test_gemini_session.py index 94fbaf260..437a87846 100644 --- a/components/runners/ambient-runner/tests/test_gemini_session.py +++ b/components/runners/ambient-runner/tests/test_gemini_session.py @@ -78,6 +78,26 @@ async def _wait(): # ------------------------------------------------------------------ +class TestWorkerSubprocessConfig: + """Verify subprocess configuration passed to create_subprocess_exec.""" + + @pytest.mark.asyncio + async def test_stream_buffer_limit_raised(self): + """The subprocess must use a 10 MB buffer limit to handle large MCP tool responses.""" + worker = GeminiSessionWorker(model="gemini-2.5-flash", api_key="key1") + proc = _make_mock_process( + stdout_lines=[b'{"type":"result"}\n'], + stderr_lines=[], + ) + + with patch("asyncio.create_subprocess_exec", return_value=proc) as mock_exec: + async for _ in worker.query("test"): + pass + + call_kwargs = mock_exec.call_args[1] + assert call_kwargs["limit"] == 10 * 1024 * 1024 + + class TestWorkerCommandConstruction: """Verify the CLI command built by query().""" From 46a816890b10dd764654a73d0771a35c9e0ff8ba Mon Sep 17 00:00:00 2001 From: Vishali Date: Tue, 7 Apr 2026 16:58:58 -0400 Subject: [PATCH 2/2] add integration test proving 10 MB buffer handles >64 KB lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spawns a real subprocess that outputs 100 KB on a single line and verifies readline() succeeds — would crash with the default 64 KB StreamReader limit. Signed-off-by: Vishali --- .../tests/test_gemini_session.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/runners/ambient-runner/tests/test_gemini_session.py b/components/runners/ambient-runner/tests/test_gemini_session.py index 437a87846..5ac60edb5 100644 --- a/components/runners/ambient-runner/tests/test_gemini_session.py +++ b/components/runners/ambient-runner/tests/test_gemini_session.py @@ -97,6 +97,24 @@ async def test_stream_buffer_limit_raised(self): call_kwargs = mock_exec.call_args[1] assert call_kwargs["limit"] == 10 * 1024 * 1024 + @pytest.mark.asyncio + async def test_large_stdout_line_does_not_crash(self): + """A real subprocess outputting >64 KB on one line must not raise ValueError. + + Without the 10 MB limit the default 64 KB StreamReader would blow up with: + ValueError: Separator is found, but chunk is longer than limit + """ + payload = "x" * 100_000 # 100 KB — well above the old 64 KB default + proc = await asyncio.create_subprocess_exec( + "python3", "-c", f'print("{payload}")', + stdout=asyncio.subprocess.PIPE, + limit=10 * 1024 * 1024, + ) + line = await proc.stdout.readline() + await proc.wait() + assert len(line) > 64 * 1024 + assert proc.returncode == 0 + class TestWorkerCommandConstruction: """Verify the CLI command built by query()."""