{"record":{"id":"abc7486ffce10340","repo":"docker/compose","slug":"service-q-pre-start-d-wait-error-s","errorCode":null,"errorMessage":"service %q pre_start[%d] wait error: %s","messagePattern":"service %q pre_start\\[(.+?)\\] wait error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/compose/pre_start.go","lineNumber":251,"sourceCode":"\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t}\n\t\t\treturn preStartResultErr(serviceName, index, res)\n\t\tcase err := <-errCh:\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// nil on Error: stream closed cleanly. Disable this case so a\n\t\t\t// closed channel can't fire repeatedly.\n\t\t\terrCh = nil\n\t\t}\n\t}\n}\n\nfunc preStartResultErr(serviceName string, index int, res container.WaitResponse) error {\n\tif res.Error != nil {\n\t\treturn fmt.Errorf(\"service %q pre_start[%d] wait error: %s\", serviceName, index, res.Error.Message)\n\t}\n\tif res.StatusCode != 0 {\n\t\treturn fmt.Errorf(\"service %q pre_start[%d] exited with code %d\", serviceName, index, res.StatusCode)\n\t}\n\treturn nil\n}\n\n// streamPreStartLogs returns a channel that is closed once the hook log stream\n// has been fully drained (or never opened). Callers must wait on it before\n// returning so the goroutine cannot outlive the hook.\nfunc (s *composeService) streamPreStartLogs(ctx context.Context, containerID string, service types.ServiceConfig, index int, listener api.ContainerEventListener) <-chan struct{} {\n\tdone := make(chan struct{})\n\tif listener == nil {\n\t\tclose(done)\n\t\treturn done\n\t}\n\tsource := fmt.Sprintf(\"%s pre_start[%d] ->\", service.Name, index)\n\tlogs, err := s.apiClient().ContainerLogs(ctx, containerID, client.ContainerLogsOptions{","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/pkg/compose/pre_start.go#L233-L269","documentation":"After a pre_start hook container exits, compose reads the container wait response. If the daemon reports an error for the wait itself (res.Error set — e.g. the container died abnormally), the daemon's message is surfaced with the service name and hook index. This is distinct from a clean non-zero exit: the container failed at the runtime level.","triggerScenarios":"ContainerWait returning a WaitResponse with Error set: hook container OOM-killed, killed by the daemon, or an engine-side error while the hook ran.","commonSituations":"Memory-hungry init scripts exceeding container memory limits; cgroup/ulimit kills; daemon instability during hook execution.","solutions":["docker inspect the hook container: check OOMKilled, ExitCode, and the wait error detail","Raise mem_limit/resources for the hook or reduce the hook's memory footprint","Check daemon logs (journalctl -u docker) for the wait error cause","Fix whatever the daemon's Error.Message names (it is included verbatim in the compose error)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := upWithPreStart(ctx, project); err != nil {\n    if strings.Contains(err.Error(), \"pre_start\") && strings.Contains(err.Error(), \"wait error\") {\n        // daemon-side failure: inspect hook container for OOMKilled/kill cause\n    }\n}","preventionTips":["Set generous memory limits for hook containers running heavy init scripts","Inspect the hook container (OOMKilled, ExitCode) whenever a wait error appears"],"tags":["pre-start","hooks","wait","oom"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}