A gitignore re-include that never shipped broke my Tauri CI build
A Tauri resource glob hard-failed on CI with exit code 101 because a gitignore !KEEP re-include pointed at a file that was never committed, leaving the resources dir empty on fresh checkouts.
A Tauri app built fine on my machine and failed in CI with exit code 101, a resource glob that matched nothing, and a directory the fresh checkout did not have. The cause was a .gitignore re-include rule pointing at a file that was never committed.
Symptom
The desktop app’s tauri.conf.json bundles every file under a resources directory into the final .app and .dmg:
// apps/desktop/src-tauri/tauri.conf.json
{
"bundle": {
"resources": ["resources/bin/**/*"]
}
}
That directory holds binaries the app ships alongside itself: a deepb CLI and, on macOS, bundled ActivityWatch components. They are large and platform-specific, so they are not checked in. A mise task stages them at bundle time:
# prepare:bundle task in apps/desktop/mise.toml (excerpt)
RESOURCE_DIR="$APP_ROOT/src-tauri/resources/bin"
rm -rf "$RESOURCE_DIR"
mkdir -p "$RESOURCE_DIR"
cargo build --release -p deepblock-cli
cp "$TARGET_DIR/deepb" "$RESOURCE_DIR/deepb"
Because the binaries are ignored, the directory would otherwise be empty on a fresh clone. To keep it present, the .gitignore re-includes a KEEP sentinel:
# .gitignore
apps/desktop/src-tauri/resources/*
!apps/desktop/src-tauri/resources/bin/
apps/desktop/src-tauri/resources/bin/*
!apps/desktop/src-tauri/resources/bin/KEEP
The intent is clear: ignore the binaries, but track KEEP so the directory exists. The rust-workspace CI job runs cargo clippy --workspace, which compiles every crate including the Tauri one. On a fresh checkout it failed:
glob pattern resources/bin/**/* path not found or didn't match any files.
error: build failed, waiting for other jobs to finish...
##[error]Process completed with exit code 101.
Tauri’s build script resolves the resources globs at compile time. An empty or missing directory is a hard error, not a warning. The job exited with cargo’s 101. The macOS bundle job, which is the one that actually runs prepare:bundle and populates the directory, never got there.
Locally it always built, because the binaries were sitting in resources/bin/ from previous local bundle runs.
Root cause and fix
I assumed KEEP was tracked, because the .gitignore re-include rule was written for it. It was not:
$ git ls-files apps/desktop/src-tauri/resources/bin/
(empty)
A .gitignore !path re-include un-ignores a path. It does not create or commit the file. If nothing is ever git added there, the re-include matches nothing and the directory stays absent from the repo. The rule was a promise that was never kept.
To confirm the empty-directory hypothesis, I moved the local binaries aside, created only the sentinel, and compiled the desktop crate:
cd apps/desktop/src-tauri/resources/bin
mv aw-server-rust aw-watcher-afk aw-watcher-window deepb /tmp/backup
echo "placeholder for tauri resources glob (populated at bundle time)" > KEEP
cd -
cargo check -p deepblock-desktop
# Finished `dev` profile ... target(s) in 14.34s
The build succeeded with only KEEP present. That confirmed the glob needs the directory to exist and contain at least one file, and that any file satisfies it. The fix was committing the sentinel:
git add apps/desktop/src-tauri/resources/bin/KEEP
The .gitignore already allowed it through (git check-ignore apps/desktop/src-tauri/resources/bin/KEEP returns nothing), and the binaries stayed ignored. After the commit, cargo clippy --workspace passed on the fresh CI checkout.
Prevention
The re-include-without-commit was the real bug, but I would also question why a compile step depends on a directory whose contents only exist at bundle time. cargo clippy --workspace compiling the Tauri crate and evaluating resources globs couples the lint/test job to the bundling contract. A cleaner split would be either to make the build script tolerate an empty glob during non-bundle compiles, or to keep the test job from compiling the crate that carries bundle resources. The sentinel is a working fix; it is not the structural one.
References
- Tauri: bundle configuration (
resources). Theresourcesfield that bundles extra files into the app, and the glob format it accepts. - tauri-apps/tauri#7221: use
resourcesconfig, can not bundle empty directory. The upstream bug where an empty or missing resources directory hard-fails the build, and where a maintainer confirms the dummy-file-in-the-directory workaround this post uses. - gitignore documentation: re-includes. “It is not possible to re-include a file if a parent directory of that file is excluded” and the precedence rules that make
!KEEPwork only when the file is actually committed. - Cargo: build scripts. How Tauri’s build script runs during
cargo check/cargo clippy, which is why the glob failure surfaced in the lint job rather than only at bundle time.
This post was written with AI assistance.