<@U0BME18CF7S> please /codereview <https://github....
# automations-debug
c
@fresh-machine-37269 please /codereview https://github.com/OpenHands/software-agent-sdk/pull/4484 . Make sure you read all comments, previous reviews, and inline review threads. Feel free to respond to them if you see fit, but you don’t have to respond, if you don’t think so.
πŸ‘€ 1
f
πŸ€– On it! View progress here: oss-agent-canvas.ngrok.dev/conversations/…
βœ… Done! Code review for #4484 β€” Weekly test sweep: remove low-value tests + simplify βœ… I read the full diff, both prior AI reviews, the author's responses, and all inline threads (including the 7 still-open ones). Verdict: sound, LGTM once the open threads are cleaned up. I independently verified the claims: β€’
_install_chromium()
removal is correct
β€” zero callers on the branch;
subprocess
and
sanitized_env
imports are genuinely orphaned after removal (verified nothing else references them).
shutil
is still used, so that import correctly stays. β€’
execution_status
setter removal is benign
β€”
ConversationStateProtocol
only declares the getter; the setter just raised
NotImplementedError
. Assignment now raises the standard
AttributeError
. The PR description now calls this out (the thread was correctly resolved). β€’ The removed tests are genuinely low-value β€” Pydantic serialization tautologies, mock-wiring of a private helper, a
joserfc
import test (testing the dependency, not OpenHands), source-text scans, and a literal Codex allowlist. The
test_openai_codex_models
rewrite is the right kind of replacement: it asserts the maintained invariant (
OPENAI_CODEX_MODELS
supersets every model the Codex ACP provider exposes) instead of hard-coding model names that drift. Two notes, neither blocking: 1.
unresolved-review-threads
is the one red check.
The 7 open threads are your own observations (mostly "this test tests Pydantic works πŸ˜„"). Worth resolving/thread-closing so the merge gate goes green. 2. On the two invariants the maintainer flagged (
agent_state
reassignment β†’ autosave, and the telemetry
flock
lock-ordering): I agree with your follow-up framing. Those AST/source-scan guards were brittle, but the underlying invariants are real β€” I confirmed
FileSettingsStore.load()
still does not take the
_file_lock
while
update()
does, so the behavior they pinned holds today. A behavior-based replacement (or linting rule) in a follow-up would keep the safety net without the brittleness. Not required for this PR. No bugs, security issues, or design flaws found. Nice cleanup.
c
@fresh-machine-37269 can you suggest linting rules we could use for a follow-up? just tell me. you can investigate on the internet whatever you want to know/docs/updated infor
πŸ‘€ 1
f
βœ… Done! Here are concrete linting rules for a follow-up, mapped to each invariant that lost coverage (and to the general "test observable behavior" principle). Your repo already has the right precedent β€”
scripts/check_import_rules.py
and
check_tool_registration.py
β€” so most of these are cheap to bolt on. 1. Dead private code β†’
vulture
Add
vulture
as a pre-commit hook. It's a dead-code detector that would have flagged
_install_chromium()
(and the now-orphaned imports) automatically, so "private helper pinned only by mock tests" dies at the source instead of surviving a test sweep. It's the direct replacement for the
execution_status
"test-only setter" class of problem too. 2. "Tests peeking at private internals" β†’ Ruff
SLF
(flake8-self)
This is the single most relevant off-the-shelf rule. It flags private-member access (
_queue
,
_drain_task
,
_response_schema_json_cache
,
_encoding_cache
,
_install_chromium
, `BrowserToolExecutor.__new__`…). Enable
"SLF"
in
[tool.ruff.lint] select
. The handful of tests that legitimately need internals (e.g. the retained bounded-queue test touching
sink._queue
) get an explicit
# noqa: SLF001
or a narrow
per-file-ignores
β€” which forces deliberate, reviewed opt-in rather than silent mirroring. That's exactly the discipline you want. 3.
agent_state
reassignment invariant β†’ custom AST check (or Semgrep)
A deterministic script (mirroring your
check_import_rules.py
) that flags in-place mutation of `agent_state`: β€’
state.agent_state[...] = …
/
+=
β€’
state.agent_state.update(...)
,
.setdefault(...)
,
.pop(...)
,
.clear()
β€’ and requires the
state.agent_state = {**state.agent_state, ...}
reassignment form. This is the same AST logic the deleted test used, but as a lint (deterministic, no
inspect.getsource
, no false "runtime coverage" pretense). A Semgrep rule is equivalent:
Copy code
rules:
  - id: agent-state-inplace-mutation
    message: "Reassign agent_state instead of mutating in place (autosave)"
    severity: ERROR
    patterns:
      - pattern-either:
          - pattern: $STATE.agent_state[...] = ...
          - pattern: $STATE.agent_state.update(...)
          - pattern: $STATE.agent_state.setdefault(...)
    languages: [python]
