{"record":{"id":"7356b02a3da408c0","repo":"argoproj/argo-workflows","slug":"failed-to-create-parent-directory-for-artifact-q","errorCode":null,"errorMessage":"failed to create parent directory for artifact %q at %s: %w","messagePattern":"failed to create parent directory for artifact %q at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/argoexec/commands/emissary.go","lineNumber":399,"sourceCode":"// its user volume.\nfunc linkInputArtifactsAt(ctx context.Context, baseDir string, tmpl *wfv1.Template) error {\n\tlogger := logging.RequireLoggerFromContext(ctx)\n\tfor _, art := range tmpl.Inputs.Artifacts {\n\t\tsrc := filepath.Join(baseDir, art.Name)\n\t\tif _, statErr := os.Lstat(src); statErr != nil {\n\t\t\tif os.IsNotExist(statErr) {\n\t\t\t\tlogger.WithFields(logging.Fields{\"name\": art.Name, \"path\": art.Path}).Info(ctx, \"no input-artifacts entry (optional or overlap) — skipping symlink\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to stat input artifact %q at %s: %w\", art.Name, src, statErr)\n\t\t}\n\t\tdst := art.Path\n\t\tif dst == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif parent := filepath.Dir(dst); parent != \"\" && parent != \"/\" {\n\t\t\tif err := os.MkdirAll(parent, 0o755); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create parent directory for artifact %q at %s: %w\", art.Name, dst, err)\n\t\t\t}\n\t\t}\n\t\t// If nothing exists at art.Path, just create the symlink. Creating is\n\t\t// always safe — os.Symlink returns EEXIST rather than overwriting and the\n\t\t// MkdirAll above only ever creates — so even when art.Path resolves into a\n\t\t// user volume we deliberately let the artifact land there (the user asked\n\t\t// for it). Only an *overwrite* can destroy data, and that is gated below.\n\t\tif _, err := os.Lstat(dst); err != nil {\n\t\t\tif !os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to stat artifact path %q at %s: %w\", art.Name, dst, err)\n\t\t\t}\n\t\t} else {\n\t\t\t// Something is already at art.Path. Replacing it (os.RemoveAll then\n\t\t\t// symlink) reproduces the legacy SubPath mount's shadowing — but only\n\t\t\t// when it is safe. RemoveAll resolves symlinks in the parent chain, so\n\t\t\t// resolve the parent to find where the delete would actually land\n\t\t\t// (resolve the parent, not the final element, so an image symlink *at*\n\t\t\t// art.Path is just unlinked rather than followed). If that resolved","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/cmd/argoexec/commands/emissary.go#L381-L417","documentation":"Before creating an artifact symlink, the executor creates the destination's parent directory with os.MkdirAll(parent, 0o755). Failure here is fatal and wrapped with the artifact name and destination path. It typically means the destination resolves somewhere the executor cannot write — most often a user volume mounted with restrictive permissions, or a read-only filesystem.","triggerScenarios":"os.MkdirAll fails for the artifact's destination parent: path lives on a read-only volume, the volume's fsGroup/permissions exclude the executor user (usually root), the path is invalid (e.g. a file exists where a directory component is expected), or the mount was unmounted.","commonSituations":"Artifacts writing into PVCs or emptyDir mounts with non-root securityContext and no fsGroup; subPath mounts pointing at files; hostPath volumes mounted read-only; artifact.path typos like /etc/passwd/foo making a path component a file.","solutions":["Read the wrapped errno: EACCES/EPERM → permissions; EROFS → read-only mount; ENOTDIR → a path component is a file.","For permission failures, set pod securityContext fsGroup/runAsUser or chmod the volume so the executor user can write.","Remove readOnly from the volumeMount that receives the artifact.","Correct artifact.path so it doesn't run through an existing file.","Verify the destination volume is mounted (kubectl describe pod → Mounts)."],"exampleFix":"// before\nsecurityContext:\n  runAsUser: 1000   # cannot write to root-owned volume\n// after\nsecurityContext:\n  runAsUser: 1000\n  fsGroup: 1000     # volume group-writable","handlingStrategy":"validation","validationCode":"// Pre-flight: can the executor user create dirs at the artifact parent?\nparent=$(dirname '<artifact.path>'); test -w \"$parent\" || { echo \"cannot write $parent\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":"try {\n  await stageArtifacts(tmpl)\n} catch (e) {\n  if (String(e).includes('failed to create parent directory')) {\n    // fix fsGroup/runAsUser or remove readOnly from the destination mount\n  }\n}","preventionTips":["Set securityContext.fsGroup on pods writing artifacts to volumes.","Never mount the artifact destination volume readOnly.","Validate artifact.path doesn't traverse an existing file.","Document artifact destination permissions in your platform templates."],"tags":["kubernetes","argo-workflows","artifacts","permissions","filesystem"],"backgroundTag":"mkdir-permission-denied","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}