My issue
GitLab CI used to interpret file path patterns differently in different contexts:
-
in
cache:pathsorartifacts:paths, like glob patterns -
in
cache:key:files, like git pathspecs
Globs and pathspecs behave differently, e.g. for **/foo.
$ tree
.
├── dir
│ └── foo
└── foo
2 directories, 2 files
$ shopt -s globstar # enable globstar expansion
$ ls **/foo
dir/foo foo
$ git ls-files '**/foo'
dir/foo
This caught me out:
I used a pattern like **/foo in cache:key:files, expecting it to catch foo files at every level, but it didn’t.
I raised an issue, suggesting the docs make this clear. It was closed in favour of https://gitlab.com/gitlab-org/gitlab/-/work_items/460235. But that addressed a different issue: commit hashing versus content hashing. So has my issue actually been fixed?
Digging into the history
When I raised the issue in June 2025:
-
cache:key:filescalledlast_commit_id_for_path(path), which delegated to Gitaly (RPC access to Git), which interpreted the path as a pathspec -
wildcard support seems to be accidental
-
switched
cache:key:filesfrom commit to content hashing -
cache:key:filespaths are matched literally (no wildcard support) -
preserved the old commit hashing behaviour under a new key
cache:key:files_commits
-
restored wildcard support to
cache:key:files, which broke with 203233 -
wildcards are supported by creating regexes from user-provided globs (fiddly!)
-
fixed a bug when matching wildcards, so
**/foodoes now match at top-level
-
reverted 209633 because it allowed a file to include itself
-
better version of 209633
-
restored wildcard support and fixed wildcard matching, as in 209633
-
but also prevented a file including itself
Has my issue been fixed?
I tested directly (https://gitlab.com/cosmo-grant/ci-by-example/-/commits/config/cache-and-artifacts-globbing).
I also skimmed the GitLab source.
In cache:key:files, patterns are now interpreted like globs.
So **/foo does match foo at the top-level.
But cache:key:files_commits still behaves as it did originally:
it calls last_commit_id_for_path(path),
which delegates to Gitaly,
which interprets the path as a pathspec.
So **/foo still does not match foo at the top-level.
The bottom line: pattern interpretation is still inconsistent and confusing.
I’ve raised another issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/595745.