From 94f82cbc5a95b23da55f7fbc5476af09a7f7aa50 Mon Sep 17 00:00:00 2001 From: Graham Dennis Date: Tue, 11 Nov 2025 11:40:40 -0800 Subject: [PATCH] fix: make task failure errors include stack of running tasks (#2286) Previously if a task was run as a dependency of another task, the error message simply reported something like: exit status 1 It is desirable instead to name the root task and all child tasks in the tree to the failing task. After this PR, the error message will read: task: Failed to run task "root": task: Failed to run task "failing-task": exit status 1 --- errors/errors_task.go | 4 ++++ executor_test.go | 9 +++++++++ task.go | 14 +++++++------- task_test.go | 4 +++- testdata/label_error/Taskfile.yml | 7 +++++++ .../TestLabel-label_in_error-err-run.golden | 1 + .../testdata/TestLabel-label_in_error.golden | 1 + ...ition-a_precondition_was_not_met-err-run.golden | 2 +- ...econdition_in_cmd_fails_the_task-err-run.golden | 2 +- ...ion_in_dependency_fails_the_task-err-run.golden | 2 +- ..._raise_errors.TaskCancelledError-err-run.golden | 2 +- ...stops_task-test_Enter_stops_task-err-run.golden | 2 +- ..._task-test_junk_value_stops_task-err-run.golden | 2 +- ...-test_stops_task-test_stops_task-err-run.golden | 2 +- 14 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 testdata/label_error/Taskfile.yml create mode 100644 testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden create mode 100644 testdata/label_error/testdata/TestLabel-label_in_error.golden diff --git a/errors/errors_task.go b/errors/errors_task.go index 816d8230..f588f13e 100644 --- a/errors/errors_task.go +++ b/errors/errors_task.go @@ -54,6 +54,10 @@ func (err *TaskRunError) TaskExitCode() int { return err.Code() } +func (err *TaskRunError) Unwrap() error { + return err.Err +} + // TaskInternalError when the user attempts to invoke a task that is internal. type TaskInternalError struct { TaskName string diff --git a/executor_test.go b/executor_test.go index 4b9a42cd..7f6a3feb 100644 --- a/executor_test.go +++ b/executor_test.go @@ -665,6 +665,15 @@ func TestLabel(t *testing.T) { ), WithTask("foo"), ) + + NewExecutorTest(t, + WithName("label in error"), + WithExecutorOptions( + task.WithDir("testdata/label_error"), + ), + WithTask("foo"), + WithRunError(), + ) } func TestPromptInSummary(t *testing.T) { diff --git a/task.go b/task.go index 4162e4c5..0afe2215 100644 --- a/task.go +++ b/task.go @@ -150,7 +150,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error { release := e.acquireConcurrencyLimit() defer release() - return e.startExecution(ctx, t, func(ctx context.Context) error { + if err = e.startExecution(ctx, t, func(ctx context.Context) error { e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task) if err := e.runDeps(ctx, t); err != nil { return err @@ -228,16 +228,16 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error { deferredExitCode = uint8(exitCode) } - if call.Indirect { - return err - } - - return &errors.TaskRunError{TaskName: t.Task, Err: err} + return err } } e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) return nil - }) + }); err != nil { + return &errors.TaskRunError{TaskName: t.Name(), Err: err} + } + + return nil } func (e *Executor) mkdir(t *ast.Task) error { diff --git a/task_test.go b/task_test.go index 5b543171..68d5128a 100644 --- a/task_test.go +++ b/task_test.go @@ -569,7 +569,9 @@ func TestCyclicDep(t *testing.T) { task.WithStderr(io.Discard), ) require.NoError(t, e.Setup()) - assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(t.Context(), &task.Call{Task: "task-1"})) + err := e.Run(t.Context(), &task.Call{Task: "task-1"}) + var taskCalledTooManyTimesError *errors.TaskCalledTooManyTimesError + assert.ErrorAs(t, err, &taskCalledTooManyTimesError) } func TestTaskVersion(t *testing.T) { diff --git a/testdata/label_error/Taskfile.yml b/testdata/label_error/Taskfile.yml new file mode 100644 index 00000000..fe795ebd --- /dev/null +++ b/testdata/label_error/Taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + foo: + label: "foobar" + cmds: + - "false" diff --git a/testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden b/testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden new file mode 100644 index 00000000..67e166bc --- /dev/null +++ b/testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden @@ -0,0 +1 @@ +task: Failed to run task "foobar": exit status 1 \ No newline at end of file diff --git a/testdata/label_error/testdata/TestLabel-label_in_error.golden b/testdata/label_error/testdata/TestLabel-label_in_error.golden new file mode 100644 index 00000000..9ac0e341 --- /dev/null +++ b/testdata/label_error/testdata/TestLabel-label_in_error.golden @@ -0,0 +1 @@ +task: [foobar] false diff --git a/testdata/precondition/testdata/TestPrecondition-a_precondition_was_not_met-err-run.golden b/testdata/precondition/testdata/TestPrecondition-a_precondition_was_not_met-err-run.golden index 6952491e..839c74ba 100644 --- a/testdata/precondition/testdata/TestPrecondition-a_precondition_was_not_met-err-run.golden +++ b/testdata/precondition/testdata/TestPrecondition-a_precondition_was_not_met-err-run.golden @@ -1 +1 @@ -task: precondition not met \ No newline at end of file +task: Failed to run task "impossible": task: precondition not met \ No newline at end of file diff --git a/testdata/precondition/testdata/TestPrecondition-precondition_in_cmd_fails_the_task-err-run.golden b/testdata/precondition/testdata/TestPrecondition-precondition_in_cmd_fails_the_task-err-run.golden index 7e115563..14ae8d12 100644 --- a/testdata/precondition/testdata/TestPrecondition-precondition_in_cmd_fails_the_task-err-run.golden +++ b/testdata/precondition/testdata/TestPrecondition-precondition_in_cmd_fails_the_task-err-run.golden @@ -1 +1 @@ -task: Failed to run task "executes_failing_task_as_cmd": task: precondition not met \ No newline at end of file +task: Failed to run task "executes_failing_task_as_cmd": task: Failed to run task "impossible": task: precondition not met \ No newline at end of file diff --git a/testdata/precondition/testdata/TestPrecondition-precondition_in_dependency_fails_the_task-err-run.golden b/testdata/precondition/testdata/TestPrecondition-precondition_in_dependency_fails_the_task-err-run.golden index 6952491e..a4ee7b3f 100644 --- a/testdata/precondition/testdata/TestPrecondition-precondition_in_dependency_fails_the_task-err-run.golden +++ b/testdata/precondition/testdata/TestPrecondition-precondition_in_dependency_fails_the_task-err-run.golden @@ -1 +1 @@ -task: precondition not met \ No newline at end of file +task: Failed to run task "depends_on_impossible": task: Failed to run task "impossible": task: precondition not met \ No newline at end of file diff --git a/testdata/prompt/testdata/TestPromptAssumeYes-task_should_raise_errors.TaskCancelledError-err-run.golden b/testdata/prompt/testdata/TestPromptAssumeYes-task_should_raise_errors.TaskCancelledError-err-run.golden index 8e264302..a2e7b643 100644 --- a/testdata/prompt/testdata/TestPromptAssumeYes-task_should_raise_errors.TaskCancelledError-err-run.golden +++ b/testdata/prompt/testdata/TestPromptAssumeYes-task_should_raise_errors.TaskCancelledError-err-run.golden @@ -1 +1 @@ -task: Task "foo" cancelled by user \ No newline at end of file +task: Failed to run task "foo": task: Task "foo" cancelled by user \ No newline at end of file diff --git a/testdata/prompt/testdata/TestPromptInSummary-test_Enter_stops_task-test_Enter_stops_task-err-run.golden b/testdata/prompt/testdata/TestPromptInSummary-test_Enter_stops_task-test_Enter_stops_task-err-run.golden index 8e264302..a2e7b643 100644 --- a/testdata/prompt/testdata/TestPromptInSummary-test_Enter_stops_task-test_Enter_stops_task-err-run.golden +++ b/testdata/prompt/testdata/TestPromptInSummary-test_Enter_stops_task-test_Enter_stops_task-err-run.golden @@ -1 +1 @@ -task: Task "foo" cancelled by user \ No newline at end of file +task: Failed to run task "foo": task: Task "foo" cancelled by user \ No newline at end of file diff --git a/testdata/prompt/testdata/TestPromptInSummary-test_junk_value_stops_task-test_junk_value_stops_task-err-run.golden b/testdata/prompt/testdata/TestPromptInSummary-test_junk_value_stops_task-test_junk_value_stops_task-err-run.golden index 8e264302..a2e7b643 100644 --- a/testdata/prompt/testdata/TestPromptInSummary-test_junk_value_stops_task-test_junk_value_stops_task-err-run.golden +++ b/testdata/prompt/testdata/TestPromptInSummary-test_junk_value_stops_task-test_junk_value_stops_task-err-run.golden @@ -1 +1 @@ -task: Task "foo" cancelled by user \ No newline at end of file +task: Failed to run task "foo": task: Task "foo" cancelled by user \ No newline at end of file diff --git a/testdata/prompt/testdata/TestPromptInSummary-test_stops_task-test_stops_task-err-run.golden b/testdata/prompt/testdata/TestPromptInSummary-test_stops_task-test_stops_task-err-run.golden index 8e264302..a2e7b643 100644 --- a/testdata/prompt/testdata/TestPromptInSummary-test_stops_task-test_stops_task-err-run.golden +++ b/testdata/prompt/testdata/TestPromptInSummary-test_stops_task-test_stops_task-err-run.golden @@ -1 +1 @@ -task: Task "foo" cancelled by user \ No newline at end of file +task: Failed to run task "foo": task: Task "foo" cancelled by user \ No newline at end of file