Skip to content

Ansible Debugger Deep Dive


Dropping into the (debug) prompt is only useful once you know what to actually do once you’re there. This chapter covers the commands you’ll reach for constantly.

p And pprint: Printing Variables

(debug)> p app_port
8080
(debug)> pprint some_complex_dict
{'key1': 'value1', 'key2': ['a', 'b', 'c']}

p for simple values; pprint for anything nested — a dict or a list — formatting it across multiple readable lines instead of one dense one. The same instinct behind debug’s var: versus msg: distinction from the variables section, now applied at the debug prompt itself.

Inspecting The Failed Task Itself: task.args

(debug)> p task.args
{'src': 'app.conf.j2', 'dest': '/etc/myapp/app.conf'}

Shows exactly what arguments the task was actually given — genuinely useful for confirming what the task was really doing, rather than guessing from the playbook’s raw source, since the actual resolved values (after any variables were substituted in) can differ from what a quick read of the YAML suggests.

You can also inspect the failure itself:

(debug)> p result
{'msg': "AnsibleUndefinedVariable: 'app_port' is undefined", ...}

Modifying A Task’s Arguments Live: task.args[key] = value

(debug)> task.args['dest'] = '/tmp/app.conf'

Directly changes what the task will do if retried — useful for testing a fix without editing the actual playbook file and restarting the whole run.

Modifying Variables: task_vars And update_task

(debug)> task_vars['app_port'] = 9090
(debug)> update_task

task_vars holds the variable context the task will use — setting a value there changes what a variable reference inside the task would resolve to. This needs one extra step beyond modifying task.args directly: update_task re-parses the task using the updated variable context, applying the change before a retry actually uses it. Two genuinely different workflows: change task.args directly to fix a specific argument, or change task_vars plus update_task to fix the underlying variable a task argument depends on.

redo: Retrying The Task With Your Fix Applied

(debug)> redo

Re-runs the current task with whatever changes you’ve made. Succeed this time, and execution continues normally from there. Fail again, and you’re dropped right back into the debugger, free to keep iterating.

continue: Proceeding Normally

(debug)> continue

Stops debugging this particular task and lets the playbook proceed as it normally would from here — including normal failure handling, like a rescue: block if one exists. Use this once you’ve gathered what you needed and don’t actually want to retry the task itself.

quit: Aborting The Run

(debug)> quit

Stops the entire playbook run immediately — use this once you’ve learned enough to know you need to go fix something in the actual source files before trying again from scratch.

help: When You Forget A Command

(debug)> help

Lists every available command — a fine thing to lean on rather than trying to memorize the full set immediately.

A Realistic Walkthrough

- name: Deploy config from template
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/myapp/app.conf
  debugger: on_failed
[ubuntu] TASK: Deploy config from template (debug)> p result
{'msg': "AnsibleUndefinedVariable: 'app_port' is undefined"}
(debug)> p task_vars.get('app_port')
None
(debug)> task_vars['app_port'] = 8080
(debug)> update_task
(debug)> redo

Confirmed missing variable, supplied a value directly at the prompt, applied it, and retried — succeeding this time, with execution continuing normally from there.

A Gotcha: Changes Don’t Persist Beyond This One Debug Session

This is crucial to understand before relying on the debugger as more than an investigation tool. Anything set via task.args or task_vars in the debugger is a live, in-memory, temporary fix — for this one run, right now. It does not modify your actual playbook, template, or vars files on disk at all. The moment this run ends, the fix is gone. If the underlying issue was a genuine bug — a variable that should have a real default, a hardcoded path that’s wrong in the template — you still need to make that fix permanently, in the actual source files, afterward. The debugger confirms what the fix should be and proves it works; it doesn’t apply that fix anywhere lasting on its own.

Best Practices

  • Use p for simple values, pprint for anything nested — don’t strain to read a dense inline dump when pprint would format it clearly.
  • Inspect task.args and result first, before assuming you already know what went wrong — confirm against the actual resolved values, not just what the playbook source implies.
  • Remember the two-step task_vars + update_task workflow when fixing a variable, versus modifying task.args directly when fixing a specific argument.
  • Always convert a debugger-confirmed fix into a permanent change in your actual source files — nothing set at the debug prompt survives past the current run.
Last updated on