# Buggy code
def run(self, play):
    """
    Iterates over the roles/tasks in a play, using the given (or default)
    strategy for queueing tasks. The default is the linear strategy, which
    operates like classic Ansible by keeping all hosts in lock-step with
    a given task (meaning no hosts move on to the next task until all hosts
    are done with the current task).
    """

    if not self._callbacks_loaded:
        self.load_callbacks()

    all_vars = self._variable_manager.get_vars(loader=self._loader, play=play)
    templar = Templar(loader=self._loader, variables=all_vars)

    new_play = play.copy()
    new_play.post_validate(templar)
    new_play.handlers = new_play.compile_roles_handlers() + new_play.handlers

    self.hostvars = HostVars(
        inventory=self._inventory,
        variable_manager=self._variable_manager,
        loader=self._loader,
    )

    # Fork # of forks, # of hosts or serial, whichever is lowest
    num_hosts = len(self._inventory.get_hosts(new_play.hosts))

    max_serial = 0
    if play.serial:
        # the play has not been post_validated here, so we may need
        # to convert the scalar value to a list at this point
        serial_items = play.serial
        if not isinstance(serial_items, list):
            serial_items = [serial_items]
        max_serial = max([pct_to_int(x, num_hosts) for x in serial_items])

    contenders = [self._options.forks, max_serial, num_hosts]
    contenders = [v for v in contenders if v is not None and v > 0]
    self._initialize_processes(min(contenders))

    play_context = PlayContext(
        new_play, self._options, self.passwords, self._connection_lockfile.fileno()
    )
    for callback_plugin in self._callback_plugins:
        if hasattr(callback_plugin, "set_play_context"):
            callback_plugin.set_play_context(play_context)

    self.send_callback("v2_playbook_on_play_start", new_play)

    # initialize the shared dictionary containing the notified handlers
    self._initialize_notified_handlers(new_play)

    # load the specified strategy (or the default linear one)
    strategy = strategy_loader.get(new_play.strategy, self)
    if strategy is None:
        raise AnsibleError(
            "Invalid play strategy specified: %s" % new_play.strategy, obj=play._ds
        )

    # build the iterator
    iterator = PlayIterator(
        inventory=self._inventory,
        play=new_play,
        play_context=play_context,
        variable_manager=self._variable_manager,
        all_vars=all_vars,
        start_at_done=self._start_at_done,
    )

    # Because the TQM may survive multiple play runs, we start by marking
    # any hosts as failed in the iterator here which may have been marked
    # as failed in previous runs. Then we clear the internal list of failed
    # hosts so we know what failed this round.
    for host_name in self._failed_hosts.keys():
        host = self._inventory.get_host(host_name)
        iterator.mark_host_failed(host)

    self.clear_failed_hosts()

    # during initialization, the PlayContext will clear the start_at_task
    # field to signal that a matching task was found, so check that here
    # and remember it so we don't try to skip tasks on future plays
    if (
        getattr(self._options, "start_at_task", None) is not None
        and play_context.start_at_task is None
    ):
        self._start_at_done = True

    # and run the play using the strategy and cleanup on way out
    play_return = strategy.run(iterator, play_context)

    # now re-save the hosts that failed from the iterator to our internal list
    for host_name in iterator.get_failed_hosts():
        self._failed_hosts[host_name] = True

    self._cleanup_processes()
    return play_return


# Traceback (ValueError)
(ansibleenv) R1+mitchell.usher@server[~/ansible/CatalystConfig/tasks/install-ruby-2]: ansible-playbook inst
all.yml -i 'localhost,' -e 'hosts=all ruby_version=2.1.3 force_install=yes' -kK -vvv
Using /home/R1/mitchell.usher/.ansible.cfg as config file
SSH password:
SUDO password[defaults to SSH password]:
statically included: /home/R1/mitchell.usher/ansible/CatalystConfig/tasks/install-ruby-2/install-ruby.yml

PLAYBOOK: install.yml **********************************************************
1 plays in install.yml
ERROR! Unexpected Exception: invalid literal for int() with base 10: '{{ serial | default(10) }}'
the full traceback was:

