# Include\_tasks docs lack info on tail recursion

**URL:** <https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306>\
**Category:** Project Discussions\
**Tags:** include\_tasks, playbook, documentation\
**Created:** [November 12, 2023, 2:02pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306 "2023-11-12T14:02:16Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [November 12, 2023, 2:02pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/1 "2023-11-12T14:02:17Z")

</div>

Continuing the discussion from [Async task / queue management](http://forum.ansible.com/t/async-task-queue-management/2296/3):

The fantastic solution from @flowerysong in the above referenced #Get Help discussion uses an extremely powerful `include_tasks` technique: _tail recursion_. However, the documentation for `include_tasks` contains no hint of the possibility of nor the considerable restrictions required by tail recursion.

[Apparently](https://stackoverflow.com/a/64117606/7496280) this became possible in Ansible 2.4, but I’m hesitant to rely on such critical but undocumented behavior in production.

---

<div class="post-metadata">

**Author:** ![tanganellilore](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/tanganellilore/32/178_2.png) [@tanganellilore](https://forum.ansible.com/u/tanganellilore)\
**Post date:** [November 12, 2023, 2:18pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/2 "2023-11-12T14:18:50Z")

</div>

Hi,

Recursion is possible in Ansible at minimum i last 4 year (when I start to use it) and can be used in complex use case like example above, but, like all Recursion can be very tricky to debug and Developer a good exit.  
Another you case can be the loop of loop of loop based on variable extract ed on second and third loop.

Like in python, in ansible you need to consider at least 2 key points:

1. Variable used in the innheirt loop.(for example it’s possible that you overright variable and no exit from recursor anytime)
2. Time execution of playbook. On ansible/awx you can have maximum timeout of jobs running (default is 0 but some best practice consider 8-24 hours better value) , so if you expect that Recursion will be executed lifetime, it’possible thst this will no happen

A lot of triky solution are not documented by ansible and awx team and I’m not judge them, documentation require a lot of time, revisione and so on.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [November 13, 2023, 4:43pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/3 "2023-11-13T16:43:47Z")

</div>

We’ll need to answer a few fundamental questions before we can get this documented. Here’s my short list:

1. Question for the Steering Committee: Is this an intended feature that will be carried into the future, or is this merely a side effect of the way ~~`import_tasks`~~ `include_tasks` is implemented which could disappear in a future release?

2. Does this work for any other flavors of `include_*` or `import_*`? If so, the scope of the question just got a bit creepier. (See what I did there? 🙂

3. What circumstances prevent tail recursion? ~~I’ve found by trial and error that having a `vars:` on the last task in the included task files’ last step is one such breaker.~~ [Edit: The error I incurred, “… maximum recursion depth exceeded”, was due to a variable defined in terms of itself, not that the final `include_tasks` step had a `vars:` section.] What else?

Other questions? Maybe someone who knows how it’s implemented would have additional insights…

Tail recursion is an “in-the-weeds” topic anyway. How do we provide enough technical detail to cover it while not overwhelming the `include_tasks` doc? Should it be split between the `include_tasks` doc and the `tips-n-tricks` page, or all in one place? Should an example be added the to `async:` doc (since async job queue management is a practical example of its application)?

---

<div class="post-metadata">

**Author:** ![samccann](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/samccann/32/18_2.png) [@samccann](https://forum.ansible.com/u/samccann)\
**Post date:** [November 13, 2023, 5:08pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/4 "2023-11-13T17:08:05Z")

</div>

Thanks for the clarification @utoddl!

So I wonder if the [@Core](https://forum.ansible.com/groups/core) team can pipe in here to verify that this is intentional (the answer to question 0 above)…

---

<div class="post-metadata">

**Author:** ![russoz](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/russoz/32/127_2.png) [@russoz](https://forum.ansible.com/u/russoz)\
**Post date:** [November 14, 2023, 2:43am UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/5 "2023-11-14T02:43:12Z")

</div>

hi @utoddl

I seen no reason for Ansible to preemptively restrict that use of `include_task`. However, I must say that using Ansible as a _programming language_ is not something I would be very keen about. To solve your problem, I would try and look into using `strategy` as @tanganellilore recommended in [Async task / queue management - #2 by tanganellilore](http://forum.ansible.com/t/async-task-queue-management/2296/2)

As of the documentation for `include_task` I reckon that recursion (whether it’s tail or not) could be added, but IMHO it should come with a big warning saying that although this is not restricted, one should be _ **extremely** _ careful doing that.

Just my $0.02

---

<div class="post-metadata">

**Author:** ![flowerysong](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/flowerysong/32/2305_2.png) [@flowerysong](https://forum.ansible.com/u/flowerysong)\
**Post date:** [November 14, 2023, 7:12am UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/6 "2023-11-14T07:12:00Z")

</div>

> [@utoddl](#):
>
> 1. Question for the Steering Committee: Is this an intended feature that will be carried into the future, or is this merely a side effect of the way `import_tasks` is implemented which could disappear in a future release?

You cannot do this type of recursive include with `import_tasks`, so I’m going to assume that’s a typo. Being able to write something structured this way is a consequence of the existence of `include_tasks`, not a side effect.

Includes are a documented feature, and changing how they work would be a major breaking change. If you’re unwilling to use features because they could change in the future, _technically_ any feature could disappear or be changed in a future release.

> [@utoddl](#):
>
> 1. Does this work for any other flavors of `include_*` or `import_*`? If so, the scope of the question just got a bit creepier. (See what I did there? 🙂

Yes, `include_role` can also be used recursively.

> [@utoddl](#):
>
> 1. What circumstances prevent tail recursion? I’ve found by trial and error that having a `vars:` on the last task in the included task files’ last step is one such breaker. What else?

There are no circumstances that prevent it other than exhausting resources (e.g. hitting Python’s recursion limit or running out of memory.) There are ways to write broken logic that doesn’t work the way you want it to, but that’s not a “circumstance”, that’s broken logic.

```yaml
- debug:
    msg: "{{ foo }}"

- set_fact:
    depth: "{{ depth | default(0) + 1 }}"

- include_tasks:
    file: recurs.yml
  vars:
    foo: baz
  when: depth < 3

```

```auto
PLAY [localhost] ***************************************************************

TASK [ansible.builtin.include_tasks] *******************************************
included: /home/ec2-user/testing/recurs.yml for localhost

TASK [debug] *******************************************************************
ok: [localhost] =>
    msg: bar

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [include_tasks] ***********************************************************
included: /home/ec2-user/testing/recurs.yml for localhost

TASK [debug] *******************************************************************
ok: [localhost] =>
    msg: baz

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [include_tasks] ***********************************************************
included: /home/ec2-user/testing/recurs.yml for localhost

TASK [debug] *******************************************************************
ok: [localhost] =>
    msg: baz

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [include_tasks] ***********************************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost : ok=9 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

```

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [November 14, 2023, 1:05pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/7 "2023-11-14T13:05:49Z")

</div>

> [@flowerysong](#):
>
> You cannot do this type of recursive include with `import_tasks`, so I’m going to assume that’s a typo.

Yes, it was. I meant `include_tasks`. I’ve edited [that post](http://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/3) to indicate such. Thanks for catching that.

> [@flowerysong](#):
>
> Being able to write something structured this way is a consequence of the existence of `include_tasks`, not a side effect.

In that case, it should be sufficient then to document how it can be used to achieve the same effect as `until:` would have on the initial `include_tasks` step if it were available.

Likewise for `include_role`.

My other concerns are moot. Thanks for the perspective.

---

<div class="post-metadata">

**Author:** ![samccann](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/samccann/32/18_2.png) [@samccann](https://forum.ansible.com/u/samccann)\
**Post date:** [November 14, 2023, 10:01pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/8 "2023-11-14T22:01:36Z")

</div>

I’m less sure whether we should change documentation here. While it seems it works, it also seems by one of the posts above that it’s tricky and not recommended (and thus shouldn’t be added to the docs)…

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [November 15, 2023, 1:54am UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/9 "2023-11-15T01:54:03Z")

</div>

I find the expressed concerns about trickyness amusing and entirely overblown. It’s nothing like so tricky as a Jinja2 expression with several pipes, or why you might choose an `include_tasks` with a `vars:` section over a `set_fact` (lazy evaluation later vs. current static values), or the behavior of tags between imports vs. includes, or the distinctions among all the different types of plugins, or strategies, or variable precedence… Without the tricky bits there wouldn’t be much documentation left!

It is precisely as tricky – no more, no less – than the task level `until:` keyword, which is not supported on `include_*` tasks for technical reasons. The user can achieve the exact same effect by adding a self-including `include_tasks` at the end of the included file that has a “`when: not (<condition we would have put on the 'until:' had it been supported>)`” on it.

_[We could have a separate reasonable discussion about implementing `until:` on includes by effectively adding such a task onto the end of included files, but that’s for another time.]_

The arguments in favor of documenting it are: (1) it’s actually a pretty clean, not-at-all tricky workaround for the lack of `until:` support on includes, (2) it solves real-world problems (otherwise why have `until:` at all?), and (3) it’s all but undiscoverable from the current documentation.

I’ve been kicking Ansible’s tires for almost 7 years. My mind was blown last week when I was shown this technique. That hasn’t happened in a while. It shouldn’t be surprising to someone who has read the documents as much as I have. This should be documented.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [November 15, 2023, 2:13pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/10 "2023-11-15T14:13:09Z")

</div>

First, tail recursion was not a ‘designed’ feature, it just happens when you allow recursive inclusion and combine that with how variables work in Ansible. That said, I’m not aware of any plans to remove this feature and we’ve known of it for a long time in core, we just didn’t feel like it was something that should be featured in documentation as that would encourage it’s usage. I’m also not aware of any explicit testing to prevent it from happening accidentally. I would advise you ensure these tests exist in core before you commit to this path, then feel free to document it.

As to why we didn’t want to encourage it’s usage, note that includes are pretty resource intensive, create a serialization of the hosts/tasks execution and have a recursion limit, which we’ve expanded it several times, so it is probable you crash for resource starvation than hitting it now. This is why we normally recommend using imports unless you reaaally need the dynamic nature of an include.

Ansible is designed for automation, not as a full programming language, yes, it can be used as such, but then don’t complain to the hammer manufacturer that the screws end up stripped.

Related proposal that would make most keywords work with includes (including until) ['taskify' includes · Issue #136 · ansible/proposals · GitHub](https://github.com/ansible/proposals/issues/136). There have also been efforts to deprecate until/retry as a 2nd loop and integrate it into the ‘loop’ structure [[WIP] loop\_control until feature by bcoca · Pull Request #62151 · ansible/ansible · GitHub](https://github.com/ansible/ansible/pull/62151).

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [November 15, 2023, 4:33pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/11 "2023-11-15T16:33:07Z")

</div>

Thanks for these insights, @bcoca . Lots to unpack.

> [@bcoca](#):
>
> First, **tail recursion** was not a ‘designed’ feature, it just happens when you allow recursive inclusion and combine that with how variables work in Ansible.

_[emphasis added]_ and

> [@bcoca](#):
>
> [N]ote that includes […] have a recursion limit […]

and

> [@bcoca](#):
>
> This is why we normally recommend using imports […]

This inspired me to go back and do some more thorough testing. The results are consistent with the comments above, and sobering.

**I owe everyone a huge apology for wasting your time.**

Includes do support recursion, but there is no optimization on tail calls. Further, I had been using the term “tail recursion” incorrectly, assuming tail call optimization was implied. It is not.

Using include file tail recursion as a workaround for the missing “`until:`” support on includes is a non-starter. It may appear to work at first, but it blows its stack at about 250 or so iterations (empirically determined on core 2.14.11, subject to change, etc.).

I am truly sorry, on about 250 levels.

---

<div class="post-metadata">

**Author:** ![samccann](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/samccann/32/18_2.png) [@samccann](https://forum.ansible.com/u/samccann)\
**Post date:** [November 15, 2023, 6:47pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/12 "2023-11-15T18:47:17Z")

</div>

No need to apologize @utoddl ! If you had the questions/perceptions, guaranteed others have and will have in the future. Now we have a topic that covers it in detail and allows others to learn. Great discussion imo.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [November 15, 2023, 11:50pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/13 "2023-11-15T23:50:40Z")

</div>

@utoddl not a waste of time, even if it was … it would be the fault of those that did not document how this works and why you should not do it (me).

This is still a useful ‘trick’ for some hairy situations, but as you saw, must be used with limits and care or things explode.

I’m tempted to add a ‘here be dragons’ section to the docs and start documenting this and other ‘feature traps’ that exist in Ansible. At this point too many intelligent and curious people use it as to be able to hide/ignore this kind of thing. Worse yet, they might start using w/o knowing the reason why we ignored/kept quiet about it.

---

<div class="post-metadata">

**Author:** ![sirjaren](https://avatars.discourse-cdn.com/v4/letter/s/c2a13f/32.png) [@sirjaren](https://forum.ansible.com/u/sirjaren)\
**Post date:** [November 16, 2023, 5:12pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/14 "2023-11-16T17:12:50Z")

</div>

Anytime subtle tricks are exposed, you’ll find the curious making use of them.  
I know I have. 😄

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [November 16, 2023, 5:46pm UTC](https://forum.ansible.com/t/include-tasks-docs-lack-info-on-tail-recursion/2306/15 "2023-11-16T17:46:53Z")

</div>

I suspect that is why I was hired by Ansible inc … to stop exposing such tricks as an outsider and help preemptively patch them as an insider …
