Fix: Taskfile Not Working — Variables, Sources/Generates, Includes, Watch, and Run-Once Semantics
Quick Answer
How to fix Task (go-task) errors — Taskfile.yml not found, vars interpolation, sources/generates fingerprint, includes scoping, watch mode glob, deps parallel execution, and run: once preventing reruns.
The Error
You run task in your project and it complains there’s no Taskfile:
$ task build
task: No Taskfile found. Use "task --init" to create a new oneOr a variable interpolation produces empty output:
# Taskfile.yml
version: '3'
vars:
BIN: ./bin/myapp
tasks:
build:
cmds:
- go build -o {{.BIN}} ./cmd/...$ task build
go build -o ./cmd/...
# {{.BIN}} expanded to nothing — empty binary path.Or a task with sources runs every time even when nothing changed:
tasks:
build:
sources:
- "**/*.go"
generates:
- ./bin/myapp
cmds:
- go build -o ./bin/myapp ./cmd/...$ task build # Runs go build
$ task build # Runs go build AGAIN — should skipOr task watch doesn’t pick up file changes:
$ task --watch build
# Edit src/main.go → no rebuild.Why This Happens
Task (the go-task/task project) is a YAML-based task runner. Most issues map to one of:
Taskfile.ymllocation. Task walks up from the working directory looking forTaskfile.yml,taskfile.yml,Taskfile.yaml, ortaskfile.yaml. Misnamed files (Taskfile.YML, Taskfile.yaml.bak) are ignored.- Variable scope. Vars defined in
vars:at the file level are global. Task-localvars:shadow them. Env vars are separate (env:). Mixing them up produces empty interpolations. sources/generatesuse modification time + checksum. Atask --statuscheck compares timestamps and contents. If your “generated” file changes for unrelated reasons (formatting), Task sees it as stale.- Watch mode triggers on
sourceschanges, not arbitrary files. Without listing the rightsources, watch won’t restart.
Fix 1: Name the File Correctly
Task accepts these names (case-sensitive on Linux):
Taskfile.ymlTaskfile.yamltaskfile.ymltaskfile.yamlTaskfile.dist.yml(a “default” that can be overridden by a local Taskfile)
Run task --list to verify Task can see your file:
$ task --list
task: Available tasks for this project:
* build: Build the project
* test: Run testsIf the list is empty or “No Taskfile found”, check the working directory and filename.
For Taskfiles in subdirectories:
task -d ./build/Taskfile.yml build
# or:
task --taskfile ./build/Taskfile.yml buildPro Tip: Always include version: '3' at the top of Taskfile.yml. Older version: '2' syntax is deprecated but still parses with subtle differences.
Fix 2: Variables and Templating
Task uses Go’s text/template syntax. Vars are accessed with {{.VarName}}:
version: '3'
vars:
APP_NAME: myapp
VERSION:
sh: git describe --tags --always # Compute from shell
BIN: "./bin/{{.APP_NAME}}" # Reference other vars
tasks:
build:
cmds:
- go build -ldflags "-X main.version={{.VERSION}}" -o {{.BIN}} ./cmd/...
vars:
LOCAL_VAR: hello # Task-scoped, only inside this taskThree sources of values (lowest to highest precedence):
vars:at file level.vars:at task level.--<var-name>=valueCLI args.
To pass at runtime:
task build VERSION=1.2.3For env vars (vs Task vars):
env:
CGO_ENABLED: "0"
GOOS: "linux"
tasks:
build:
env:
GOARCH: "amd64" # Task-scoped env
cmds:
- go build -o ./bin/app ./cmd/...Env vars are exported to the shell that runs cmds. Task vars are template-expanded before the shell sees them.
Common Mistake: {{.PATH}} inside a cmd. Task’s templating happens first, replacing it with Task’s idea of PATH. If you want the shell’s $PATH, use $PATH (shell expansion):
cmds:
- echo $PATH # Shell expands this
- echo {{.PATH}} # Task tries to template-expand .PATH firstFix 3: sources/generates for Incremental Builds
tasks:
build:
sources:
- "**/*.go"
- "go.mod"
- "go.sum"
generates:
- "./bin/myapp"
cmds:
- go build -o ./bin/myapp ./cmd/...Task computes a checksum of sources and stores it in .task/checksum/. If checksums match the last successful run and generates files still exist, the task is skipped.
To force a run:
task build --forceTo check status without running:
task build --status
# Prints whether the task is up-to-date (exit 0) or stale (non-zero).For tasks that don’t produce a file but should still run-once based on inputs:
tasks:
install-deps:
sources:
- "package.json"
- "package-lock.json"
cmds:
- npm install
method: checksum # or "timestamp" (faster, less reliable)method: checksum (default) reads file contents — accurate but slower for many files. method: timestamp uses mtime — faster but breaks if your editor “touches” files.
Pro Tip: Pair sources with generates for outputs. If generates is empty, Task only knows whether sources changed, not whether the output exists. Without generates, deleting your ./bin/myapp doesn’t trigger a rebuild.
Fix 4: Dependencies Run in Parallel
deps: lists tasks to run before this one — in parallel:
tasks:
release:
deps: [test, lint, build]
cmds:
- ./scripts/release.shtest, lint, and build run concurrently. release.sh runs only after all three succeed.
For sequential execution, use cmds: - task: <name>:
tasks:
release:
cmds:
- task: test
- task: lint
- task: build
- ./scripts/release.shThe four steps run in order; any failure stops the chain.
For mixed parallel + sequential:
tasks:
ci:
deps: [test, lint] # Parallel
cmds:
- task: build # Sequential after deps
- task: publish # Sequential after buildFix 5: Includes for Modular Taskfiles
For large projects, split into multiple Taskfiles:
# Taskfile.yml (root)
version: '3'
includes:
frontend:
taskfile: ./frontend/Taskfile.yml
dir: ./frontend
backend:
taskfile: ./backend/Taskfile.yml
dir: ./backend
tasks:
build:
deps: [frontend:build, backend:build]dir: sets the working directory for tasks inside the included file. Without it, included tasks run from the root.
To run from the root:
task frontend:build
task backend:testFor internal-only tasks (not callable from root):
includes:
internal:
taskfile: ./scripts/Taskfile.yml
internal: trueinternal: true hides the included tasks from task --list output but they’re still callable internally.
Common Mistake: Forgetting dir: on an included file. Tasks inside it then run from the parent’s directory, breaking relative paths in commands.
Fix 6: Watch Mode
task build --watchWatch re-runs the task whenever sources change. Without explicit sources, watch does nothing useful:
tasks:
test:
sources:
- "**/*.go"
- "**/*_test.go"
cmds:
- go test ./...task test --watch
# Edits to any .go file trigger re-test.For a persistent watch task, write a wrapper:
tasks:
dev:
desc: "Run tests in watch mode"
cmds:
- task: test
vars: { WATCH: "1" }
watch: true # Forces watch even without --watch flag
test:
sources:
- "**/*.go"
cmds:
- go test ./...watch: true at the task level makes watch the default for that task.
Pro Tip: Combine watch with clear: true to wipe the terminal between runs:
tasks:
dev:
sources: ["**/*.go"]
cmds:
- clear
- go test ./...
watch: trueFix 7: Preconditions and status
Block a task from running unless conditions are met:
tasks:
deploy-prod:
preconditions:
- sh: "[ \"$ENV\" = \"production\" ]"
msg: "Set ENV=production to deploy to prod"
- sh: "git diff --quiet"
msg: "Working tree is dirty. Commit changes first."
cmds:
- ./scripts/deploy.shpreconditions run before the task. Each sh: is a shell command; non-zero exit blocks the task with the msg.
For “skip if already up-to-date” semantics (different from sources):
tasks:
install:
status:
- test -f ./bin/myapp
cmds:
- go install ./cmd/...status: runs each command; if all succeed (exit 0), the task is considered up-to-date and skipped.
status vs sources:
sources— fingerprint of inputs. Re-run when inputs change.status— arbitrary check. Re-run when status check fails.
Use sources for build artifacts; status for things like “is this tool installed” or “is the file populated”.
Fix 8: Run-Once Semantics
For tasks that should run at most once per Task invocation (even if listed in multiple deps):
tasks:
setup:
run: once
cmds:
- ./scripts/setup.sh
build:
deps: [setup]
cmds: [go build ./...]
test:
deps: [setup]
cmds: [go test ./...]
all:
deps: [build, test]task all
# setup runs once (not twice from build and test).run: once makes Task deduplicate the task during the run.
For tasks that should always rerun:
tasks:
log-deploy:
run: always
cmds: [echo "Deploy at $(date)"]run: always opts out of the standard skip-if-up-to-date behavior.
Pro Tip: Default is run: when_changed (skip if sources/status say so). Override only when you need different semantics.
Still Not Working?
A few less-obvious failures:
- Task seems to ignore my changes. Cache stuck. Clear:
rm -rf .task/. exec: "task": executable file not found. Not installed. Install:brew install go-task(macOS) /go install github.com/go-task/task/v3/cmd/task@latest.- Includes break with absolute paths in subtasks. Use
${PWD}from the Taskfile’s perspective, or definedir:on the include. - Variables don’t pass to included Taskfiles. Includes have their own var scope. To share: define in root and pass via
vars:on the include:
includes:
frontend:
taskfile: ./frontend/Taskfile.yml
vars: { ENV: "{{.ENV}}" }silent: truedoesn’t silence everything. It silences Task’s “task: [name] cmd” prefix, but the cmd itself still prints stdout. Usecmds: - cmd: ... silent: truefor per-cmd silence.- Shell-specific syntax fails on Windows. Task uses
shby default — Windows doesn’t have it natively. Install Git Bash or WSL, or setset: -eand use cross-platform commands. - Long output gets truncated. Task buffers output by default. For streaming output (logs, watch), set
output: prefixedinTaskfile.yml. depsruns sequentially in dry-run.task --dryshows the plan in order even though execution is parallel. The parallel execution happens at runtime; dry-run just lists what would run.
For related build tool and automation issues, see Lefthook not working, Pre-commit not working, mise not working, and Bun shell not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Bun Bundler Not Working — Targets, Format, Externals, Macros, and Code Splitting
How to fix bun build errors — target (browser/bun/node) mismatch, format esm/cjs/iife, externals not respected, Bun macros at compile time, splitting and chunks, plugin API, and Bun.build vs CLI.
Fix: Nx Not Working — project.json Targets, Affected Commands, Caching, and Generators
How to fix Nx errors — nx.json plugin config, project.json target inputs/outputs, nx affected base branch, cache misses, generator schema, custom executors, and nx migrate failures.
Fix: sqlc Not Working — sqlc.yaml v2, pgx/v5 Driver, Nullable Types, JSONB, and Array Parameters
How to fix Go sqlc errors — sqlc.yaml v2 config schema, pgx/v5 vs database/sql driver choice, sql.NullString vs pointer types, JSONB and UUID overrides, ANY($1::int[]) slice params, and migration ordering.
Fix: Scrapy Not Working — Spider Crawl Returns Nothing, Robots.txt Blocked, and Pipeline Errors
How to fix Scrapy errors — spider yields no items, robots.txt blocking all requests, 403 forbidden response, AttributeError on response.css, item pipeline not processing, AsyncIO reactor errors, and middleware not running.