Skip to content
This repository was archived by the owner on Sep 12, 2026. It is now read-only.

feat: Add aggregate byte cap to subprocess output capture - #20

Merged
mabry1985 merged 1 commit into
mainfrom
feat/bd-un50-add-aggregate-byte-cap-to-subprocess-out
Aug 28, 2026
Merged

mabry1985 merged 1 commit into
mainfrom
feat/bd-un50-add-aggregate-byte-cap-to-subprocess-out

Conversation

@mabry1985

Copy link
Copy Markdown
Member

Summary

The _run() helper in tools.py previously buffered a browser command's entire stdout+stderr via subprocess.run(capture_output=True), so untrusted page content (via get text, get html, eval) could consume process memory and flood the model's context with no plugin-owned bound.

_run() now uses subprocess.Popen with two concurrent drain threads (one per pipe, so a full stderr can't deadlock stdout) that enforce an aggregate stdout+stderr byte cap. The cap is a new config key, max_response_bytes (default 200000), read at tool-registration time and exposed as an operator-editable Settings field. On overflow the child is killed and the tool returns a bounded Error: output exceeded {cap} bytes (truncated); on timeout the child is terminated — and in both cases reaped, so no zombies are left behind. Within-cap behavior is byte-for-byte unchanged (raw stdout stripped; stderr on failure still sliced to 500 chars), and the existing timeout_s continues to work alongside the new cap. Version bumped to 0.6.5 in both protoagent.plugin.yaml and pyproject.toml.

Tests were retargeted from mocking subprocess.run to a Popen stand-in with BytesIO pipes, plus new coverage for: over-cap truncation + child kill, exact-cap pass-through, aggregate stdout+stderr bounding, configured-cap override, the 200KB default when unconfigured, and timeout terminate-and-reap.

Fixes #18

Replace subprocess.run(capture_output=True) in _run() with Popen + two
concurrent drain threads that enforce a plugin-owned aggregate stdout+stderr
byte cap (max_response_bytes, default 200000). On overflow the child is killed
and a bounded "Error: output exceeded {cap} bytes (truncated)" is returned; on
timeout the child is terminated and reaped. Draining both pipes concurrently
avoids deadlock, and the child is always reaped (no zombies). Within-cap
behavior is unchanged; stderr on failure is still sliced to 500 chars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mabry1985
mabry1985 merged commit 8368753 into main Aug 28, 2026
1 check passed
@mabry1985
mabry1985 deleted the feat/bd-un50-add-aggregate-byte-cap-to-subprocess-out branch August 28, 2026 08:55

@protoreview protoreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA panel review — PASS

code-review-structural · head 7448a84b1f47 · formal

Low-risk PR: the core change (switching tools.py from subprocess.run to subprocess.Popen with drain threads, and updating the test mocks accordingly) was reviewed by two finders and found sound. The single actionable item is a stale documentation reference — PROTO.md's "Test patterns" section still describes the old conftest.fake_run / CompletedProcess mechanism that no longer exists. Fix first: update that bullet to describe the new fake_popen / _FakeProc pattern. No panel disagreement; the verifier confirmed the finding by re-reading PROTO.md on main and cross-checking the diff. Coverage note: tools.py (the main production file) drew zero findings, which is consistent with the finders' assessment that the Popen refactor is correct.

Findings

Severity Location Finding Verified
🟡 minor PROTO.md The PR moves CLI mocking from subprocess.run/conftest.fake_run to subprocess.Popen with a test-local fake_popen but leaves PROTO.md's 'Test patterns' section d… confirmed
findings JSON (machine-readable)
[
  {
    "file": "PROTO.md",
    "line": 0,
    "severity": "minor",
    "category": "conventions",
    "claim": "The PR moves CLI mocking from subprocess.run/conftest.fake_run to subprocess.Popen with a test-local fake_popen but leaves PROTO.md's 'Test patterns' section describing the old mechanism, so the repo's canonical grounding doc now misdescribes how the tool tests mock the CLI.",
    "evidence": "PROTO.md (base ref): \"Mock the CLI \u2014 monkeypatch `subprocess.run` with `conftest.fake_run(...)`. It records the argv it was called with and returns a canned `CompletedProcess`\" \u2014 but the diff replaces every such call with `monkeypatch.setattr(tools.subprocess, \"Popen\", fake_popen(out=\"OPENED\", record=rec))` and adds a new local `def fake_popen(out=b\"\", err=b\"\", rc=0, timeout=False, record=None, procs=None):` in tests/test_agent_browser.py; PROTO.md is not touched by the PR.",
    "verdict": "confirmed",
    "note": "Re-read PROTO.md on main: the 'Test patterns' bullet verbatim says 'monkeypatch subprocess.run with conftest.fake_run(...) \u2026 returns a canned CompletedProcess'. The PR diff shows all test calls now use subprocess.Popen + local fake_popen (returning _FakeProc with BytesIO pipes), and PROTO.md is absent from the diff. The doc is stale."
  }
]

1 finding(s) excluded from the verdict by in-diff confinement (file not among this PR's changed paths):

  • PROTO.md (minor) — The PR moves CLI mocking from subprocess.run/conftest.fake_run to subprocess.Popen with a test-local fake_popen but leaves PROTO.md's 'Test patterns' section de

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Browser tool wrappers capture subprocess output without a byte limit

1 participant