Skip to content

test(concurrent-interrupt): add missing _apply_pending_steer_to_tool_results no-op to _Stub#12574

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/test-stub-apply-pending-steer
Closed

test(concurrent-interrupt): add missing _apply_pending_steer_to_tool_results no-op to _Stub#12574
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/test-stub-apply-pending-steer

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

TL;DR

tests/run_agent/test_concurrent_interrupt.py::_Stub is missing _apply_pending_steer_to_tool_results, so both concurrent-interrupt tests fail on clean origin/main with AttributeError. This is test-only drift — production code is unaffected.

Fixing this takes two tests off the repo's standing baseline-failure list that's currently blocking every open PR's test check.

Root cause

_execute_tool_calls_concurrent (run_agent.py:7940+) is bound onto a minimal _Stub class for these tests. At run_agent.py:8092 the method unconditionally calls:

self._apply_pending_steer_to_tool_results(messages, num_tools)

That method was added to AIAgent at run_agent.py:3339 but the stub was never updated. Every invocation of the interrupt-path tests raises:

AttributeError: '_Stub' object has no attribute '_apply_pending_steer_to_tool_results'

Reproduces on clean origin/main (6fb69229):

$ python -m pytest tests/run_agent/test_concurrent_interrupt.py -q
2 failed, 2 passed in 3.16s
FAILED test_concurrent_interrupt_cancels_pending
FAILED test_running_concurrent_worker_sees_is_interrupted

Fix

Add a no-op _apply_pending_steer_to_tool_results to _Stub, matching the existing pattern for the other stub methods (_vprint, _safe_print, _should_emit_quiet_tool_messages, _has_stream_consumers, …).

