vxcontrol/pentagi · error

failed to purge container '%s': %w

Error message

failed to purge container '%s': %w

What it means

flowToolsExecutor.Release removes the flow's primary terminal container via fte.docker.RemoveContainer; if the Docker API call fails, the error is wrapped with the container's computed name (PrimaryTerminalName(tenantPrefix, flowID)). It signals flow teardown could not purge the sandbox container, leaving an orphaned container behind.

Source

Thrown at backend/pkg/tools/tools.go:765

	return flowfiles.FlowResourcesDir(dataDir, uint64(fte.flowID)), nil
}

func (fte *flowToolsExecutor) Release(ctx context.Context) error {
	if fte.store != nil {
		// Do NOT close the store when it is backed by the shared pgxpool — the pool
		// outlives individual flows and is shared by all executors. Only close when
		// the store owns its own connection (no shared pool configured).
		if fte.cfg.PgxPool == nil {
			fte.store.Close()
		}
		fte.store = nil
	}

	// TODO: here better to get flow containers list and purge all of them
	if err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {
		containerName := PrimaryTerminalName(fte.cfg.TenantPrefix(), fte.flowID)
		return fmt.Errorf("failed to purge container '%s': %w", containerName, err)
	}

	return nil
}

func (fte *flowToolsExecutor) GetCustomExecutor(cfg CustomExecutorConfig) (ContextToolsExecutor, error) {
	if len(cfg.Definitions) != len(cfg.Handlers) {
		return nil, fmt.Errorf("definitions and handlers must have the same length")
	}

	for _, def := range cfg.Definitions {
		if _, ok := cfg.Handlers[def.Name]; !ok {
			return nil, fmt.Errorf("handler for function %s not found", def.Name)
		}
	}

	for _, builtin := range cfg.Builtin {
		if def, ok := fte.definitions[builtin]; !ok {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Inspect the wrapped cause: if it is 'no such container', treat the purge as already done and ignore/retry Release.
  2. Verify the Docker daemon is up (docker ps) and the container exists (docker ps -a --filter name=<tenant-prefix>-flow-<id>).
  3. Manually remove the orphaned container: docker rm -f <containerName>, then rerun flow cleanup.
  4. If RemoveContainer retries/removes are flaky, add idempotent handling in Release for ErrNotFound from the Docker client.

Example fix

// before
if err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {
    return fmt.Errorf("failed to purge container '%s': %w", containerName, err)
}

// after
if err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {
    if cerr, ok := err.(dockererr.NotFound); ok { // container already gone
        return nil
    }
    return fmt.Errorf("failed to purge container '%s': %w", containerName, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before Release
out, err := exec.Command("docker", "ps", "-a", "--filter", "name="+containerName, "--format", "{{.ID}}").Output()
// if empty output and no error, the container is already gone; skip removal

Try / catch

if err := executor.Release(ctx); err != nil {
    var nerr docker.NoSuchContainerError
    if errors.As(err, &nerr) {
        return nil // already purged
    }
    log.WithError(err).Warn("flow container purge failed; orphan may remain")
}

Prevention

When it happens

Trigger: Calling Release(ctx) when the primary container was already removed, doesn't exist (wrong fte.primaryID/LID), the Docker daemon is unreachable, or the container is in a state that refuses removal (e.g. removal already in progress, device or resource busy).

Common situations: Docker daemon restarted mid-flow; container already cleaned manually with docker rm; race where the flow container exited and was auto-removed; network/daemon timeouts during shutdown; stale primaryID after a container was recreated.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/3e14f9f9a28d8bca. Report an issue: GitHub.