Diagnosing Stale Ansible Fact Caches: When Conditionals Act on Yesterday's Host State
A diagnostic guide for when Ansible conditionals act on stale facts after a host is rebuilt or changed outside Ansible. Covers the recognizable condition, a cause/diagnostic table, ordered checks, fixes mapped to findings, and escalation criteria.
30 May 2026, 06:35 UTC

The condition: tasks fire or skip based on outdated host state
A host is rebuilt, re-IPed, or changed outside Ansible — a package installed manually, a service removed, a disk resized — yet the next playbook run behaves as if nothing happened. A when conditional that tests ansible_facts['packages'] evaluates against the old snapshot. Reruns keep producing the same wrong result no matter how many times you execute the play. The play does not fail; it silently acts on stale facts.
Ansible's fact caching stores the output of the setup module per host, keyed by inventory hostname, so that subsequent runs can skip the fact gathering step. When the cache outlives the facts, the play reads yesterday's state. This guide helps you confirm the cache is the cause and pick the right fix.
Cause and diagnostic table
| Likely cause | How to check | Fix |
|---|---|---|
| fact_caching enabled with a long timeout | ansible-config dump | grep FACT_CACHING | Shorten or drop fact_caching_timeout |
| Recycled inventory hostname inherits a predecessor's facts | Compare cached file against fresh setup gather | Delete the per-host cache file or rename inventory entries |
| Shared cache backend (redis, memcached) holding entries no single operator controls | Check which backend is configured and who owns flush rights | Define TTL and flush ownership for the shared cache |
| gather_subset or smart gathering limiting which facts exist | Run ansible -m setup and check for undefined variables in when conditionals | Restrict caching to expensive, slow-changing subsets |
Ordered checks
1. Confirm effective configuration
Run ansible-config dump and filter for FACT_CACHING to see which settings won precedence — ./ansible.cfg, ~/.ansible.cfg, or /etc/ansible/ansible.cfg. The built-in jsonfile plugin writes one file per host under fact_caching_connection. Other plugins (redis, memcached) usually come from community collections, so verify against your installed version with ansible-doc -t cache -l.
2. Inspect the cache
Look at the timestamps and contents of the per-host files under fact_caching_connection. If the cached facts predate the host change, the cache is the suspect.
3. Compare fresh gather against cache
Run ansible -m setup for a fresh gather and diff it against the cached file. If the fresh gather shows the new state but the play still uses the old one, the cache is confirmed.
4. Rerun with --flush-cache
Re-run the play with ansible-playbook --flush-cache. If the conditional now evaluates correctly, the cache was the problem. Note that --flush-cache clears cached facts for hosts in the current inventory at run start; it does not repair stale entries in other backends or outside that scope.
Fixes mapped to findings
- Volatile facts: shorten or drop
fact_caching_timeoutfor facts that change often. - CI jobs and runbooks: add
--flush-cacheafter reprovisioning so stale state has a known remedy. - Expensive facts: restrict caching to slow-changing subsets via
gather_subsetin the setup task. - Shared backends: define TTL and flush ownership so no single operator has to guess who clears the cache.
- Recycled hostnames: delete the per-host cache file or rename inventory entries to avoid inheriting a predecessor's facts.
Escalation criteria
If a fresh setup gather and the play still disagree with caching disabled, stop treating it as a cache problem. Escalate to variable precedence review — group_vars, host_vars, set_fact, ansible_facts naming — or report to the maintainers of the cache plugin collection.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.