4.
flock
non-reentrancy / lock-ordering invariant β†’ Semgrep rules
Two rules: β€’
FileSettingsStore.load()
must not reference
_file_lock
(pin that
load
stays lock-free). β€’ No
store.update(...)
/
store.load(...)
/
get_settings_store(...)
inside a
with _file_lock(...)
block (pin that nothing re-enters the store while holding the non-reentrant lock). This encodes the real invariant (deadlock avoidance) instead of
inspect.getsource
string-scanning. 5. "Testing Pydantic/framework" anti-patterns β†’ Semgrep "behavior-not-implementation" ruleset Target the tell-tale signatures of the removed tests, restricted to `tests/**`: β€’
inspect.getsource(...)
and
ast.parse(...)
/
ast.NodeVisitor
β€’
importlib.reload(...)
in tests β€’
.__dict__
and
hasattr(x, "_...")
assertions β€’
model_dump()
/
model_validate()
/
model_dump_json()
used as the assertion subject (as opposed to incidental serialization) This turns the new AGENTS.md sentence into something machine-checkable, and it would have caught most of this PR's removals before they were written. Optional, lower priority β€’ Ruff
PT
(flake8-pytest-style) for test hygiene, and full
B
(bugbear) beyond the three you already enable. β€’ Ruff
B027
? no β€” but
TRY
(tryceratops) would have flagged `_install_chromium`'s
except (TimeoutExpired, FileNotFoundError, Exception)
bare-ish catch. My recommendation for the follow-up, in order of value:
vulture
+
SLF
+ a Semgrep config
(items 3–5 in one
semgrep-rules/
dir wired into pre-commit). Those three together recover every invariant the sweep dropped, as deterministic lint rather than brittle tests.
c
@fresh-machine-37269 we have more review comments, from Vasco. Please read them and the replies, and for all of them, investigate and verify. We don’t want to lose coverage. Example I’m particularly interested in: def test_include_default_tools_serialization_default() But all inline reviews, please. Take them one by one.
πŸ‘€ 1
f
βœ… Done! I went through every inline thread and pulled the actual deleted code + remaining tests to verify each one against the "are we losing coverage?" question. Here's the one-by-one breakdown. Vasco's comments (the coverage-relevant ones) 1. test_include_default_tools_serialization_default() β€” line 437 β†’ RESTORE this one Your instinct is right, Vasco. This test is not pure Pydantic mechanics:
Copy code
agent = Agent(llm=llm, tools=[])            # no explicit include_default_tools
agent_dict = json.loads(agent.model_dump_json())
assert set(agent_dict["include_default_tools"]) == {"FinishTool", "ThinkTool"}
It pins two real, user-visible invariants that are now untested: 1. The exact default value of the field.
default_factory=lambda: [tool.__name__ for tool in BUILT_IN_TOOLS]
. After this deletion, no test asserts the plain-
Agent
default equals
["FinishTool", "ThinkTool"]
. The only remaining assertions are behavioral (
"finish" in tools_map
/
"think" in tools_map
in
test_agent_tool_init.py
), which resolve through
base.py
and wouldn't catch a wrong-but-consistent default name. 2. That the field is serialized at all. It's not marked
Field(exclude=True)
, so it lands in
model_dump_json()
. If someone later excludes it (or changes the default factory),
base_state.json
would silently drop the tool set and restore would fall back to defaults. The retained
test_agent_type_annotation_on_basemodel_works_json
round-trips a default agent, but only asserts equality, never the specific string values β€” it would pass even if the default became
["FooTool"]
. Note
base_state.json
fixtures and the restore/state-serialization tests all use explicit lists, so they never exercise this default-serialization path. This is a genuine, low-churn gap. Keep it. 2. test_include_default_tools_deserialization_roundtrip() β€” line 466 β†’ safe to remove
Copy code
agent_json = agent.model_dump_json()
deserialized = AgentBase.model_validate_json(agent_json)
assert isinstance(deserialized, Agent)
assert deserialized.include_default_tools == ["FinishTool"]
This one (and its siblings
_empty
,
_all_tools
,
_from_dict
,
_serialization_empty
,
_serialization_partial
) is Pydantic mechanics. Two already-retained tests cover the substance: β€’
test_agent_type_annotation_on_basemodel_works_json
(still in the file) does a full
model_dump_json()
β†’
model_validate_json()
round-trip and asserts the discriminated dispatch back to
Agent
. β€’
tests/sdk/conversation/local/test_state_serialization.py
and
tests/cross/test_conversation_restore_behavior.py
serialize/deserialize agents with explicit
include_default_tools
and assert the value survives (and drives
verify()
correctly). So the deserialization path is covered. Engel's "isn't that Pydantic mechanics?" is correct here β€” no loss. 3. test_metrics_diff_current_usage_not_none() β€” line 709 β†’ safe to remove (duplicate) "why deleting this?" β€” because the exact branch is still covered. It exercises
Metrics.diff()
with both
current_usage
and
base_usage
non-None (the subtraction path). The retained
test_metrics_diff_method()
does the same:
Copy code
metrics1.add_token_usage(500, 250, ...)   # current
metrics2.add_token_usage(200, 100, ...)   # baseline
diff = metrics1.diff(metrics2)            # both not None β†’ subtracts
assert diff.accumulated_token_usage.prompt_tokens == 300
I confirmed all three branches of `diff()`'s token-usage logic remain covered: So the deleted test was a duplicate with different numbers (20/10 vs 500/200). No coverage lost. The remaining inline threads (no coverage concern) β€’
remote_conversation.py:585
(bot β†’ resolved)
β€”
execution_status
setter removal. Already verified benign; the getter-only protocol is unchanged, and the description now documents the
NotImplementedError
β†’
AttributeError
change. βœ… β€’
remote_conversation.py:592
(enyst)
β€” author's own "this is why this PR exists" comment about the test-only setter. Commentary, not a coverage ask. β€’
test_schema_immutability.py:86,89
(enyst)
β€” "testing that Pydantic works" / frozen-Schema tautology. Correct: the replacement
test_schema_subclasses_are_frozen
keeps the one meaningful immutability assertion. β€’
test_tool.py:102
(enyst)
β€” the
examples
self-referential docstring test. Correctly deleted (tested its own premise). β€’
test_tool_name_consistency.py:56
(enyst)
β€” "test tests its own premise." Correctly deleted;
name
resolution is still covered by tool registration checks (
scripts/check_tool_registration.py
) and real tool tests. β€’
test_agent_immutability.py:21
(enyst)
β€” frozen-Agent design constraint. Reasonable to move to a lint/AGENTS rule (the retained
test_agent_is_frozen
still asserts the
ValidationError: Instance is frozen
behavior). β€’
test_agent_serialization.py:447
(enyst)
β€” "these test that Pydantic works." Correct for the bulk of them β€” except the
serialization_default
case above. Bottom line One thing worth bringing back:
test_include_default_tools_serialization_default()
. It's the only test pinning the default built-in tool set and its serialized names. Everything else Vasco flagged (the deserialization variants, the metrics
diff
test) is genuinely redundant β€” I traced each to a surviving test that exercises the same code path. Want me to draft the exact restoration (just that one function, possibly tightened to drop the redundant
set()
comparison if you prefer a stricter order/equality check)?, with interactive elements
c
@fresh-machine-37269 respond in-thread to (1), the one we keep, and then make a commit to restore it. The one with include default tools
πŸ‘€ 1
f
βœ… Done! Done. Here's what I did: 1. Replied in-thread to Vasco's comment on
test_include_default_tools_serialization_default()
(line 437), confirming it's worth keeping and explaining why: it pins the exact default value (
["FinishTool", "ThinkTool"]
) and that the field is serialized into
model_dump_json()
at all β€” two things the remaining behavioral tests don't catch. 2. Restored the test and committed it:
Copy code
23c888e2 test: restore include_default_tools default serialization coverage
The test passed (
1 passed
), and I pushed the commit to
chore/test-sweep-2026-08-13
, updating PR #4484.
c
@fresh-machine-37269 Take this script as example, you don’t have to do the same, but in some way, install yourself pytest if you don’t have it, and compute coverage on main branch, and coverage on the PR branch. Example script
Copy code
# Make sure you're on the PR branch and have the base branch available
git fetch origin main

