GoogleContainerTools/skaffold · error

preparing job %v for execution

Error message

preparing job %v for execution

What it means

K8sTask.Exec in Skaffold's k8sjob action runner calls k8sjobutil.ForceJobDelete to delete any stale Job with the same name before applying the new Job manifest. If that forced delete (or the preparation it performs) fails, the error is wrapped as "preparing job <name> for execution". The new Job cannot be scheduled until the old one is gone, since Job names are immutable in a namespace.

Source

Thrown at pkg/skaffold/actions/k8sjob/task.go:103

	if err != nil {
		return err
	}

	c := t.getContainerToDeploy()
	var original corev1.Container
	for _, ec := range t.jobManifest.Spec.Template.Spec.Containers {
		if ec.Name == c.Name {
			original = ec
			break
		}
	}
	patchToK8sContainer(c, &original)

	t.jobManifest.Spec.Template.Spec.Containers = []corev1.Container{original}
	t.jobManifest.ObjectMeta.Name = t.Name()

	if err := k8sjobutil.ForceJobDelete(ctx, t.jobManifest.Name, jm, t.kubectl); err != nil {
		return errors.Wrap(err, fmt.Sprintf("preparing job %v for execution", t.jobManifest.Name))
	}

	t.execEnv.TrackContainerAndJobFromBuild(graph.Artifact{
		ImageName: t.jobManifest.Name,
		Tag:       t.jobManifest.Name,
	}, tracker.Job{Name: t.jobManifest.Name, ID: t.jobManifest.Name}, &t.jobManifest)

	if err := t.deployJob(ctx, t.jobManifest, jm); err != nil {
		return err
	}

	if err = t.watchStatus(ctx, t.jobManifest, jm); err != nil {
		t.execEnv.logger.CancelJobLogger(t.jobManifest.Name)
		k8sjobutil.ForceJobDelete(context.TODO(), t.jobManifest.Name, jm, t.kubectl)
	}

	return err
}

View on GitHub (pinned to a1189de023)

Solutions

  1. Check the unwrapped kubectl error; run `kubectl -n <ns> get job <name> -o yaml` and remove blocking finalizers if stuck Terminating.
  2. Manually delete the stale job (`kubectl -n <ns> delete job <name> --force --grace-period=0`) then rerun.
  3. Grant the active identity RBAC: `rules: [{apiGroups: [batch], resources: [jobs], verbs: ["delete", "create"]}]`.
  4. Verify kubeconfig context/namespace (kubectl config current-context) match the intended cluster.
  5. Retry if the API server was transiently unreachable.

Example fix

// before
if err := task.Exec(ctx, out); err != nil { return err }
// after
// pre-check stale job before Exec
err := exec.Command("kubectl", "-n", ns, "delete", "job", name, "--ignore-not-found").Run()
if err != nil { return fmt.Errorf("stale job cleanup failed: %w", err) }
if err := task.Exec(ctx, out); err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

# ensure stale job is gone before Exec
kubectl -n $NS delete job $JOB --ignore-not-found
kubectl -n $NS wait --for=delete job/$JOB --timeout=60s || true

Try / catch

// Go
err := task.Exec(ctx, out)
if err != nil && strings.Contains(err.Error(), "finalizer") {
    // retry after unblocking finalizers / forced delete
    err = task.Exec(ctx, out)
}

Prevention

When it happens

Trigger: Calling Exec when ForceJobDelete errors: a previous Job with the same name exists and cannot be deleted (finalizers blocking deletion, API server unreachable, RBAC forbids delete on jobs.batch, or bad kubecontext/namespace).

Common situations: A prior run's Job is stuck Terminating due to a finalizer; the service account running skaffold lacks delete permission on jobs; kubeconfig points at the wrong cluster/namespace; API server temporarily unavailable.

Related errors


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