this issue is created with Cursor
TLDR: seems like the function is_absolute_path is POSIX-only
Summary
On Windows agents, caching an absolute path such as C:/cache/foo (or C:\cache\foo) saves an archive but never restores it. Every restore fails with:
tar: C\:/cache/foo: Not found in archive
When soft-fail is enabled the build then silently continues with a cold cache, so the failure is easy to miss. This affects the zstd, tgz, and zip compression backends.
Environment
- Plugin:
cache-buildkite-plugin
- Compression backends affected:
zstd, tgz, zip
- Agent OS: Windows, using GNU
tar under Git Bash / msys2
- Backends: reproduces on the generic compress/restore path (observed in production with the
s3 backend)
Root cause
All three wrappers in compression/ decide whether to pass tar's -P (--absolute-names) flag using:
is_absolute_path() { [ "${1:0:1}" = "/" ]; }
This only treats POSIX /-rooted paths as absolute. A Windows path begins with a drive letter (C:/… or C:\…), so is_absolute_path returns false for it. The two sides then disagree:
- Compress: runs
tar without -P. GNU/msys tar strips the drive prefix and stores the member as a relative path, e.g. cache/foo, emitting tar: Removing leading 'C:/' from member names.
- Restore: still without
-P, asks tar to extract the member named C:/cache/foo. That member does not exist in the archive (it was stored as cache/foo) → Not found in archive → restore fails.
Because compress and restore compute the member name differently, the cache can never be restored for an absolute Windows path.
A secondary consequence: if save skips when the key already exists, the poisoned/empty-from-the-consumer's-perspective archive persists until the cache key rotates, so the cache stays permanently cold.
Reproduction (Git Bash on Windows)
# from the plugin root
mkdir -p /c/cache/foo && echo hi > /c/cache/foo/a.txt
# compress an ABSOLUTE windows path — note the "removing leading drive letter" warning
compression/zstd_wrapper compress "C:/cache/foo" out.tzst
rm -rf /c/cache/foo
# restore fails: the member was stored as cache/foo, not C:/cache/foo
compression/zstd_wrapper decompress out.tzst "C:/cache/foo"
# => tar: C:/cache/foo: Not found in archive
A relative path (e.g. compress "cache/foo" / decompress out.tzst "cache/foo") works, because both sides treat it consistently as relative — which is why the bug only surfaces for absolute cache paths.
Impact
- Any Windows pipeline that caches an absolute path gets a permanent, silent cache miss.
- With
soft-fail enabled, builds appear green while re-doing all the work the cache was meant to skip.
- Relative (in-checkout) cache paths are unaffected, which is why this can go unnoticed for a long time.
Suggested fix
Teach is_absolute_path about Windows drive-absolute paths, in compression/zstd_wrapper, compression/tgz_wrapper, and compression/zip_wrapper:
is_absolute_path() {
case "$1" in
/*) return 0 ;; # POSIX absolute
[A-Za-z]:[\\/]*) return 0 ;; # Windows drive-absolute: C:/... or C:\...
*) return 1 ;;
esac
}
With this change both compress and restore pass -P, so tar stores and looks up the same absolute member name and the cache restores correctly.
Notes
- The fix needs to land in all three wrappers, since they each carry their own copy of
is_absolute_path.
- Existing cache objects created before the fix were stored with the drive prefix stripped; consumers using an absolute path will need their cache key rotated (or the stale objects deleted) to pick up a correctly-stored archive, since
save skips when the key already exists.
this issue is created with Cursor
TLDR: seems like the function
is_absolute_pathis POSIX-onlySummary
On Windows agents, caching an absolute path such as
C:/cache/foo(orC:\cache\foo) saves an archive but never restores it. Every restore fails with:When
soft-failis enabled the build then silently continues with a cold cache, so the failure is easy to miss. This affects thezstd,tgz, andzipcompression backends.Environment
cache-buildkite-pluginzstd,tgz,ziptarunder Git Bash / msys2s3backend)Root cause
All three wrappers in
compression/decide whether to passtar's-P(--absolute-names) flag using:This only treats POSIX
/-rooted paths as absolute. A Windows path begins with a drive letter (C:/…orC:\…), sois_absolute_pathreturns false for it. The two sides then disagree:tarwithout-P. GNU/msystarstrips the drive prefix and stores the member as a relative path, e.g.cache/foo, emittingtar: Removing leading 'C:/' from member names.-P, askstarto extract the member namedC:/cache/foo. That member does not exist in the archive (it was stored ascache/foo) →Not found in archive→ restore fails.Because compress and restore compute the member name differently, the cache can never be restored for an absolute Windows path.
A secondary consequence: if
saveskips when the key already exists, the poisoned/empty-from-the-consumer's-perspective archive persists until the cache key rotates, so the cache stays permanently cold.Reproduction (Git Bash on Windows)
A relative path (e.g.
compress "cache/foo"/decompress out.tzst "cache/foo") works, because both sides treat it consistently as relative — which is why the bug only surfaces for absolute cache paths.Impact
soft-failenabled, builds appear green while re-doing all the work the cache was meant to skip.Suggested fix
Teach
is_absolute_pathabout Windows drive-absolute paths, incompression/zstd_wrapper,compression/tgz_wrapper, andcompression/zip_wrapper:With this change both compress and restore pass
-P, sotarstores and looks up the same absolute member name and the cache restores correctly.Notes
is_absolute_path.saveskips when the key already exists.