Traceback (most recent call last):
File "/home/R1/mitchell.usher/ansible/ansible/bin/ansible-playbook", line 97, in <module>
exit_code = cli.run()
File "/home/R1/mitchell.usher/ansible/ansible/lib/ansible/cli/playbook.py", line 154, in run
results = pbex.run()
File "/home/R1/mitchell.usher/ansible/ansible/lib/ansible/executor/playbook_executor.py", line 147, in run
result = self._tqm.run(play=play)
File "/home/R1/mitchell.usher/ansible/ansible/lib/ansible/executor/task_queue_manager.py", line 233, in run
max_serial = max([pct_to_int(x, num_hosts) for x in serial_items])
File "/home/R1/mitchell.usher/ansible/ansible/lib/ansible/utils/helpers.py", line 33, in pct_to_int
return int(value)
ValueError: invalid literal for int() with base 10: '{{ serial | default(10) }}'

# Fixed code
def run(self, play):
    """
    Iterates over the roles/tasks in a play, using the given (or default)
    strategy for queueing tasks. The default is the linear strategy, which
    operates like classic Ansible by keeping all hosts in lock-step with
    a given task (meaning no hosts move on to the next task until all hosts
    are done with the current task).
    """

    if not self._callbacks_loaded:
        self.load_callbacks()

    all_vars = self._variable_manager.get_vars(loader=self._loader, play=play)
    templar = Templar(loader=self._loader, variables=all_vars)

    new_play = play.copy()
    new_play.post_validate(templar)
    new_play.handlers = new_play.compile_roles_handlers() + new_play.handlers

    self.hostvars = HostVars(
        inventory=self._inventory,
        variable_manager=self._variable_manager,
        loader=self._loader,
    )

    # Fork # of forks, # of hosts or serial, whichever is lowest
    num_hosts = len(self._inventory.get_hosts(new_play.hosts))

    max_serial = 0
    if new_play.serial:
        # the play has not been post_validated here, so we may need
        # to convert the scalar value to a list at this point
        serial_items = new_play.serial
        if not isinstance(serial_items, list):
            serial_items = [serial_items]
        max_serial = max([pct_to_int(x, num_hosts) for x in serial_items])

    contenders = [self._options.forks, max_serial, num_hosts]
    contenders = [v for v in contenders if v is not None and v > 0]
    self._initialize_processes(min(contenders))

    play_context = PlayContext(
        new_play, self._options, self.passwords, self._connection_lockfile.fileno()
    )
    for callback_plugin in self._callback_plugins:
        if hasattr(callback_plugin, "set_play_context"):
            callback_plugin.set_play_context(play_context)

    self.send_callback("v2_playbook_on_play_start", new_play)

    # initialize the shared dictionary containing the notified handlers
    self._initialize_notified_handlers(new_play)

    # load the specified strategy (or the default linear one)
    strategy = strategy_loader.get(new_play.strategy, self)
    if strategy is None:
        raise AnsibleError(
            "Invalid play strategy specified: %s" % new_play.strategy, obj=play._ds
        )

    # build the iterator
    iterator = PlayIterator(
        inventory=self._inventory,
        play=new_play,
        play_context=play_context,
        variable_manager=self._variable_manager,
        all_vars=all_vars,
        start_at_done=self._start_at_done,
    )

    # Because the TQM may survive multiple play runs, we start by marking
    # any hosts as failed in the iterator here which may have been marked
    # as failed in previous runs. Then we clear the internal list of failed
    # hosts so we know what failed this round.
    for host_name in self._failed_hosts.keys():
        host = self._inventory.get_host(host_name)
        iterator.mark_host_failed(host)

    self.clear_failed_hosts()

    # during initialization, the PlayContext will clear the start_at_task
    # field to signal that a matching task was found, so check that here
    # and remember it so we don't try to skip tasks on future plays
    if (
        getattr(self._options, "start_at_task", None) is not None
        and play_context.start_at_task is None
    ):
        self._start_at_done = True

    # and run the play using the strategy and cleanup on way out
    play_return = strategy.run(iterator, play_context)

    # now re-save the hosts that failed from the iterator to our internal list
    for host_name in iterator.get_failed_hosts():
        self._failed_hosts[host_name] = True

    self._cleanup_processes()
    return play_return