From a59392982d6e793b27fe4859eddbfa37e4d26431 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 9 Aug 2026 22:50:54 +0200 Subject: [PATCH 1/2] fix: report no timestamp when a task's sources match no file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimestampChecker.Value returned a sentinel for "no source found": first the string "0", then time.Unix(0, 0) once the value became a time.Time exposed to templates. Rendered, that sentinel reads as a genuine date, 1970-01-01 01:00:00 +0100 CET, whose zone depends on the machine — so a command interpolating {{.TIMESTAMP}} gets a plausible but meaningless multi-word value instead of nothing. Return an empty string, like NoneChecker already does for a checker with nothing to report. --- internal/fingerprint/sources_timestamp.go | 6 ++- .../fingerprint/sources_timestamp_test.go | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 internal/fingerprint/sources_timestamp_test.go diff --git a/internal/fingerprint/sources_timestamp.go b/internal/fingerprint/sources_timestamp.go index 929b044bca..3bb7a07cd3 100644 --- a/internal/fingerprint/sources_timestamp.go +++ b/internal/fingerprint/sources_timestamp.go @@ -122,8 +122,12 @@ func (checker *TimestampChecker) Value(t *ast.Task) (any, error) { return time.Now(), err } + // No source matched, so there is no modification time to report. Returning + // the Unix epoch here would render as a real date in templates, e.g. + // "1970-01-01 01:00:00 +0100 CET", which reads as a genuine timestamp and + // carries a machine-dependent zone. if sourcesMaxTime.IsZero() { - return time.Unix(0, 0), nil + return "", nil } return sourcesMaxTime, nil diff --git a/internal/fingerprint/sources_timestamp_test.go b/internal/fingerprint/sources_timestamp_test.go new file mode 100644 index 0000000000..665dc7cc89 --- /dev/null +++ b/internal/fingerprint/sources_timestamp_test.go @@ -0,0 +1,44 @@ +package fingerprint + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-task/task/v3/taskfile/ast" +) + +func TestTimestampCheckerValue(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "source.txt"), []byte("content"), 0o644)) + checker := NewTimestampChecker(t.TempDir(), true) + + t.Run("reports the newest source modification time", func(t *testing.T) { + t.Parallel() + + value, err := checker.Value(&ast.Task{ + Dir: dir, + Sources: []*ast.Glob{{Glob: "source.txt"}}, + }) + require.NoError(t, err) + assert.IsType(t, time.Time{}, value) + assert.False(t, value.(time.Time).IsZero()) + }) + + t.Run("reports no value when no source matches", func(t *testing.T) { + t.Parallel() + + value, err := checker.Value(&ast.Task{ + Dir: dir, + Sources: []*ast.Glob{{Glob: "gen/**/*.go"}}, + }) + require.NoError(t, err) + assert.Equal(t, "", value) + }) +} From 0c6c9413ff1ab2a95a3e62b1205d237e5101de86 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 9 Aug 2026 22:51:17 +0200 Subject: [PATCH 2/2] chore: changelog for #2960 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb1322765..317c733196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ gets `{{.TIMESTAMP}}` and no longer a `{{.CHECKSUM}}` (which now renders as an empty string), and neither variable is injected when the effective method is `none` (#2924 by @vmaerten). +- Fixed `{{.TIMESTAMP}}` rendering as the Unix epoch (e.g. `1970-01-01 01:00:00 + +0100 CET`) when a task's `sources` matched no file. It now renders as an + empty string, instead of a date that looks genuine and carries the machine's + time zone (#2960 by @vmaerten). ## v3.52.0 - 2026-07-02