Skip to content

kit pack silently drops files whose names share a prefix with another layer path #1279

Description

@rishabhsai

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions