GoogleContainerTools/skaffold · error

failed to execute host %s hook %d for artifact %q: %w

Error message

failed to execute host %s hook %d for artifact %q: %w

What it means

During sync (or dev) pre/post phases, skaffold runs each host hook defined for an artifact. If a host (local shell) hook fails — and the failure is not a deliberate Skip — run wraps the original error with the phase, 1-based hook index, artifact image name, and underlying error, then aborts the sync.

Source

Thrown at pkg/skaffold/hooks/sync.go:86

	return r.run(ctx, out, r.PostHooks, phases.PostSync)
}

func (r syncRunner) getEnv() []string {
	common := getEnv(staticEnvOpts)
	sync := getEnv(r.opts)
	return append(common, sync...)
}

func (r syncRunner) run(ctx context.Context, out io.Writer, hooks []latest.SyncHookItem, phase phase) error {
	if len(hooks) > 0 {
		output.Default.Fprintf(out, "Starting %s hooks for artifact %q...\n", phase, r.imageName)
	}
	env := r.getEnv()
	for i, h := range hooks {
		if h.HostHook != nil {
			hook := hostHook{*h.HostHook, env}
			if err := hook.run(ctx, nil, out); err != nil && !errors.Is(err, &Skip{}) {
				return fmt.Errorf("failed to execute host %s hook %d for artifact %q: %w", phase, i+1, r.imageName, err)
			}
		} else if h.ContainerHook != nil {
			hook := containerHook{
				cfg:        *h.ContainerHook,
				cli:        r.cli,
				selector:   runningImageSelector(r.imageRef),
				namespaces: r.namespaces,
				formatter:  r.formatter,
			}
			if err := hook.run(ctx, out); err != nil {
				return fmt.Errorf("failed to execute container %s hook %d for artifact %q: %w", phase, i+1, r.imageName, err)
			}
		}
	}
	if len(hooks) > 0 {
		output.Default.Fprintf(out, "Completed %s hooks for artifact %q\n", phase, r.imageName)
	}
	return nil

View on GitHub (pinned to a1189de023)

Solutions

  1. Read the wrapped %w error in the message — it names the failing command and exit reason; fix that command
  2. Test the hook command manually with the same working directory skaffold uses (the build workspace)
  3. Make the hook executable (chmod +x) or use an explicit interpreter prefix (sh -c ...)
  4. If the hook may legitimately fail, handle the Skip sentinel or make the script exit 0 with a warning

Example fix

// before (skaffold.yaml)
sync:
  hooks:
    before:
      - host:
          command: ["./scripts/migrate"]
// after
sync:
  hooks:
    before:
      - host:
          command: ["sh", "-c", "./scripts/migrate || echo 'migration skipped' >&2"]
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check host hooks before enabling them in sync:
for _, cmd := range hookCmds {
    if err := exec.Command("sh", "-c", "command -v "+cmd[0]).Run(); err != nil {
        return fmt.Errorf("hook command %q not available", cmd[0])
    }
}

Try / catch

if err := syncRunner.RunPreHooks(ctx, out); err != nil {
    var skip *hooks.Skip
    if !errors.As(err, &skip) {
        log.Printf("host hook failed: %v", err) // wrapped msg names phase, hook index, artifact
    }
}

Prevention

When it happens

Trigger: A hostHook command in skaffold.yaml (e.g. `before-hooks`/`after-hooks` under sync) exits non-zero, times out, or the executable isn't found while RunPreHooks/RunPostHooks iterate hooks for artifact r.imageName.

Common situations: Hook script missing execute bit or not on PATH; command exits non-zero due to a failing migration or script bug; shell interpreter missing in minimal environments; intentional failure during tests.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/ea942105188ee350. Report an issue: GitHub.