GoogleContainerTools/skaffold · error

pre-sync hooks failed for artifact %q: %w

Error message

pre-sync hooks failed for artifact %q: %w

What it means

The PodSyncer.Sync entry point runs the artifact's pre-sync lifecycle hooks (kubectl-based) before performing the copy/delete sync. If RunPreHooks fails, Skaffold wraps the error naming the artifact image so users know which artifact's hooks failed.

Source

Thrown at pkg/skaffold/sync/sync.go:295

	if !item.HasChanges() {
		return nil
	}

	var copy, delete []string
	for k := range item.Copy {
		copy = append(copy, k)
	}
	for k := range item.Delete {
		delete = append(delete, k)
	}

	opts, err := hooks.NewSyncEnvOpts(item.Artifact, item.Image, copy, delete, *s.namespaces, s.kubectl.KubeContext)
	if err != nil {
		return err
	}
	hooksRunner := hooks.NewSyncRunner(s.kubectl, item.Artifact.ImageName, item.Image, *s.namespaces, s.formatter, item.Artifact.Sync.LifecycleHooks, opts)
	if err := hooksRunner.RunPreHooks(ctx, out); err != nil {
		return fmt.Errorf("pre-sync hooks failed for artifact %q: %w", item.Artifact.ImageName, err)
	}
	if err := s.sync(ctx, item); err != nil {
		return err
	}
	if err := hooksRunner.RunPostHooks(ctx, out); err != nil {
		return fmt.Errorf("post-sync hooks failed for artifact %q: %w", item.Artifact.ImageName, err)
	}
	return nil
}

func (s *PodSyncer) sync(ctx context.Context, item *Item) error {
	if len(item.Copy) > 0 {
		log.Entry(ctx).Info("Copying files:", item.Copy, "to", item.Image)

		if err := Perform(ctx, item.Image, item.Copy, s.copyFileFn, *s.namespaces, s.kubectl.KubeContext); err != nil {
			return fmt.Errorf("copying files: %w", err)
		}
	}

View on GitHub (pinned to a1189de023)

Solutions

  1. Check the nested error's output: run the failing hook's command manually (kubectl exec) in the target pod/namespace
  2. Fix the preSync spec in skaffold.yaml (command, container name, timeouts) for the artifact named in the message
  3. Verify *s.namespaces and the kube context match where the app actually runs (kubectl config current-context)

Example fix

// before
sync:
  lifecycleHooks:
    preSync:
      - exec: {command: ["./scripts/missing.sh"]}
// after
sync:
  lifecycleHooks:
    preSync:
      - exec: {command: ["sh", "-c", "curl -fs http://localhost:8080/health"]}
Defensive patterns

Strategy: try-catch

Validate before calling

for _, h := range a.Sync.LifecycleHooks.PreSync {
    if h.Exec != nil && len(h.Exec.Command) == 0 { return errors.New("empty preSync exec command") }
    if h.Container != nil && h.Container.ContainerName == "" { return errors.New("preSync container hook missing containerName") }
}
// also verify pod/namespace exist: kubectl get ns <ns>

Try / catch

err := s.Sync(ctx, out, names, item)
if err != nil {
    var hookErr interface{ Unwrap() error }
    if errors.As(err, &hookErr) && strings.Contains(err.Error(), "pre-sync hooks failed") {
        log.Errorf("artifact %s preSync failed, continuing sync state check: %v", item.Artifact.ImageName, err)
    }
    return err
}

Prevention

When it happens

Trigger: Any `sync.lifecycleHooks.preSync` exec or container hook returns non-zero, times out, or errors creating the hook environment (bad kube context, missing namespace, invalid hook spec) during a sync triggered by file change.

Common situations: preSync exec command references a binary absent in the container image; hook pod targets a namespace that doesn't exist; kube context switched to a cluster where the pod isn't running; container hook times out on slow startup.

Related errors


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