module

Krikri::ErrorBlock

Matches ansible-core 2.19's task-failure error-block rendering: Display._error -> _display_utils.format_message -> _event_formatting.format_event/format_event_verbose_message, over an Event tree whose source context comes from _error_utils.SourceContext.from_origin. This is what ansible-playbook prints as

[ERROR]: Task failed: <message> ... Origin: <file>:<line>:<col> ... <<< caused by >>> ...

before the fatal: line whenever a task failure carries an exception event (controller/action exceptions, module failures wrapped by the module API, conditional-evaluation failures).

The event tree is small (a message, its optional source context, and a linear cause chain); every shape Ansible 2.19 produces for task failures is one of:

  • collapsed: the whole cause chain collapses into one segment ("Task failed: Module failed: non-zero return code" + the task origin), when every chained event shares the direct-cause reason and carries no distinct source context.
  • handling: the chain breaks at an event whose own chain reason differs ("raise X(...) from Y" vs a bare raise X(...) inside except Y), producing the two-segment form with the <<< caused by >>> separator (template action failures, conditional-evaluation failures).

Constants

DIRECT_CAUSE = "<<< caused by >>>"
HANDLING = "<<< while handling >>>"
MODULE_FAILED_SEGMENT = "Module failed: "
SET_ORDERED_BOOL_ENUM = "is not a valid boolean. Valid booleans include: "

Ansible composes its "Valid booleans include: ..." enumeration from a Python SET (BOOLEANS in module_utils/parsing/convert_bool.py), whose iteration order is randomized per PROCESS. A MODULE runs in a fresh process per invocation on the target, so one module-level bool failure never renders the same text twice and Display._deduplicate cannot collapse two of them: a loop's identical failed items - and the same failure on several hosts - each print their own block (live-verified vs ansible-core 2.21.6, issue #952: five expect: items gave five blocks with five orderings, two hosts gave two). krikri keeps this enumeration's order fixed (see PluginHelpers::StrictBoolValidation), so its rendering of the same failures WOULD dedupe - hence the exemption in record_displayed.

The identical wording raised controller-side (set_fact's cacheable:, gather_facts' parallel:) comes from the one long-lived controller process, whose set order never changes, so real DOES collapse those repeats into a single block (live-verified vs 2.21.6). Only the module-rendered chain carries the "Module failed: " segment, which is what separates the two.

Class methods

brief_message(event : Node) : String

Matches _event_utils.format_event_brief_message.

Source
concat_message(left : String, right : String) : String

Matches _text_utils.concat_message.

Source
deduplicate_message_parts(parts : Array(String)) : String

Matches _event_utils.deduplicate_message_parts.

Source
emit(node : Node) : Bool

Renders the event tree into Ansible's [ERROR]: ... block and prints it, unless the exact text was already displayed (Ansible's Display._deduplicate - one global set, so a loop's per-item failures with identical text, or the same failure on several hosts, display the block once). Returns true when printed.

Source
emit_rendered_stderr(text : String) : Bool

Same dedup and STDERR routing as emit_stderr, for a block already rendered at its raise site (a RemovedModuleError's render carries the Origin context the raise site built from the source map).

Source
emit_stderr(node : Node) : Bool

Same rendering and dedup as emit, but on STDERR. Ansible routes an error block to stderr when the error is raised OUTSIDE task result processing - a dynamic include_role:'s role resolution failure ("The role 'x' was not found in: ...") - while ordinary task-failure blocks go to stdout (live-verified vs 2.21.6).

Source
emit_to(io : IO, node : Node) : Bool

emit variant capturing to an IO instead of printing: TaskExecutor's unreachable report buffers its block (run_task_batch books unreachable hosts before it knows the task banner is coming, and ansible prints the report under that banner).

Source
format_event(event : Node) : String

Matches _event_formatting.format_event (no traceback).

Source
format_event_verbose_message(original : Node) : String

Matches _event_formatting.format_event_verbose_message.

A Node owns its outgoing chain link (chain/chain_reason/ chain_follow mirror EventChain.event/msg_reason/follow), so the traversal walks node-as-link-owner pairs exactly the way the Python walks EventChain objects.

Source
message_lines(message : String, help_text : String | Nil, source_context : String | Nil) : Array(String)

Matches _event_formatting._get_message_lines.

Source
origin_context(path : String, line_num : Int, col_num : Int | Nil, show_content : Bool = true) : String

Matches _error_utils.SourceContext.from_origin + #to_s: Origin: path:line:col, a blank line, then up to 2 lines of context before the target line (right-aligned line numbers, tabs collapsed to single spaces, 120-column truncation with a ... marker) and the caret line under the target column. Returns the block WITHOUT a trailing newline.

Source
reset_seen

Test hook: forget all displayed blocks.

Source
seen?(text : String) : Bool
Source

Nested types