When a parallel task fails allow the other parallel tasks to finish before exiting with failure · Issue #1246 · go-task/task · GitHub
Skip to content

When a parallel task fails allow the other parallel tasks to finish before exiting with failure #1246

Description

@scheuk

Currently when a parallel task fails, Taskfile will kill the other running process.
If you have a running a long-running task like terraform, this will break processes and might leave the task in a bad state.

We should have a config or flag to allow the parallel tasks to complete before quitting with error from the failed task.

this is similar to:
#1226

Activity

  1. supersmile2009 commented on Sep 19, 2023

    @supersmile2009

    This is definitely a must have. I'm currently considering an alternative to Just (a competing tool) because it doesn't support parallelization. And it's great the Task does support it, however the implementation without waiting for the remaining tasks to complete is not acceptable to me.

    I'd also like to mention, that the best approach IMO would be to default to waiting for all the tasks to finish and only kill the remaining tasks (fail fast) when explicitly requested by user (e.g. via a CLI flag or a task definition option). At the end of the day Task is not a build tool like Make, but a task runner. Killing tasks mid-execution can have detrimental effects as already mentioned in the original post (e.g. you likely don't want to kill terraform apply), this should not be the default behavior.

    This is also how various test frameworks capable of test parallelization usually work, waiting for all the tests to complete before existing and presenting you with the aggregated report unless instructed otherwise. So it's not an entirely new concept. In fact, most of the tools doing some sort of parallelization, in my experience fail fast only when you explicitly ask them to do so.

  2. KuttKatrea commented on Jan 6, 2024

    @KuttKatrea

    I agree with @supersmile2009 that the expected behavior is to finish running the deps/parallel tasks.
    Fail-fast could be included as other options (like watch) as both a configuration:

    task-d:
      deps: [ task-a, task-b, task-c]
      failfast: true

    and a command line option:

    task --failfast task-d
    
  3. andrewimeson commented on Jan 16, 2024

    @andrewimeson

    My ugly workaround is to touch a file if there's an error, and then at the end exit nonzero if that file exists. That means I have to clean up that file before starting another run, and it's pretty ugly, but it's a better experience than seeing a stacktrace generated from a program that got sigkilled because another task failed.

    Here's a simplified example:

    ---
    version: 3
    env:
      VENV: .venv
      VENV_ACTIVATE: '{{ .VENV }}/bin/activate'
      ANS_ENV: .ansible
      # TODO: Remove this once mvdan's Go sh interpreter auto-sets OSTYPE, which
      # later versions of Python depend upon being set in the activate script, which
      # breaks with `set -u` enforced.
      #   https://github.com/mvdan/sh/pull/912
      OSTYPE:
        sh: bash -c 'echo $OSTYPE'
      TASK_ISSUE_FILE: .task/had-issue
    # Shell options
    set:
      - u  # Fail on unset variable reference
    tasks:
      _clean_lint_state:
        internal: true
        silent: true
        cmds:
          - rm -f {{ .TASK_ISSUE_FILE }} || true
    
      _ansible_lint:
        internal: true
        deps:
          - init
        cmds:
          - name: ansible-lint playbooks
            cmd: |
              ansible-lint --force-color || touch {{ .TASK_ISSUE_FILE }}
    
      _yamllint:
        internal: true
        deps:
          - init
        cmds:
          - name: Lint YAML files with yamllint
            cmd: |
              yamllint --strict $(git ls-files '*.yaml' '*.yml') ||
                touch {{ .TASK_ISSUE_FILE }}
    
      init:
        desc: Initialize and install dependencies
        run: once
        deps:
          - _clean_lint_state
    
      lint:
        desc: Run linters to check code for style
        deps:
          - _yamllint
          - _ansible_lint
        cmds:
          - '[[ ! -f {{ .TASK_ISSUE_FILE }} ]]'
  4. bennieswart commented on Nov 12, 2025

    @bennieswart

    My use case is very similar to @andrewimeson's. I have a lint aggregate task which depends on a bunch of individual lint tasks (yamllint, eslint etc).

    Consider the following tasks: lint:eslint, lint:yamllint and lint (which depends on the other two). I would like all lint:* tasks to run, and run to completion, even if one of them fails early. The failure should also be reflected in the exit code when running lint.

    Without any workarounds:
    ❌ If one lint:* task fails, the others are killed and their outputs are never displayed.
    ✔️ The exit code correctly reflects the failure.

    The only way that I could find to prevent a failing task from killing others is to use ignore_error: true on the task who's failure shouldn't kill other tasks. With ignore_error: true on all lint:* tasks:
    ✔️ Failing tasks no longer kill other tasks.
    ❌ The exit code of the aggregate lint task as well as any individual lint:* task does not reflect the failure.

    I don't think ignore_error: true on lint:* tasks is the correct approach, as those should be runnable by themselves and the exit code should reflect any failures. But without ignore_error: true, it's a bloodbath once the first task fails.

    There are quite a few comments in the issues where people express the need for the ability to prevent a task from being killed mid-execution. Those comments are typically geared towards long-running tasks, but I think the solution might be the same.

    A very easy solution is probably just to mark a task as being shielded from killing. On failure, shielded tasks are allowed to complete and then the exit code reflects whatever the first failure was.

    Not killing tasks is one thing, but another thing is to ensure that all tasks have had the opportunity to run. This is more relevant to the case of linters. In other cases, you might not care that subsequent tasks have never run if prior tasks have failed, as long as you don't kill something mid-execution. When linting, you want everything to run, and run to completion.

  5. andreynering commented on Nov 22, 2025

    @andreynering
    Member

    Hey everybody!

    I just opened a PR to implement a setting for this.

    We're basically changing the default behavior to always wait, and adding failfast: true or --failfast to opt for the previous behavior (stop once any fail).

    Please give that branch a try if you can and let me know if it works well for you.

  6. elvism-lullabot commented on Nov 24, 2025

    @elvism-lullabot

    great addition, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions