- A Linux release stays rebuildable for good. The pinned build environment
is now one locked package set whose sources stay fetchable indefinitely,
replacing hand-pinned Alpine packages that eventually disappear. Rebuilding a
release still needs only Docker and
./build.sh; thebuild-info.txtshipped with each Linux release now records aflake lock:digest in place ofapk pins:.
- vk-registry caches need only an address and an API key. A cache model
can name a vk-registry repository —
vk: registry.example/task-cache— and Task derives the cache entries and the build-once lock from it; an optionalnamespacekeeps entries built by different toolchains apart, and avkthat renders empty turns the cache off. The key comes fromapi_keyorTASK_VK_API_KEY;api_keyalso works on an explicitoci://cache orvk://lock, where it outranks any other credential. - One vk-registry API key authenticates an explicit
oci://cache andvk://lock.TASK_VK_API_KEYis sent as a bearer token tooci://caches.vk://locks without URL credentials useTASK_VK_LOCK_TOKEN, then fall back to that key or theTASK_CACHE_OCI_USER/TASK_CACHE_OCI_PASSWORDpair. - Authenticate transparent-zstd capability probes. The
oci://cache now sends registry credentials when negotiating transparent-zstd uploads, so credential-gated vk-registry servers use that mode instead of compressed-digest uploads. - A
vk://lock is released when its task finishes. It used to stay held until its lease expired, making a peer waiting on the same build wait up to 30 s longer than needed. - An
oci://cache miss is quiet. A tag the registry does not have was reported as a registry error in verbose output on every miss. - A
vk://lock names its holder's machine on the registry even when the shell did not exportHOSTNAME, as under a CI job.
- Match files with trailing
**globs —target/**now matches the same files astarget/**/*, restoring Go Task and bashglobstarcompatibility forsources:,generates:, andexclude:patterns. These patterns previously matched nothing. - Exclude build directories without scanning them — an
exclude:such astarget/**/*skips matching trees, keeping large excludes fast without changing the matched file set.
task --helplists the flags — it now prints the usage line, every option with its description, and the hint to runtask --list-all, instead of a short blurb that pointed back at--helpfor the flag list.
- Linux release binaries are reproducible — each release includes a
task-linux-<arch>.build-info.txtmanifest for verifying a local rebuild. See reproducible builds.
defaultis a filter, not a backwards-looking call —--migratenow converts both Go spellings,{{ .X | default "y" }}and{{ default "y" .X }}, to{{ X | default("y", true) }}. That second argument makes Jinja's own filter mean what sprig'sdefaultmeans — substituting for any empty value, not only an unset one — so a migrated Taskfile renders what it always did while reading in the natural order. A Go{{ default "y" }}with no value at all now rendersy, as sprig does, instead of failing.- Breaking: the sprig-ordered
default(fallback, value)function is gone. A Taskfile written or migrated against 4.1.0 / 4.1.1 that calls it now fails withunknown function; write the value first and add, true—X | default("y", true). truncandregexReplaceAllwork as filters —s | trunc(n)ands | regexReplaceAll(pattern, repl), so a Jinja Taskfile no longer has to spell either as a sprig-ordered call. The functions keep working as before, and a Go Taskfile's{{ .P | trunc 3 }}, which used to fail to render, now works and migrates to{{ P | trunc(3) }}.- A file's template dialect now covers the
vars:it passes to anincludes:entry and thecaches:models it defines. A tree partway through--migrateworks: the migrated file no longer fails on its own include vars, and a not-yet-migrated task no longer fails on the cache URL it inherits. A file that declares one dialect but writes those in the other used to be read as Go regardless, and now errors — runtask --migrateon it.
task --updatereplaces the binary with a published release — the latest, or--update=<version>for a specific one. It asks before touching anything (--yesskips that), checks the download against thesha256published beside it, and runs the new binary to confirm it works here before putting it in place.task --update --checkonly reports what is available, exiting1when a newer release exists. A task namedupdatestill runs as it always did.install-task.shinstalls again — every run used to fail before it downloaded the archive.- A failing cache registry or cache lock names the reason — a rejected certificate, a refused connection, a name that does not resolve — instead of only reporting that the registry could not be reached.
- A
vks://lock trusts the same certificate as the cache, so one registry behind a private CA serves both the cache and the build-once lock. Such a lock could not connect before, and every run took a local lock instead. - Go-syntax Taskfiles can call
printfandprint, so a Taskfile that builds a path out of several variables renders and migrates instead of being rejected as an unsupported construct.printfcovers the string-composing verbs (%s,%v,%q,%d,%%, with flags and a width); another verb, a precision, an argument its verb cannot render, or a mismatched argument count is an error. - An unsupported Go construct is reported as the
{{ … }}that used it — with the line it is on when migrating a whole Taskfile — instead of quoting the string it came from, which under--migratewas the whole file.
- Quoted strings in Go-syntax templates keep their dots, so
{{ .FILE | replace ".tar.gz" "" }}strips what it was given. Affected tasks re-run once, and a Taskfile already converted bytask --migrate --writeneeds checking by hand. - An escaped quote works inside a Go-syntax template string. A Taskfile
already converted by
task --migrate --writeneeds checking by hand. - Go-syntax templates can call the string helpers directly, not only after
a pipe, so
{{trimSuffix ".po" .ITEM}}works.splitListwas splitting the wrong argument. Affected tasks re-run once. default,title,join,firstandlastmean the same on either side of a pipe in Go-syntax Taskfiles — most visibly,defaultsubstitutes for any empty value, not only an unset one. Jinja Taskfiles keep Jinja's meaning. Affected tasks re-run once, and a Taskfile already converted needs checking by hand.- The Go-template deprecation warning no longer runs into the next line.
- A dependency cycle is reported as one, naming the path (
a -> b -> a), instead of running until the process ran out of stack. A task that reaches itself is rejected: calling itself with differentvars:still works, but recursion driven by state outside the Taskfile no longer does. - Deep dependency trees no longer crash the runner. Thousands of levels run; a runaway recursion stops with an error instead.
- Tasks start in a different order, so interleaved output can differ.
--output groupandprefixedstill keep each task's output together. - A failing dependency no longer stops its siblings starting. Under
--failfastthey all start and are cancelled when one fails, so a cancelled sibling may leave partial work behind. - A failing run stops the commands it leaves behind instead of orphaning
them. It cannot tell those from a job a task backgrounded on purpose, so both
are stopped;
TASK_NO_REAP=1turns that off. - Ctrl-C reaches the commands, not just the runner. The second press is passed on to them, and the third stops them before exiting.
- A confirmation prompt no longer pauses the rest of the run, and Ctrl-C works while one is waiting.
- A
SIGTERMstops the run at once, exiting1— it comes from a supervisor, so there is no second signal to wait for. Ctrl-C keeps its three-press escalation, and watch sessions are unchanged.
Task is now a Rust program. v4.0.0 is a full rewrite that aims to be drop-in compatible.
- Local deduplicated cache. A content-defined-chunking, zstd-compressed,
content-addressed cache (the
ocicascrate) backs--export-cache/--import-cacheand the OCI cache backend, so shared build outputs are stored and transferred once. - Native Jinja templating (opt-in), with migration. Alongside the existing
Go
text/templatesyntax, a Taskfile can opt into native minijinja withtemplater: jinja, unlocking{% for %},{% if %}, filters, and function-call syntax. The dialect is auto-detected per file;task --migrateconverts a Go-syntax Taskfile to Jinja (preview by default,--writeto apply). - Go template syntax is deprecated. Files that still use it get a one-time
warning pointing at
task --migrate. Go rendering will be removed in a future release; migration will remain. Suppress the warning withTASK_NO_GO_DEPRECATION=1. - vk-registry distributed lock.
cache.lock: vk://host/<prefix>takes the build-once lock over the registry's own/lockAPI, so one vk-registry serves both theoci://cache and the lock with no separate Redis. Credentials come from the URL (Basic) or$TASK_VK_LOCK_TOKEN(bearer); the lease is renewed by a heartbeat and expires 30 s after a holder goes away. - Single static binary. The release build links no system C libraries (musl-static, rustls+ring) and ships as one file.
- A duplicate task key in a Taskfile is now an error instead of silently taking the last definition.
- Task env/vars take precedence over the process environment by default. A
task-defined
env/varsvalue overrides one already present in the inherited environment; setTASK_X_ENV_PRECEDENCE=0to restore the old order where the process environment wins. - Remote (HTTP) Taskfiles are not supported.
- Storing cache contents in Redis (
cache.url: redis://) is not supported. (The file and OCI cache backends, and the Redis and vk-registry distributed locks —cache.lock: redis://andvk://— all work.) - Syntax highlighting in error snippets.