Describe the bug
kit pack silently leaves files out of the ModelKit when one layer's path happens to be a string prefix of a sibling file or directory name. With a Kitfile that packs the whole context as code (path: .) and a data directory as a dataset, every sibling entry whose name starts with the characters data is attributed to the dataset layer and never written to any layer. database.txt and data-v2/extra.txt are both dropped. Pack succeeds with no warning, so the loss only becomes visible after kit unpack, or worse, after the ModelKit has been pushed and someone else pulls it.
To Reproduce
mkdir -p /tmp/kitrepro/ctx/data /tmp/kitrepro/ctx/data-v2 && cd /tmp/kitrepro/ctx
echo hello > database.txt
echo 'print()' > main.py
echo rows > data/set1.txt
echo x > data-v2/extra.txt
cat > Kitfile <<'YAML'
manifestVersion: 1.0.0
package:
name: prefix-repro
code:
- path: .
datasets:
- name: ds
path: data
YAML
export KITOPS_HOME=/tmp/kitrepro/kitops
kit pack . -t prefix-repro:test
kit unpack prefix-repro:test -d /tmp/kitrepro/out
find /tmp/kitrepro/out -type f | sort
The context has five files:
/tmp/kitrepro/ctx/Kitfile
/tmp/kitrepro/ctx/data-v2/extra.txt
/tmp/kitrepro/ctx/data/set1.txt
/tmp/kitrepro/ctx/database.txt
/tmp/kitrepro/ctx/main.py
Pack reports no problem:
Saved code layer: sha256:b5654950221098a1f8d8340db57b97599786e50d6b271ce13dd38931adcd0914
Saved dataset layer: sha256:1dc98eaff0e9c380061bc158144cb1e11dbea7a48de5b36408dc8890f2833582
Saved configuration: sha256:4206414718cec70a5ae1c43bbf31dec7ba2c173624d52b93a6047e3e0fad9149
Saved manifest to storage: sha256:6ed740487c37854f5a8e6c3572bfb2004c262bad744dcd19c26824acd5f74b53
Model saved: sha256:6ed740487c37854f5a8e6c3572bfb2004c262bad744dcd19c26824acd5f74b53
But the unpacked ModelKit is missing database.txt and data-v2/extra.txt:
$ find /tmp/kitrepro/out -type f | sort
/tmp/kitrepro/out/Kitfile
/tmp/kitrepro/out/data/set1.txt
/tmp/kitrepro/out/main.py
Expected: all four files plus the Kitfile.
Version
1.15.0
Version: 1.15.0
Commit: 6b8162ae5da4d46f1d2af2beb43e7fb077f052f4
Built: 2026-06-23T22:29:43Z
Go version: go1.25.7
Also reproduced with a binary built from main at b676284.
Additional context
The test for "does another layer own this path" in pkg/lib/filesystem/ignore/ignore.go:87 is a raw string prefix compare rather than a path-element compare:
if strings.HasPrefix(path, layer) {
// The current path is included in another layer that is a subdirectory of the current layer
return true, nil
}
"database.txt" has the string prefix "data" but is not inside the data directory, so it is treated as belonging to the dataset layer and skipped. The same applies to the sibling compare on line 82. This Matches is the gate used by both writeLayerToTar (pkg/lib/filesystem/tar.go:238) and getTotalSize (pkg/lib/filesystem/paths.go:121), so affected files are left out of the tar and out of the progress total, which is why nothing is reported.
pkg/cmd/kitimport/kitfile_filter.go:97 already does the comparison the other way (strings.HasPrefix(path, layer+"/")), so the ignore package looks like the odd one out.
Nothing about this depends on the layer types; code/docs, datasets/datasets, and so on collide the same way as long as one layer path is a character prefix of a sibling name.
Describe the bug
kit packsilently leaves files out of the ModelKit when one layer's path happens to be a string prefix of a sibling file or directory name. With a Kitfile that packs the whole context as code (path: .) and adatadirectory as a dataset, every sibling entry whose name starts with the charactersdatais attributed to the dataset layer and never written to any layer.database.txtanddata-v2/extra.txtare both dropped. Pack succeeds with no warning, so the loss only becomes visible afterkit unpack, or worse, after the ModelKit has been pushed and someone else pulls it.To Reproduce
The context has five files:
Pack reports no problem:
But the unpacked ModelKit is missing
database.txtanddata-v2/extra.txt:Expected: all four files plus the Kitfile.
Version
1.15.0
Also reproduced with a binary built from
mainat b676284.Additional context
The test for "does another layer own this path" in
pkg/lib/filesystem/ignore/ignore.go:87is a raw string prefix compare rather than a path-element compare:"database.txt"has the string prefix"data"but is not inside thedatadirectory, so it is treated as belonging to the dataset layer and skipped. The same applies to the sibling compare on line 82. ThisMatchesis the gate used by bothwriteLayerToTar(pkg/lib/filesystem/tar.go:238) andgetTotalSize(pkg/lib/filesystem/paths.go:121), so affected files are left out of the tar and out of the progress total, which is why nothing is reported.pkg/cmd/kitimport/kitfile_filter.go:97already does the comparison the other way (strings.HasPrefix(path, layer+"/")), so the ignore package looks like the odd one out.Nothing about this depends on the layer types;
code/docs,datasets/datasets, and so on collide the same way as long as one layer path is a character prefix of a sibling name.