ruby-concurrency/concurrent-ruby · error · Concurrent::Agent::ValidationError
Concurrent::Agent::ValidationError
Error message
Concurrent::Agent::ValidationError
What it means
`Agent#restart(new_value, opts)` is the recovery path for a failed agent: it first raises `Error('agent is not failed')` unless `failed?`, then validates `new_value` with the agent's `:validate` proc via `ns_validate`, raising Concurrent::Agent::ValidationError when the value is rejected — leaving the agent failed. The validator is the same proc supplied at creation (`Concurrent::Agent.new(0, validate: -> v { v.is_a?(Integer) })`); it is not exposed publicly, so keep your own reference if you need to pre-check values.
Source
Thrown at lib/concurrent-ruby/concurrent/agent.rb:428
# the `:clear_actions` option is give and true, any actions queued on the
# Agent that were being held while it was failed will be discarded,
# otherwise those held actions will proceed. The `new_value` must pass the
# validator if any, or `restart` will raise an exception and the Agent will
# remain failed with its old {#value} and {#error}. Observers, if any, will
# not be notified of the new state.
#
# @param [Object] new_value the new value for the Agent once restarted
# @param [Hash] opts the configuration options
# @option opts [Symbol] :clear_actions true if all enqueued but unprocessed
# actions should be discarded on restart, else false (default: false)
# @return [Boolean] true
#
# @raise [Concurrent:AgentError] when not failed
def restart(new_value, opts = {})
clear_actions = opts.fetch(:clear_actions, false)
synchronize do
raise Error.new('agent is not failed') unless failed?
raise ValidationError unless ns_validate(new_value)
@current.value = new_value
@error.value = nil
@queue.clear if clear_actions
ns_post_next_job unless @queue.empty?
end
true
end
class << self
# Blocks the current thread (indefinitely!) until all actions dispatched
# thus far to all the given Agents, from this thread or nested by the
# given Agents, have occurred. Will block when any of the agents are
# failed. Will never return if a failed Agent is restart with
# `:clear_actions` true.
#
# @param [Array<Concurrent::Agent>] agents the Agents on which to wait
# @return [Boolean] trueView on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass a value that satisfies the agent's validator — e.g. `agent.restart(Integer(params[:count]))` instead of the raw string.
- Keep the validator proc in a constant/reusable lambda, and call it yourself before `restart` to fail fast with a better message.
- If the value contract changed, fix the restart payload at its source; relaxing `:validate` requires creating the agent anew.
- Guard with `agent.failed?` first so you do not hit the sibling 'agent is not failed' error.
Example fix
// before agent.restart(params[:count]) # String from a web form -> ValidationError // after agent.restart(Integer(params[:count]))
Defensive patterns
Strategy: try-catch
Validate before calling
# keep the validator reusable at creation time
VALID = -> v { v.is_a?(Integer) }
agent = Concurrent::Agent.new(0, validate: VALID)
# before restarting:
raise ArgumentError, 'invalid restart value' unless agent.failed? && VALID.call(new_value) Try / catch
begin agent.restart(new_value) rescue Concurrent::Agent::ValidationError agent.restart(0) # known-valid fallback satisfying the validator rescue Concurrent::Agent::Error # agent was not failed; nothing to restart end
Prevention
- Define the validator once and reuse it for creation and restart pre-checks.
- Coerce restart payloads at the boundary (Integer(), JSON parsing) before they reach the agent.
- Check agent.failed? before calling restart.
- Cover restart paths in tests with valid and invalid values.
When it happens
Trigger: `agent.restart('oops')` on an agent created with `validate: -> v { v.is_a?(Integer) }` after it entered a failed state; restarting with a value whose shape differs from the initial one (nil for a numeric-validated agent); restart values taken raw from form params or parsed JSON.
Common situations: Error-recovery code that restores a hardcoded default without honoring the validator; validators tightened later so previously accepted restart values now fail; restart payloads built from user input.
Related errors
- agent is not failed
- unrecognized error mode
- no action given
- Concurrent::TimeoutError
- Required array size too large
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/c0909b67756e2275.
Report an issue: GitHub.