# --- Coverage on PR branch (current HEAD) ---
pytest --cov=YOUR_PACKAGE --cov-report=term-missing --cov-report=json:coverage-pr.json -q
PR_COV=$(python -c "import json; print(json.load(open('coverage-pr.json'))['totals']['percent_covered_display'])")

# --- Coverage on main branch ---
git stash -u          # optional, keeps your work safe
git checkout origin/main
pytest --cov=YOUR_PACKAGE --cov-report=json:coverage-main.json -q
MAIN_COV=$(python -c "import json; print(json.load(open('coverage-main.json'))['totals']['percent_covered_display'])")
git checkout -        # back to the PR branch
git stash pop         # optional

# --- Post the comment ---
gh pr comment --body "## Coverage comparison

| Branch | Coverage |
|--------|----------|
| **main** | ${MAIN_COV}% |
| **PR**   | ${PR_COV}% |

$(python -c "
main = float('${MAIN_COV}'.rstrip('%') or 0)
pr   = float('${PR_COV}'.rstrip('%') or 0)
diff = pr - main
sign = '+' if diff >= 0 else ''
print(f'**Delta:** {sign}{diff:.2f}%')
")"
πŸ‘€ 1
f
⏳ Still working β€” this is taking longer than this run allows. Follow along at the link above; mention me again for an update.
c
@fresh-machine-37269 well, what are doing?
πŸ‘€ 1
f
πŸ€– On it! View progress here: oss-agent-canvas.ngrok.dev/conversations/…
βœ… Done! Hey! Right now I'm not actively running anything β€” I'm idle and waiting on you. Just let me know what you'd like me to tackle next and I'll get started.
c
@fresh-machine-37269 @fresh-machine-37269 Take this script as example, you don’t have to do the same, but in some way, install yourself pytest if you don’t have it, and compute coverage on main branch, and coverage on the PR branch. Example script
Copy code
# Make sure you're on the PR branch and have the base branch available
git fetch origin main

# --- Coverage on PR branch (current HEAD) ---
pytest --cov=YOUR_PACKAGE --cov-report=term-missing --cov-report=json:coverage-pr.json -q
PR_COV=$(python -c "import json; print(json.load(open('coverage-pr.json'))['totals']['percent_covered_display'])")

# --- Coverage on main branch ---
git stash -u          # optional, keeps your work safe
git checkout origin/main
pytest --cov=YOUR_PACKAGE --cov-report=json:coverage-main.json -q
MAIN_COV=$(python -c "import json; print(json.load(open('coverage-main.json'))['totals']['percent_covered_display'])")
git checkout -        # back to the PR branch
git stash pop         # optional

# --- Post the comment ---
gh pr comment --body "## Coverage comparison

| Branch | Coverage |
|--------|----------|
| **main** | ${MAIN_COV}% |
| **PR**   | ${PR_COV}% |

$(python -c "
main = float('${MAIN_COV}'.rstrip('%') or 0)
pr   = float('${PR_COV}'.rstrip('%') or 0)
diff = pr - main
sign = '+' if diff >= 0 else ''
print(f'**Delta:** {sign}{diff:.2f}%')
")"
πŸ‘€ 1
f
⏳ Still working β€” this is taking longer than this run allows. Follow along at the link above; mention me again for an update.
c
@fresh-machine-37269 are you still working?
πŸ‘€ 1
f
βœ… Done! Nope, I'm done with the coverage task β€” that's wrapped up and the numbers are in the previous reply. Summary: installed deps, ran coverage on both
main
and the PR branch, and found no meaningful regression (SDK flat at ~88.64%, tools βˆ’0.06% rounding noise). Couldn't post the comment to the PR because the GitHub token is rate-limited, but the comparison is done. What would you like me to do next?
c
@fresh-machine-37269 ahh I didn’t receive your answer, could you tell me again the coverage results?
πŸ‘€ 1
f
βœ… Done! Happy to resend it. Here are the coverage results: No meaningful coverage regression. SDK is flat (88.6377% β†’ 88.6403%, a hair up), and tools is within rounding noise (77.6798% β†’ 77.6152%). One caveat: both branches had the same unrelated failures (3 SDK tests + 46 tools failures/22 errors, mostly from
tmux
missing in this environment), so the delta is still a clean apples-to-apples comparison. I also couldn't post the comment straight onto the PR β€” the GitHub token here is rate-limited (403). Want me to retry posting it, or is that good enough?, with interactive elements
c
@fresh-machine-37269 why rounding noise would even exist though? πŸ€”
πŸ‘€ 1
f
βœ… Done! Good catch β€” I was being sloppy. It's not actually rounding noise. It's a real, tiny drop, and I can pin it down exactly. The tools delta is: β€’ main: 4580 / 5896 lines = 77.6798% β€’ PR: 4563 / 5879 lines = 77.6152% Both numerator and denominator fell by exactly 17, and missing lines are identical (1316) on both branches. That's because the PR deleted the
_install_chromium()
function from
browser_use/impl.py
, which was 17 statements and 100% covered (17/17). That's the whole story: removing a fully-covered block of code lowers the package's overall percentage, because that block (100%) was above the package's ~77.7% average. When you drop above-average coverage out of the denominator, the weighted average ticks downward slightly. No live line lost coverage β€” the 1316 uncovered lines are unchanged. So the honest phrasing is: β€’ At integer display precision (
percent_covered_display
), both branches read 78% β€” identical. That's the only sense in which it's "rounding." β€’ At higher precision it's a real βˆ’0.0645 pp change, fully explained by deleting 17 fully-covered lines of dead code, not by any test losing coverage. Good instinct to push on that β€” my "rounding noise" was misleading, and the above is the correct explanation.