{"record":{"id":"3e14f9f9a28d8bca","repo":"vxcontrol/pentagi","slug":"failed-to-purge-container-s-w","errorCode":null,"errorMessage":"failed to purge container '%s': %w","messagePattern":"failed to purge container '(.+?)': %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/tools.go","lineNumber":765,"sourceCode":"\n\treturn flowfiles.FlowResourcesDir(dataDir, uint64(fte.flowID)), nil\n}\n\nfunc (fte *flowToolsExecutor) Release(ctx context.Context) error {\n\tif fte.store != nil {\n\t\t// Do NOT close the store when it is backed by the shared pgxpool — the pool\n\t\t// outlives individual flows and is shared by all executors. Only close when\n\t\t// the store owns its own connection (no shared pool configured).\n\t\tif fte.cfg.PgxPool == nil {\n\t\t\tfte.store.Close()\n\t\t}\n\t\tfte.store = nil\n\t}\n\n\t// TODO: here better to get flow containers list and purge all of them\n\tif err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {\n\t\tcontainerName := PrimaryTerminalName(fte.cfg.TenantPrefix(), fte.flowID)\n\t\treturn fmt.Errorf(\"failed to purge container '%s': %w\", containerName, err)\n\t}\n\n\treturn nil\n}\n\nfunc (fte *flowToolsExecutor) GetCustomExecutor(cfg CustomExecutorConfig) (ContextToolsExecutor, error) {\n\tif len(cfg.Definitions) != len(cfg.Handlers) {\n\t\treturn nil, fmt.Errorf(\"definitions and handlers must have the same length\")\n\t}\n\n\tfor _, def := range cfg.Definitions {\n\t\tif _, ok := cfg.Handlers[def.Name]; !ok {\n\t\t\treturn nil, fmt.Errorf(\"handler for function %s not found\", def.Name)\n\t\t}\n\t}\n\n\tfor _, builtin := range cfg.Builtin {\n\t\tif def, ok := fte.definitions[builtin]; !ok {","sourceCodeStart":747,"sourceCodeEnd":783,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/tools.go#L747-L783","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Inspect the wrapped cause: if it is 'no such container', treat the purge as already done and ignore/retry Release.","Verify the Docker daemon is up (docker ps) and the container exists (docker ps -a --filter name=<tenant-prefix>-flow-<id>).","Manually remove the orphaned container: docker rm -f <containerName>, then rerun flow cleanup.","If RemoveContainer retries/removes are flaky, add idempotent handling in Release for ErrNotFound from the Docker client."],"exampleFix":"// before\nif err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {\n    return fmt.Errorf(\"failed to purge container '%s': %w\", containerName, err)\n}\n\n// after\nif err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {\n    if cerr, ok := err.(dockererr.NotFound); ok { // container already gone\n        return nil\n    }\n    return fmt.Errorf(\"failed to purge container '%s': %w\", containerName, err)\n}","handlingStrategy":"try-catch","validationCode":"// before Release\nout, err := exec.Command(\"docker\", \"ps\", \"-a\", \"--filter\", \"name=\"+containerName, \"--format\", \"{{.ID}}\").Output()\n// if empty output and no error, the container is already gone; skip removal\n","typeGuard":null,"tryCatchPattern":"if err := executor.Release(ctx); err != nil {\n    var nerr docker.NoSuchContainerError\n    if errors.As(err, &nerr) {\n        return nil // already purged\n    }\n    log.WithError(err).Warn(\"flow container purge failed; orphan may remain\")\n}","preventionTips":["Make Release idempotent: treat 'no such container' as success.","Ensure the Docker daemon is healthy before starting flows; monitor it.","Periodically reconcile orphaned flow containers via a cleanup job listing containers by tenant prefix."],"tags":["docker","go","container-lifecycle"],"backgroundTag":"container-removal-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}