Skip to content

Collect tool worker context failures without hanging - #1329

Merged
crmne merged 1 commit into
crmne:mainfrom
yorzi:fix/tool-concurrency-worker-failure
Oct 27, 2026
Merged

crmne merged 1 commit into
crmne:mainfrom
yorzi:fix/tool-concurrency-worker-failure

Conversation

@yorzi

@yorzi yorzi commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What this does

ToolConcurrency waits for one result per tool call, but each worker entered the workflow and subscriber context wrappers outside the rescue in capture_result. When a wrapper raised, the worker ended without a result and the caller waited forever. A wrapper failing on the way out could also follow a success that was already reported.

capture_result now runs both wrappers inside its rescue and builds the success only after they return, so every worker publishes exactly one result, and a teardown failure becomes that worker's error. The tool's value is captured in the block, so a subscriber's return value never replaces it. Workers are recorded as they start, so when a later Thread.new or task.async raises, the existing ensure still joins or waits for the ones already running.

The rescue Exception stays as it is: Async::Stop and Interrupt still reach the caller as errors and never as successful results. No thread is interrupted from outside.

The new examples cover:

  • Red before the fix, in both modes: setup failures reach the caller before a watchdog, teardown failures publish no success, workflow wrapper failures propagate, and already started workers are joined or waited for when a later launch fails. Fiber mode is also covered inside an existing reactor.
  • Guards, in both modes: tool values survive a subscriber that returns something else, remaining workers finish when a result callback fails, empty input returns an empty result, tool errors wait for every worker, and Rails executor run! and complete! failures propagate. Async::Stop and Interrupt never become successful results.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Performance improvement

Scope check

  • I read the Contributing Guide
  • This aligns with RubyLLM's focus on LLM communication
  • This isn't application-specific logic that belongs in user code
  • This benefits most users, not just my specific use case

Related issue

Fixes #1325

Quality check

  • I ran overcommit --install and all hooks pass
  • I tested my changes thoroughly
    • For provider changes: Re-recorded VCR cassettes with bundle exec rake vcr:record[provider_name]
    • All tests pass: bundle exec rspec --tag '~live' --tag '~generator' (6046 examples; the only 2 failures, in spec/ruby_llm/sandbox/mxc_spec.rb, fail the same way on main on macOS)
  • I updated documentation if needed (none needed: internal executor behavior, no documented API changes)
  • I didn't modify auto-generated files manually (models.json, aliases.json)

Ownership

  • I understand every line of this change and can answer questions about it

API changes

  • Breaking change
  • New public methods/classes
  • Changed method signatures
  • No API changes

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability when tools run concurrently, helping preserve the correct execution context and ensuring errors are reported without losing tool results.
    • Improved handling of failures during tool execution and worker startup, reducing the chance of incomplete or misleading results.

Context setup can fail before a worker publishes a result, leaving
the caller waiting forever. Capture the workflow and subscriber
wrappers with tool execution, and publish only after cleanup succeeds.
Keep the execution value independent of subscriber return values.

Retain already started workers when a later launch fails so cleanup
can still join or wait for them. Keep cancellation and interrupts on
the existing error path.

List the captured value's initial nil as an equivalent mutant: every
context wrapper yields, so the block always replaces it.

Fixes crmne#1325
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 59df5a0 and 1b7018c.

Files selected for processing: 3
  • .mutineer.yml
  • lib/ruby_llm/chat/tool_concurrency.rb
  • spec/ruby_llm/chat/tool_concurrency_spec.rb

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.

Walkthrough

Thread and fiber workers now pass workflow and instrumentation contexts to capture_result, which applies both scopes around isolated tool execution. Added specifications cover context and callback failures, worker cleanup, task stops, and interrupts.

Changes: Concurrent tool execution

Layer / File(s) Summary
Worker execution and result capture
lib/ruby_llm/chat/tool_concurrency.rb, spec/ruby_llm/chat/tool_concurrency_spec.rb, .mutineer.yml
Thread and fiber workers pass captured contexts to capture_result, which applies workflow and instrumentation scopes around tool execution. Specifications cover context setup and teardown failures, callback and workflow errors, empty inputs, and Rails executor failures. The mutant ignore list includes an entry for ToolConcurrency.capture_result.
Worker startup and completion
spec/ruby_llm/chat/tool_concurrency_spec.rb
Specifications cover cleanup of workers started before a later startup failure, Async task stops, and thread interrupts.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

