Skip to main content
RunBook Academy

AnsibleVIII · Modules and the Module ModelThe module model

How modules fail, and what the error means

Intermediate⏱ ~20 minansibleansible-galaxy

What you'll learn

  • Identify the five recurring module failure classes from their first lines
  • Sort a failure into a controller problem or a managed-node problem before investigating
  • Diagnose an unresolvable module name when the error cannot distinguish typo from missing collection
  • Recognise a module that succeeded on the target but reported failure, and recover safely

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Module failures cluster into five recurring shapes. Learning to tell them apart from the first three lines of the error is worth more than learning any individual module, because the wrong first guess sends you to the wrong machine and the first machine is usually where you spend the hour.

The organising question is: did this happen on the controller, or on the target? Two of the five never reach a host at all.

#FailureWhere
1Module could not be resolvedController
2Argument validation rejected the callTarget, before any work
3Interpreter missing or wrongTarget, before the module started
4Required Python library absentTarget, at module import
5Succeeded on the target, reported failureTarget, after the work

1. Module not found

Read-only / Safea name that resolved to nothing
$ ansible localhost -m ansible.builtin.stats -a "path=/tmp"
localhost | FAILED! => {
  "changed": false,
  "msg": "Task failed: Cannot resolve 'ansible.builtin.stats' to an action or module."
}

The marker is Cannot resolve. It is a controller problem, and it happens before anything is packaged or transferred.

2. Argument validation error

Read-only / Safean option that does not exist
$ ansible localhost -m ansible.builtin.stat -a "path=/etc/hostname folow=true"
localhost | FAILED! => {
  "changed": false,
  "msg": "Unsupported parameters for (ansible.builtin.stat) module: folow. Supported parameters include: checksum_algorithm, follow, get_attributes, get_checksum, get_mime, get_selinux_context, path."
}

Illustrative output

Three variants, all the same class:

Message beginsCause
Unsupported parameters for (...)Option name does not exist for this module
missing required arguments: ...A required option was not supplied
argument 'x' is of type str and we were unable to convert to ...Type mismatch

The type variant is worth seeing, because it names every acceptable value:

Read-only / Safea value of the wrong type
$ ansible localhost -m ansible.builtin.stat -a '{"path": "/etc/hostname", "get_checksum": "banana"}'
localhost | FAILED! => {
  "changed": false,
  "msg": "argument 'get_checksum' is of type str and we were unable to convert to bool: The value 'banana' is not a valid boolean. Valid booleans include: 0, 1, 'true', 'y', 'yes', 'on', '0', 'n', 't', 'no', '1', 'off', 'false', 'f'"
}

This is technically a target-side failure — the module was transferred and rejected the arguments there — but the fault is entirely in your playbook, so treat it as a controller problem for diagnostic purposes. Nothing about the target matters.

The high-value case is when the option is templated. A variable that rendered to the wrong type produces this error and the playbook text looks correct. That is the situation ANSIBLE_INJECT_INVOCATION=1 was made for: it puts the fully-resolved arguments into the result so you can see what the module was actually called with.

3. Wrong or missing interpreter

Read-only / Safethe interpreter does not exist
$ ansible localhost -m ansible.builtin.ping -e ansible_python_interpreter=/usr/bin/python9
localhost | FAILED! => {
  "changed": false,
  "module_stderr": "/bin/sh: 1: /usr/bin/python9: not found\n",
  "module_stdout": "",
  "msg": "The module interpreter '/usr/bin/python9' was not found.",
  "rc": 127
}

The markers are module_stderr containing a shell error, and rc: 127 — the shell’s “command not found”. module_stdout is empty because the module never produced any.

This is a managed-node problem, and it is usually one of three things: the host genuinely has no Python at the pinned path; a group variable pinned an interpreter for a host that does not match the group’s assumptions; or discovery picked something unexpected. The lesson on interpreter discovery covers the last one.

A related shape, distinguishable by the message rather than the code, is non-empty module_stdout containing something that is not JSON. That means a program ran and printed something Ansible could not parse — almost always a shell profile printing a banner on login, which corrupts the module output. MODULE FAILURE with visible motd text in module_stdout is that, every time.

4. Missing Python library on the target

Some modules import a third-party library and fail cleanly when it is absent. The message is generated by a shared helper, so it has a consistent shape:

Failed to import the required Python library (psycopg2) on
db01.example.com's Python /usr/bin/python3. Please read the module
documentation and install it in the appropriate location. If the
required library is installed, but Ansible is using the wrong Python
interpreter, please consult the documentation on
ansible_python_interpreter

Three things in that message are doing work, and it is worth reading all of them:

  • The library name, psycopg2 — what to install.
  • The hostname, db01.example.com — this is the target’s view.
  • The interpreter path, /usr/bin/python3 — which Python was asked for the import.

That third item is the one people skip and the one that resolves the common confusion. “But I installed it” is usually true, and installed into a different Python than the one Ansible used. Compare the path in the message against where you installed the library before installing it again.

5. Succeeded on the target, reported failure

The uncomfortable one, because the play stops and the change happened anyway.

Real causes: a module completes its work and then fails while gathering the state it wants to return; a module runs a helper whose exit status does not mean what the module assumed; a connection times out after the operation committed on the target.

Recognising it: the failure message names a step that is after the change. “Failed to read back the configuration”, “unable to determine service state”, “timed out waiting for the unit to become active” — all describe verification, not action.

The recovery has two steps and the order matters:

  1. Verify on the target with a read-only module. stat the file, service_facts the unit, slurp the config. Do not guess from the error text.
  2. Rerun the task if it is idempotent. A purpose-built module finds the state already correct, reports changed: false, and either succeeds — telling you the change landed — or fails the same way, telling you it did not.

Step 2 is only available if the task was written with a module rather than a shell line. A shell task rerun does the work twice, and whether that is harmless depends entirely on what the command was. This is the concrete payoff of everything the previous lessons argued: idempotency is not tidiness, it is what makes a partial failure recoverable.

The triage order

Given an unfamiliar failure, in this order:

  1. Read msg first. It is the module’s own summary and it is usually accurate.
  2. Is it Cannot resolve? Controller. Stop; do not SSH anywhere.
  3. Is it an argument message? Your playbook. Re-run with ANSIBLE_INJECT_INVOCATION=1 if the option was templated.
  4. Is there module_stderr or a non-zero rc? Target, and the module never ran. Look at the interpreter.
  5. Is there module_stdout that is not JSON? Target, and something polluted the stream.
  6. Otherwise the module ran and reported a failure. Trust the message, verify the state read-only, and decide whether to rerun.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A task fails with module_stderr containing "/usr/bin/python3.13: not found" and rc 127. Which machine is the problem on, and what ran?

  2. Q2. The "Cannot resolve X to an action or module" error looks the same whether the name is misspelled or the collection is simply not installed.

  3. Q3. A play fails on exactly one host out of fifty with the same task. Which explanations remain plausible? Select all that apply.

  4. Q4. A MODULE FAILURE arrives with module_stdout containing the text of the login banner. What is the actual cause?

Passing score: 75%. Answers are checked in this browser.