Skip to content

test: deflake async-delegation interrupt test under CI load - #64767

Merged
teknium1 merged 2 commits into
mainfrom
fix/async-delegation-interrupt-flake
Jul 15, 2026
Merged

test: deflake async-delegation interrupt test under CI load#64767
teknium1 merged 2 commits into
mainfrom
fix/async-delegation-interrupt-flake

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Deflakes test_interrupt_all_signals_running_children, which failed twice consecutively on a loaded CI worker (PR #64756's slice 5) while passing reliably on any quiet machine.

Root cause: the test's blocker used ev.wait(timeout=5) as an internal safety timeout. On a CPU-starved runner, more than 5s elapsed between dispatch and interrupt_all() — the blocker's wait expired on its own, the record finalized, and interrupt_all() found nothing running (n == 0, interrupted["count"] == 0 — the exact CI assertion failure). Mechanism reproduced locally by injecting a 5.6s dispatch→interrupt gap before touching anything; the fixed semantics survive the same gap.

Changes

  • tests/tools/test_async_delegation.py: raise the internal safety timeouts on gated runners from 5s → 60s (9 sites). Every test releases its gate explicitly, so these timeouts are only runaway guards — the pytest-level timeout is the real backstop. The 2-thread barrier.wait(timeout=5) is untouched (both parties arrive immediately; no load dependency).

Same flake class as test_crashed_runner_produces_error_completion, removed in #64431. This one is fixable rather than removable — the race was in the test's guard timeout, not its assertion design.

Validation

Check Result
Failure mechanism repro (5.6s gap, old semantics) n=0 count=0 — matches CI
Same gap, fixed semantics n=1 count=1
Full file under CI-parity runner 26 collected, all green

Infographic

Deflake interrupt-signal race

test_interrupt_all_signals_running_children failed twice on a loaded CI
worker: the blocker's ev.wait(timeout=5) expired before interrupt_all()
ran, the record finalized on its own, and interrupt_all() found nothing
running (n == 0, interrupted count 0 — the exact CI assertion failure,
reproduced locally by inserting a 5.6s dispatch->interrupt gap).

Raise the internal safety timeouts from 5s to 60s across the file's
gated runners — they exist only as runaway guards (every test releases
its gate explicitly); the pytest-level timeout is the real backstop.
Same flake class as the removed test_crashed_runner_produces_error_
completion (#64431).
@alt-glitch alt-glitch added type/test Test coverage or test infrastructure tool/delegate Subagent delegation P3 Low — cosmetic, nice to have labels Jul 15, 2026
The first CI failure was the 5s guard-timeout race; the rerun exposed a
SECOND mechanism with a different signature: 'completed' == 'interrupted'.
Prior tests (e.g. test_dispatch_rejected_at_capacity) release their gate
and return immediately, but their workers finalize asynchronously — on a
loaded runner the teardown drain races the in-flight _finalize, and the
straggler 'completed' events leak into the NEXT test's queue, where
_drain_one() picks one up instead of the interrupt event. Reproduced
locally: gate release + immediate drain + 0.15s finalize delay leaked 2
events.

Fixes:
- teardown waits (bounded 2s) for active workers to finalize before
  draining, so events land in the owning test
- the interrupt test matches its OWN delegation_id via _drain_for()
  instead of taking whatever event arrives first
@teknium1
teknium1 merged commit f5c2ea4 into main Jul 15, 2026
31 checks passed
@teknium1
teknium1 deleted the fix/async-delegation-interrupt-flake branch July 15, 2026 06:36
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…arch#64767)

* test: deflake async-delegation interrupt test under CI load

test_interrupt_all_signals_running_children failed twice on a loaded CI
worker: the blocker's ev.wait(timeout=5) expired before interrupt_all()
ran, the record finalized on its own, and interrupt_all() found nothing
running (n == 0, interrupted count 0 — the exact CI assertion failure,
reproduced locally by inserting a 5.6s dispatch->interrupt gap).

Raise the internal safety timeouts from 5s to 60s across the file's
gated runners — they exist only as runaway guards (every test releases
its gate explicitly); the pytest-level timeout is the real backstop.
Same flake class as the removed test_crashed_runner_produces_error_
completion (NousResearch#64431).

* test: fix cross-test completion-event leak (second flake mechanism)

The first CI failure was the 5s guard-timeout race; the rerun exposed a
SECOND mechanism with a different signature: 'completed' == 'interrupted'.
Prior tests (e.g. test_dispatch_rejected_at_capacity) release their gate
and return immediately, but their workers finalize asynchronously — on a
loaded runner the teardown drain races the in-flight _finalize, and the
straggler 'completed' events leak into the NEXT test's queue, where
_drain_one() picks one up instead of the interrupt event. Reproduced
locally: gate release + immediate drain + 0.15s finalize delay leaked 2
events.

Fixes:
- teardown waits (bounded 2s) for active workers to finalize before
  draining, so events land in the owning test
- the interrupt test matches its OWN delegation_id via _drain_for()
  instead of taking whatever event arrives first
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…arch#64767)

* test: deflake async-delegation interrupt test under CI load

test_interrupt_all_signals_running_children failed twice on a loaded CI
worker: the blocker's ev.wait(timeout=5) expired before interrupt_all()
ran, the record finalized on its own, and interrupt_all() found nothing
running (n == 0, interrupted count 0 — the exact CI assertion failure,
reproduced locally by inserting a 5.6s dispatch->interrupt gap).

Raise the internal safety timeouts from 5s to 60s across the file's
gated runners — they exist only as runaway guards (every test releases
its gate explicitly); the pytest-level timeout is the real backstop.
Same flake class as the removed test_crashed_runner_produces_error_
completion (NousResearch#64431).

* test: fix cross-test completion-event leak (second flake mechanism)

The first CI failure was the 5s guard-timeout race; the rerun exposed a
SECOND mechanism with a different signature: 'completed' == 'interrupted'.
Prior tests (e.g. test_dispatch_rejected_at_capacity) release their gate
and return immediately, but their workers finalize asynchronously — on a
loaded runner the teardown drain races the in-flight _finalize, and the
straggler 'completed' events leak into the NEXT test's queue, where
_drain_one() picks one up instead of the interrupt event. Reproduced
locally: gate release + immediate drain + 0.15s finalize delay leaked 2
events.

Fixes:
- teardown waits (bounded 2s) for active workers to finalize before
  draining, so events land in the owning test
- the interrupt test matches its OWN delegation_id via _drain_for()
  instead of taking whatever event arrives first
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have tool/delegate Subagent delegation type/test Test coverage or test infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants