containerd/containerd · error

can't find container for TaskExit event: %w

Error message

can't find container for TaskExit event: %w

What it means

HandleEvent returns this when a TaskExit event's container ID cannot be retrieved from the CRI container store with an error other than NotFound. NotFound is treated as benign (container already forgotten); other errors indicate the store lookup itself failed. Note the ID (not ContainerID) is used to exclude exec-process exits.

Source

Thrown at internal/cri/server/events.go:338

// HandleEvent handles a containerd event.
func (ce *criEventHandler) HandleEvent(any any) error {
	ctx := ctrdutil.NamespacedContext()
	ctx, cancel := context.WithTimeout(ctx, handleEventTimeout)
	defer cancel()

	switch e := any.(type) {
	case *eventtypes.TaskExit:
		log.L.Infof("TaskExit event %+v", e)
		// Use ID instead of ContainerID to rule out TaskExit event for exec.
		cntr, err := ce.c.containerStore.Get(e.ID)
		if err == nil {
			if err := ce.c.handleContainerExit(ctx, e, cntr, cntr.SandboxID); err != nil {
				return fmt.Errorf("failed to handle container TaskExit event: %w", err)
			}
			return nil
		} else if !errdefs.IsNotFound(err) {
			return fmt.Errorf("can't find container for TaskExit event: %w", err)
		}
		sb, err := ce.c.sandboxStore.Get(e.ID)
		if err == nil {
			if err := ce.c.handleSandboxExit(ctx, sb, e.ExitStatus, e.ExitedAt.AsTime()); err != nil {
				return fmt.Errorf("failed to handle sandbox TaskExit event: %w", err)
			}
			return nil
		} else if !errdefs.IsNotFound(err) {
			return fmt.Errorf("can't find sandbox for TaskExit event: %w", err)
		}
		return nil
	case *eventtypes.SandboxExit:
		log.L.Infof("SandboxExit event %+v", e)
		sb, err := ce.c.sandboxStore.Get(e.GetSandboxID())
		if err == nil {
			if err := ce.c.handleSandboxExit(ctx, sb, e.ExitStatus, e.ExitedAt.AsTime()); err != nil {
				return fmt.Errorf("failed to handle sandbox TaskExit event: %w", err)
			}

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Check containerd logs for metadata store errors at the same time
  2. Restart containerd to recover transient store failures
  3. Let event backoff retry the event after transient failure
  4. Run ctr c ls / crictl ps to verify store integrity after recovery
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: treat NotFound as benign, everything else needs investigation:
cntr, err := containerStore.Get(e.ID)
if err != nil {
    if errdefs.IsNotFound(err) {
        return nil
    }
    return fmt.Errorf("can't find container for TaskExit event: %w", err)
}

Type guard

func lookupFailed(err error) bool {
    return err != nil && !errdefs.IsNotFound(err)
}

Try / catch

if err := HandleEvent(ev); err != nil {
    if lookupFailed(err) {
        log.L.WithError(err).Errorf("store lookup failure; daemon health check needed")
        monitor.Backoff(id, ev)
    }
}

Prevention

When it happens

Trigger: containerStore.Get(e.ID) returns a non-NotFound error while dispatching *eventtypes.TaskExit — e.g. metadata DB failure, store corruption, or transport error.

Common situations: Bolt metadata DB contention or failure; containerd restart mid-event stream; corrupted container store after unclean shutdown.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/ed1c94961acbd827. Report an issue: GitHub.