Possibly related PRs

  • crmne/ruby_llm#1300: This PR preserves workflow context during concurrent tool execution, which relates to the workflow context recorded by PR #1300.

Suggested labels: bug

Suggested reviewers: crmne

Merge Risk: Minimal · up to 1b701

No specific issue in the changed worker behavior remains identified as a reason to delay merging. Normal validation is still appropriate.

Pre-merge checks: Passed 6 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within survi… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 6 passed
Check name Status Explanation
Title check Passed The title names the main change: tool worker context failures are now reported as results instead of leaving the collector waiting forever. It matches the linked issue #1325 and the description. It is…
Description check Passed The description covers the problem, the fix, and the new specs. It fills in the Bug fix type, the Scope check, the related issue (Fixes #1325), the test run results, and the API changes section. The Q…
Linked Issues check Passed Issue #1325 requires each worker to report wrapper setup and teardown failures, avoid collector hangs, and wait for workers that started before a later launch failure. capture_result now encloses wo…
Out of Scope Changes check Passed The changes stay within Issue #1325. The source change fixes worker result publication and cleanup. The added specifications verify the required concurrency behavior and related error guards. The `.mu…
Review Evidence Stays Out Of The Repository Passed The pull request changes only .mutineer.yml, lib/ruby_llm/chat/tool_concurrency.rb, and spec/ruby_llm/chat/tool_concurrency_spec.rb. These are configuration, implementation, and automated test f…
Dependencies From Sources The Maintainer Controls Passed The pull request changes only .mutineer.yml, lib/ruby_llm/chat/tool_concurrency.rb, and its spec. It does not change a manifest, lockfile, or package-manager configuration, and added lines contain…
Full details: Title check

Explanation

The title names the main change: tool worker context failures are now reported as results instead of leaving the collector waiting forever. It matches the linked issue #1325 and the description. It is specific and short, with 7 words and 52 characters.

Full details: Description check

Explanation

The description covers the problem, the fix, and the new specs. It fills in the Bug fix type, the Scope check, the related issue (Fixes #1325), the test run results, and the API changes section. The Quality check item for VCR cassettes is unchecked. It is not required here because the change does not touch providers. The Ownership section is checked. The template's 'What this does' section is detailed and explains the why. The description follows the template structure closely.

Full details: Linked Issues check

Explanation

Issue #1325 requires each worker to report wrapper setup and teardown failures, avoid collector hangs, and wait for workers that started before a later launch failure. capture_result now encloses workflow and context wrappers in the existing rescue Exception boundary. It constructs success only after both wrappers return. Thread and fiber workers push one captured result, and the incremental worker lists support the existing ensure joins and waits. The added specs cover setup, teardown, workflow, callback, tool, launch, thread, fiber, reactor, Async::Stop, and Interrupt paths.

Full details: Out of Scope Changes check

Explanation

The changes stay within Issue #1325. The source change fixes worker result publication and cleanup. The added specifications verify the required concurrency behavior and related error guards. The .mutineer.yml entry supports mutation-test coverage for the changed implementation.

Full details: Review Evidence Stays Out Of The Repository

Explanation

The pull request changes only .mutineer.yml, lib/ruby_llm/chat/tool_concurrency.rb, and spec/ruby_llm/chat/tool_concurrency_spec.rb. These are configuration, implementation, and automated test files. It adds no screenshots, recordings, comparison pages, or other review-evidence files.

Full details: Dependencies From Sources The Maintainer Controls

Explanation

The pull request changes only .mutineer.yml, lib/ruby_llm/chat/tool_concurrency.rb, and its spec. It does not change a manifest, lockfile, or package-manager configuration, and added lines contain no dependency source, git URL, fork, patch, override, replace, or alternate registry entry.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 2 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Oct 27, 2026
@github-actions
github-actions Bot requested a review from crmne October 27, 2026 06:59
@crmne
crmne merged commit 644d266 into crmne:main Oct 27, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Concurrent tool collection waits forever when a worker's context setup fails

2 participants