{"record":{"id":"5c44bc8cec11e768","repo":"docker/compose","slug":"deleting-paths-in-s-w","errorCode":null,"errorMessage":"deleting paths in %s: %w","messagePattern":"deleting paths in (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sync/tar.go","lineNumber":105,"sourceCode":"\t\tdeleteCmd = append([]string{\"rm\", \"-rf\"}, pathsToDelete...)\n\t}\n\n\tvar (\n\t\teg    errgroup.Group\n\t\terrMu sync.Mutex\n\t\terrs  = make([]error, 0, len(containers)*2) // max 2 errs per container\n\t)\n\n\teg.SetLimit(16) // arbitrary limit, adjust to taste :D\n\tfor i := range containers {\n\t\tcontainerID := containers[i].ID\n\t\ttarReader := tarArchive(pathsToCopy)\n\n\t\teg.Go(func() error {\n\t\t\tif len(deleteCmd) != 0 {\n\t\t\t\tif err := t.client.Exec(ctx, containerID, deleteCmd, nil); err != nil {\n\t\t\t\t\terrMu.Lock()\n\t\t\t\t\terrs = append(errs, fmt.Errorf(\"deleting paths in %s: %w\", containerID, err))\n\t\t\t\t\terrMu.Unlock()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err := t.client.Untar(ctx, containerID, tarReader); err != nil {\n\t\t\t\terrMu.Lock()\n\t\t\t\terrs = append(errs, fmt.Errorf(\"copying files to %s: %w\", containerID, err))\n\t\t\t\terrMu.Unlock()\n\t\t\t}\n\t\t\treturn nil // don't fail-fast; collect all errors\n\t\t})\n\t}\n\n\t_ = eg.Wait()\n\treturn errors.Join(errs...)\n}\n\ntype ArchiveBuilder struct {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/sync/tar.go#L87-L123","documentation":"During Tar.Sync, when some host paths no longer exist, compose builds an 'rm -rf <paths>' command and execs it in each matching container via LowLevelClient.Exec before copying. If that exec fails for a container (non-zero exit, exec API failure, container restarting/paused), the error is collected (not fail-fast) and joined into the sync result. The %s names the offending container ID.","triggerScenarios":"Tar.Sync with at least one deleted host path (len(pathsToDelete) != 0) against a container that cannot run exec: container is paused, restarting, its runtime exec API errors, or 'rm' is absent/not executable in the image (distroless/scratch).","commonSituations":"Syncing deletions to minimal images without coreutils, containers mid-restart during 'docker compose watch', or a stopped-but-listed replica whose exec channel is closed.","solutions":["docker inspect <containerID> to check State (Paused/Restarting) and unpause or wait for it: docker unpause <id> or docker compose restart <service>","If the image is distroless/scratch, add rm (busybox) or avoid deletions in sync paths since exec rm -rf cannot work there","Check dockerd logs for exec API errors; restart the container if its exec channel is wedged","If deletions are not needed, ensure host paths in the mapping still exist so deleteCmd stays empty"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// only pass delete-mappings when containers can exec rm\nif len(deletedPaths) > 0 {\n    for _, c := range containers {\n        st, _ := client.ContainerInspect(ctx, c.ID)\n        if st.State == nil || st.State.Paused || st.State.Restarting { skipOrWait(c.ID) }\n    }\n}","typeGuard":null,"tryCatchPattern":"// Sync joins per-container errors; split with errors.Unwrap on the joined tree and match the 'deleting paths in <id>' prefix to target one container\nfor _, e := range errors.Unwrap(syncErr).([]error) { /* handle per container ID */ }","preventionTips":["Do not rely on deletion sync with distroless/scratch images","Keep synced containers running and unpaused during watch","Treat delete failures as retriable: rerun the sync after stabilizing the container"],"tags":["containers","exec","sync","tar","go"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}