Generated hooks are not included in the Snap
Metadata
Current evaluation
Generated part hooks now replace stubs correctly; commit 271462658 prevents premature stub creation and was targeted for Snapcraft 9.1.3.
Resolution: closed resolved
Reason: Commit 271462658, titled "fix(pack): don't generate stub hooks prematurely," prevents early meta/hooks stubs from blocking generated hooks. Maintainer @mr-cal confirmed the fix was targeted for the 9.1.3 release.
Evaluated by: mr-cal-bot
No scores available.
Issue body
### Check existing issues
- [x] I've verified that this bug isn't described by any existing issues.
### Bug description
Generated hooks, e.g. a part writing to `$CRAFT_PRIME/snap/hooks/<name>` are not properly staged in the final Snap, leaving the `#!/bin/true` stubs.
When `snapcraft.yaml` declares a `hooks` section, stubs with `#!/bin/true` is written in `meta/hooks/<name>` during the `setup_assets(..., copy_hooks_and_gui=False) -> setup_hooks() -> _ensure_hook()` call as the hooks are not yet generated.
The generated hooks are supposed to replace the stubs in `_materialize_extra_assets() -> provision_hooks(prime_dir, overwrite=False)`. However, because the stub now occupies the path, the generated hooks are not copied.
Two conditions are required to trigger this:
1. `snapcraft.yaml` declares a `hooks` section (needed for hook plugs)
2. a part produces `snap/hooks/<name>` -- what `snap-helpers write-hooks` does
This is observed in Snapcraft 9.1.x. Previous Snapcraft versions 9.0.x do not have this bug.
Most likely caused by #6379 and #6385.
Same issue came and fixed in the past: #3386 and #3837.
### Steps to reproduce
Using this helper script:
```sh
#!/bin/bash
# Reproducer: snapcraft discards a part-generated hook, shipping a
# "#!/bin/true" stub in meta/hooks/<name> so the hook never runs.
#
# ./repro-hooks.sh reproduce the bug
# ./repro-hooks.sh --no-hooks control: drop the `hooks:` section
# ./repro-hooks.sh --fix workaround applied in override-prime
#
# Builds a throwaway snap in a temp dir; nothing is installed.
set -euo pipefail
FIX=0; NO_HOOKS=0
for arg in "$@"; do
case "$arg" in
--fix) FIX=1 ;;
--no-hooks) NO_HOOKS=1 ;;
-h|--help) sed -n '2,12p' "$0"; exit 0 ;;
*) echo "usage: $0 [--fix] [--no-hooks]" >&2; exit 2 ;;
esac
done
command -v snapcraft >/dev/null || { echo "snapcraft not found" >&2; exit 1; }
command -v unsquashfs >/dev/null || { echo "unsquashfs not found (apt install squashfs-tools)" >&2; exit 1; }
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/snap"
# Two conditions are required to trigger this, and the script lets you remove
# either one:
# 1. snapcraft.yaml declares a `hooks:` section (needed for hook plugs)
# 2. a part produces snap/hooks/<name> -- what `snap-helpers write-hooks` does
{
cat <<'YAML'
name: hookstub-repro
base: core26
version: "0.1"
summary: part-generated hook replaced by a stub
description: part-generated hook replaced by a stub
grade: stable
confinement: strict
apps:
hello:
command: bin/hello
YAML
[ "$NO_HOOKS" = 0 ] && cat <<'YAML'
hooks:
configure:
plugs: [network]
YAML
cat <<'YAML'
parts:
app:
plugin: nil
override-build: |
mkdir -p $CRAFT_PART_INSTALL/bin
printf '#!/bin/sh\necho hello\n' > $CRAFT_PART_INSTALL/bin/hello
chmod 755 $CRAFT_PART_INSTALL/bin/hello
# A part generating a hook into snap/hooks/, as snap-helpers does.
mkdir -p $CRAFT_PART_INSTALL/snap/hooks
printf '#!/bin/sh\necho REAL_HOOK_RAN\n' > $CRAFT_PART_INSTALL/snap/hooks/configure
chmod 755 $CRAFT_PART_INSTALL/snap/hooks/configure
YAML
[ "$FIX" = 1 ] && cat <<'YAML'
override-prime: |
craftctl default
# Write the wrappers before metadata generation, so _ensure_hook() finds
# meta/hooks/<name> already present and returns early.
for hook in "$CRAFT_PRIME"/snap/hooks/*; do
[ -f "$hook" ] || continue
name="$(basename "$hook")"
mkdir -p "$CRAFT_PRIME/meta/hooks"
printf '#!/bin/sh\nexec "$SNAP/snap/hooks/%s" "$@"\n' "$name" \
> "$CRAFT_PRIME/meta/hooks/$name"
chmod 755 "$CRAFT_PRIME/meta/hooks/$name"
done
YAML
} > "$WORK/snap/snapcraft.yaml"
printf '\n== snapcraft version\n'; snapcraft --version 2>/dev/null | head -1
printf '\n== building (hooks-section=%s, fix=%s)\n' "$((1-NO_HOOKS))" "$FIX"
( cd "$WORK" && snapcraft --use-lxd --verbosity=quiet )
SNAP="$(ls "$WORK"/*.snap)"
printf '\n== snap/hooks/configure (what the part generated)\n'
unsquashfs -cat "$SNAP" snap/hooks/configure 2>/dev/null | sed 's/^/ /' || echo " (absent)"
printf '\n== meta/hooks/configure (what snapd actually runs)\n'
unsquashfs -cat "$SNAP" meta/hooks/configure 2>/dev/null | sed 's/^/ /' || echo " (absent)"
printf '\n'
if unsquashfs -cat "$SNAP" meta/hooks/configure 2>/dev/null | grep -q '/bin/true'; then
echo ">> REPRODUCED: meta/hooks/configure is a #!/bin/true stub; the hook never runs."
exit 1
else
echo ">> Real hook shipped (expected behaviour)."
fi
```
### Environment
Build on Noble with Snapcraft version 9.1.1.
Evaluation history
| Date | Model | Scores | Action | Summary |
|---|---|---|---|---|
| stealth/space-bunny-alpha | — | closed resolved | Generated part hooks now replace stubs correctly; commit 271462658 prevents premature stub creation and was targeted for Snapcraft 9.1.3. | |
| stealth/space-bunny-alpha | — | closed resolved | Generated-hook packaging regression was closed after a maintainer said the fix was targeted for Snapcraft 9.1.3, addressing stubs replacing part-generated hooks. | |
| gemini-3.8-flash |
Impact:
50
Quick Win:
36
Complexity:
25
Confidence:
95
Actionability:
95
|
needs triage | Generated hooks from parts writing to snap/hooks are overwritten by stub scripts in meta/hooks when a hooks section is declared in snapcraft.yaml |
Update history
| Date | Change |
|---|---|
| closed | |
| updated | |
| updated | |
| created |
Related work
-
Related To:
canonical/snapcraft#3386
(confidence 95%)
Previously fixed the related problem of preconfigured hooks being overwritten by generated stubs.
-
Related To:
canonical/snapcraft#3837
(confidence 95%)
Previously added processing for hooks generated by parts.
-
Caused By:
canonical/snapcraft#6379
(confidence 85%)
The report identified asset-mediation changes from this PR as a likely source of the regression.
-
Caused By:
canonical/snapcraft#6385
(confidence 85%)
The report identified this related change as another likely source of the regression.
Related issues
| Issue | Project | State | Summary | Similarity |
|---|---|---|---|---|
| #6434 Replace generated hook stubs with built hooks when hooks are declared | snapcraft | closed | Closed unmerged; the generated-hook regression was fixed separately by commit 271462658 and released in Snapcraft 9.1.3. | |
| #3386 meta: don't overwrite preconfigured hooks with stubs | snapcraft | merged | Hook stub generation now preserves existing primed meta/hooks scripts; merged in snapcraft#3386 via commit bb9525144, with added unit-test fixtures and coverage. | |
| #3837 fix: hooks: process generated hooks | snapcraft | merged | Merged as commit c77e3a07a (CRAFT-1159): core22 now includes lifecycle-generated hooks in the snap via meta/hooks wrappers, with snap/hooks taking priority; adds spread tests for core18/20/22. | |
| #3296 meta: write stubs for command-chain using hooks when needed | snapcraft | merged | Merged #3296 in commit 95225991f, adding Snapcraft metadata generation of stub hooks for command-chain-based hooks. | |
| #1980089 Compiled snap hooks not executed when packaging with Snapcraft 7 | snapcraft (launchpad) | closed | Compiled snap hooks are supported again by merged PR snapcore/snapcraft#3837 (commit c77e3a07a), which processes generated hooks and restores the expected meta/hooks entries. | |
| #1812003 hooks should be made executable instead of erroring out | snapcraft (launchpad) | closed | Fixed by merged PR snapcore/snapcraft#2440 (commit c2f5a0dc0), which packages recognized hooks as executable instead of rejecting non-executable source files. |