{"record":{"id":"76da495c05eaa773","repo":"dagger/dagger","slug":"source-path-q-is-a-directory","errorCode":null,"errorMessage":"source path %q is a directory","messagePattern":"source path %q is a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/layercopy/copy_linux.go","lineNumber":166,"sourceCode":"\t\t}\n\t} else if destExists && destInfo.IsDir() {\n\t\tdestPath = filepath.Join(destPath, filepath.Base(src.baseView))\n\t}\n\treturn c.copyEntry(ctx, src, matcher, root, destPath, opts, matchState{}, nil, resolvedParent{})\n}\n\nfunc (c *Copier) copyFile(ctx context.Context, src *source, srcPath, destPath string, opts CopyOptions) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn context.Cause(ctx)\n\tdefault:\n\t}\n\n\tif err := src.selectBase(srcPath, true); err != nil {\n\t\treturn err\n\t}\n\tif src.baseInfo.IsDir() {\n\t\treturn fmt.Errorf(\"source path %q is a directory\", srcPath)\n\t}\n\tdestPath = cleanContainerPath(destPath)\n\tif opts.DestPathHintIsDir {\n\t\tif _, _, err := c.dest.ensureDir(destPath, nil, CopyOptions{Chown: opts.Chown}, false); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdestPath = filepath.Join(destPath, filepath.Base(cleanContainerPath(srcPath)))\n\t} else if destInfo, exists, err := c.dest.statView(destPath); err != nil {\n\t\treturn err\n\t} else if exists && destInfo.IsDir() {\n\t\tdestPath = filepath.Join(destPath, filepath.Base(cleanContainerPath(srcPath)))\n\t}\n\n\tent := sourceEntry{\n\t\tRel:      \"\",\n\t\tViewPath: src.baseView,\n\t\tRealPath: src.baseReal,\n\t\tInfo:     src.baseInfo,","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/util/layercopy/copy_linux.go#L148-L184","documentation":"copyFile in util/layercopy refuses to copy when the selected source path is a directory: after selectBase resolves srcPath, src.baseInfo.IsDir() short-circuits with this error. CopyFile is for single-file copies only; directory copies must go through the layer's directory copy path instead.","triggerScenarios":"Calling util/layercopy.CopyFile with opts.SrcPath (or the container-resolved source) pointing at a directory inside the source layer/image; a config error where a directory was given where a file path was expected.","commonSituations":"Dockerfile/container COPY-style operations where the user gave a directory as the source but the engine dispatched the single-file copy routine; typos or wrong glob expansion resolving a directory instead of the intended file.","solutions":["Pass a specific file path (including the filename) as the source, not a directory.","Use the directory-copy API/entry point for directories instead of CopyFile.","Verify the path inside the source image is actually a file (`ls` the layer/container path).","If the path is dynamic, stat it first and branch to the directory copy routine when IsDir()."],"exampleFix":"// before\nerr := layercopy.CopyFile(ctx, src, dest, layercopy.CopyOptions{SrcPath: \"/app/assets\"}) // directory\n// after\nerr := layercopy.CopyDir(ctx, src, dest, layercopy.CopyOptions{SrcPath: \"/app/assets\"})","handlingStrategy":"validation","validationCode":"info, err := src.Stat(srcPath)\nif err != nil { return err }\nif info.IsDir() {\n\treturn useDirectoryCopy(srcPath)\n}","typeGuard":"func isRegularFile(info os.FileInfo) bool { return info.Mode().IsRegular() }","tryCatchPattern":"err := layercopy.CopyFile(ctx, src, dest, opts)\nif err != nil && strings.Contains(err.Error(), \"is a directory\") {\n\treturn layercopy.CopyDir(ctx, src, dest, opts) // or reject with a clear user message\n}","preventionTips":["Validate that the source path is a file before dispatching to CopyFile.","Route directory sources to the directory copy routine by design, not error.","Include the filename in config-derived source paths (no bare directory hints).","Test layer-copy configs against paths that may change type between versions."],"tags":["filesystem","copy","input-validation","linux"],"backgroundTag":"source-is-a-directory","analyzedSha":"82ba2681dbe30d3547a1dc50ea495900ab5b6047","analyzedAt":"2026-09-05T07:21:37.930Z","contentChangedAt":"2026-09-05T07:21:37.930Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}