From b5861b1e27163c1b2d6bd5307562aa488ea34b58 Mon Sep 17 00:00:00 2001 From: qingyingliu <37481138+Lewin671@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:48:47 -0700 Subject: [PATCH] fix: reject include without taskfile or dir (#2892) Co-authored-by: Valentin Maerten --- task_test.go | 19 +++++++++++++++++++ taskfile/ast/include.go | 4 ++++ .../includes_missing_taskfile/Taskfile.yml | 5 +++++ website/src/public/schema.json | 16 +++++++++++++--- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 testdata/includes_missing_taskfile/Taskfile.yml diff --git a/task_test.go b/task_test.go index db2417a0..dd92f8ee 100644 --- a/task_test.go +++ b/task_test.go @@ -1240,6 +1240,25 @@ func TestIncludesIncorrect(t *testing.T) { assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error()) } +func TestIncludesMissingTaskfile(t *testing.T) { + t.Parallel() + + const dir = "testdata/includes_missing_taskfile" + + var buff bytes.Buffer + e := task.NewExecutor( + task.WithDir(dir), + task.WithStdout(&buff), + task.WithStderr(&buff), + task.WithSilent(true), + ) + + err := e.Setup() + require.Error(t, err) + assert.Contains(t, err.Error(), "include must specify taskfile or dir") + assert.NotContains(t, err.Error(), "include cycle detected") +} + func TestIncludesEmptyMain(t *testing.T) { t.Parallel() diff --git a/taskfile/ast/include.go b/taskfile/ast/include.go index 18090b66..64432c19 100644 --- a/taskfile/ast/include.go +++ b/taskfile/ast/include.go @@ -2,6 +2,7 @@ package ast import ( "iter" + "strings" "sync" "github.com/elliotchance/orderedmap/v3" @@ -171,6 +172,9 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error { if err := node.Decode(&includedTaskfile); err != nil { return errors.NewTaskfileDecodeError(err, node) } + if strings.TrimSpace(includedTaskfile.Taskfile) == "" && strings.TrimSpace(includedTaskfile.Dir) == "" { + return errors.NewTaskfileDecodeError(nil, node).WithMessage("include must specify taskfile or dir") + } include.Taskfile = includedTaskfile.Taskfile include.Dir = includedTaskfile.Dir include.Optional = includedTaskfile.Optional diff --git a/testdata/includes_missing_taskfile/Taskfile.yml b/testdata/includes_missing_taskfile/Taskfile.yml new file mode 100644 index 00000000..1832c2ee --- /dev/null +++ b/testdata/includes_missing_taskfile/Taskfile.yml @@ -0,0 +1,5 @@ +version: '3' + +includes: + GOBIN: + sh: echo $(go env GOPATH)/bin diff --git a/website/src/public/schema.json b/website/src/public/schema.json index 0a814bb4..df0637b7 100644 --- a/website/src/public/schema.json +++ b/website/src/public/schema.json @@ -718,11 +718,13 @@ "properties": { "taskfile": { "description": "The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile.", - "type": "string" + "type": "string", + "minLength": 1 }, "dir": { "description": "The working directory of the included tasks when run.", - "type": "string" + "type": "string", + "minLength": 1 }, "optional": { "description": "If `true`, no errors will be thrown if the specified file does not exist.", @@ -758,7 +760,15 @@ "description": "The checksum of the file you expect to include. If the checksum does not match, the file will not be included.", "type": "string" } - } + }, + "anyOf": [ + { + "required": ["taskfile"] + }, + { + "required": ["dir"] + } + ] } ] }