Why a no-op (rather than mirroring the real method's state machine):

  • These tests assert only interrupt propagation + worker cancellation — they never set _pending_steer or exercise the /steer injection path.
  • The real method would early-return anyway (_drain_pending_steer returns None when _pending_steer is unset), so a no-op is behaviourally equivalent for this test surface.
  • Keeping the stub small avoids dragging in _drain_pending_steer + _pending_steer + _pending_steer_lock state that isn't under test.
def _apply_pending_steer_to_tool_results(self, messages, num_tool_msgs):
    # Real AIAgent method (run_agent.py:3339) mutates ``messages`` to
    # append any pending ``/steer`` text to the last tool-result.
    # These interrupt tests never set ``_pending_steer``, so the
    # real implementation would early-exit anyway — we stub it as a
    # no-op to avoid dragging in ``_drain_pending_steer`` +
    # ``_pending_steer`` / ``_pending_steer_lock`` state that isn't
    # under test here.  Required since ``_execute_tool_calls_concurrent``
    # (run_agent.py:8092) unconditionally calls this at the end of
    # each tool batch.
    return None

Validation

source venv/bin/activate
python -m pytest tests/run_agent/test_concurrent_interrupt.py -q
# 4 passed  (was: 2 failed, 2 passed on origin/main)

Broader tests/run_agent/ under -n auto783 passed, 7 skipped, 0 failures.

Narrow scope — explicitly not changed

  • No production code touched. run_agent.py is unmodified.
  • No other stub methods added. Only the one method needed to unblock the 2 failing tests. Other drift (if any) is out of scope for this PR.
  • No other baseline failures addressed. Separate follow-ups can take those (schema-version pin drift, tool-rename test drift, etc.); this PR keeps scope surgical.

Pre-empted review questions

Q. Should the stub track _pending_steer state instead, to better mirror production?
The existing stub is deliberately minimal (see its other _vprint/_safe_print/… no-ops). The tests don't exercise the steer path, so carrying that state would be speculative complexity. If a future test does want to exercise /steer injection during concurrent interrupt, that test can extend the stub then — per its actual needs.

Q. Why not fix it in production (make _execute_tool_calls_concurrent tolerant of a missing method)?
Because nothing is wrong in production — AIAgent always has this method. The bug is purely in the test stub that drifted from the class it's shadowing.


Co-authored via LLM assistance; I've reviewed every line and am responsible for correctness.

…results no-op to _Stub

``tests/run_agent/test_concurrent_interrupt.py`` binds the real
``AIAgent._execute_tool_calls_concurrent`` method (``run_agent.py:7940``+)
onto a minimal ``_Stub`` class, but the stub has drifted from the
production class: ``_execute_tool_calls_concurrent`` at
``run_agent.py:8092`` unconditionally calls
``self._apply_pending_steer_to_tool_results(messages, num_tools)`` at
the end of each tool batch, and the stub has no such method.

Result: both interrupt-path tests fail on clean ``origin/main``
(``6fb69229``)::

    FAILED test_concurrent_interrupt_cancels_pending
    FAILED test_running_concurrent_worker_sees_is_interrupted

    AttributeError: '_Stub' object has no attribute
    '_apply_pending_steer_to_tool_results'

Fix
---
Add a no-op ``_apply_pending_steer_to_tool_results`` to ``_Stub``,
matching the pattern of the existing no-op stub methods
(``_vprint``, ``_safe_print``, ``_should_emit_quiet_tool_messages``,
…).

These tests never exercise the ``/steer`` injection path — they
assert only interrupt propagation and worker cancellation.  The real
method would early-return anyway (``_drain_pending_steer`` returns
``None`` when ``_pending_steer`` is unset), so a no-op is
behaviourally equivalent for this test surface without requiring the
stub to also carry ``_drain_pending_steer`` / ``_pending_steer`` /
``_pending_steer_lock`` state.

Validation
----------
``source venv/bin/activate && python -m pytest
tests/run_agent/test_concurrent_interrupt.py -q`` → **4 passed** on
the branch (was ``2 failed, 2 passed`` on ``origin/main``).

Broader ``tests/run_agent/`` under ``-n auto`` → **783 passed, 7
skipped, 0 failures** — no regression in any other tests that exercise
the stub pattern or the production ``_execute_tool_calls_concurrent``
path.

Scope
-----
Test-only change.  Zero production-code impact.  Narrow to the two
failing tests — no other baseline failures are touched by this PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 19, 2026 13:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes test-only drift in the concurrent-interrupt test stub so it matches the current internal AIAgent API shape and no longer fails with AttributeError on clean main.

Changes:

  • Add missing no-op _apply_pending_steer_to_tool_results(...) method to the _Stub used by tests/run_agent/test_concurrent_interrupt.py.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@briandevans

Copy link
Copy Markdown
Contributor Author

CI notes on the initial run:

Focused suite on branch: pytest tests/run_agent/test_concurrent_interrupt.py -q4 passed (was 2 failed, 2 passed on origin/main).
Broader tests/run_agent/ under -n auto783 passed, 7 skipped, 0 failures.

Happy to rebase if a rerun would help — I don't have admin rights on the repo, so I can't trigger one directly.

briandevans added a commit to briandevans/hermes-agent that referenced this pull request Apr 20, 2026
…match __init__

``tests/run_agent/test_interrupt_propagation.py::_make_bare_agent``
constructs an ``AIAgent`` via ``__new__``, bypassing ``__init__``, and
manually wires up the interrupt-related attributes used by the tests.
``AIAgent.__init__`` (``run_agent.py:736``) sets ``self.provider`` but
``_make_bare_agent`` did not.

Under the full ``tests/`` suite with pytest-xdist (``-n auto``), the
``test_interrupt_during_child_api_call_detected`` path can reach
``_create_openai_client`` (``run_agent.py:4660``, ``4671``) which
accesses ``self.provider`` directly (not via ``getattr``), raising::

    AttributeError: 'AIAgent' object has no attribute 'provider'

The mocked-client fast-path in ``_create_request_openai_client``
normally short-circuits before reaching that code, but under xdist
worker-import ordering the short-circuit doesn't always engage, so the
test fails intermittently in CI while passing in isolation and under
broader-but-not-full module invocations.

Fix
---
Seed ``agent.provider = ""`` in the ``_make_bare_agent`` helper so the
bare agent matches what ``__init__`` would produce for the same
attribute.  Same surgical pattern as the landed
``1cf10b45`` (NousResearch#12574) ``_Stub`` fix in
``tests/run_agent/test_concurrent_interrupt.py`` — test-only, zero
production-code change, minimal scope.

Validation
----------
``source venv/bin/activate && python -m pytest
tests/run_agent/test_interrupt_propagation.py -q`` → **7 passed**.

Broader ``tests/run_agent/`` under ``-n auto`` → **781 passed, 7
skipped, 0 new failures** (the 2 existing
``test_concurrent_interrupt`` failures are the separately-tracked
baseline handled by NousResearch#12574).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/test Test coverage or test infrastructure P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels Apr 23, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing as superseded — the exact stub fix landed upstream in aa5bd092 (part of #12670 "fix(tests): unstick CI"). tests/run_agent/test_concurrent_interrupt.py line 83 on origin/main now has:

stub._apply_pending_steer_to_tool_results = lambda *a, **kw: None

Exactly matching this PR. Good outcome — the cleanup sweep caught it. Closing to keep the queue tidy.

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

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/test Test coverage or test infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants