argoproj/argo-workflows · error
failed to symlink input artifact %q (%s -> %s): %w
Error message
failed to symlink input artifact %q (%s -> %s): %w
What it means
After clearing (or confirming absence of) the destination, argoexec creates the symlink src -> dst for the input artifact. If os.Symlink fails, this wrapped error is returned. It usually wraps EEXIST (a path component raced or the clear was skipped) or permission/errno errors on the parent directory.
Source
Thrown at cmd/argoexec/commands/emissary.go:438
// mounts (tmpfs /run, the overlay rootfs) are not declared user volumes
// and so remain safe to shadow.
realParent, evalErr := filepath.EvalSymlinks(filepath.Dir(dst))
if evalErr != nil {
return fmt.Errorf("failed to resolve parent of artifact path %q at %s: %w", art.Name, dst, evalErr)
}
resolved := filepath.Join(realParent, filepath.Base(dst))
if mnt := common.FindOverlappingVolume(tmpl, resolved); mnt != nil {
return fmt.Errorf("refusing to stage input artifact %q at %s: it resolves to %s inside volume mount %q (%s), and clearing it would destroy the mounted volume; change the artifact path or volume mount so they do not overlap", art.Name, dst, resolved, mnt.Name, mnt.MountPath)
}
if mnt := common.FindVolumeMountNestedUnderPath(tmpl, resolved); mnt != nil {
return fmt.Errorf("refusing to stage input artifact %q at %s: it resolves to %s which contains volume mount %q (%s), and clearing it would destroy the mounted volume; change the artifact path or volume mount so they do not overlap", art.Name, dst, resolved, mnt.Name, mnt.MountPath)
}
if rmErr := os.RemoveAll(dst); rmErr != nil {
return fmt.Errorf("failed to clear existing path for artifact %q at %s: %w", art.Name, dst, rmErr)
}
}
if err := os.Symlink(src, dst); err != nil {
return fmt.Errorf("failed to symlink input artifact %q (%s -> %s): %w", art.Name, dst, src, err)
}
logger.WithFields(logging.Fields{"name": art.Name, "src": src, "dst": dst}).Debug(ctx, "linked input artifact")
}
return nil
}
// waitForSupervisorReady blocks until the supervisor's status marker reports a
// terminal outcome (READY/FAILED), or until the supervisor is presumed dead.
// Used only in init-less pod mode where main and supervisor start concurrently.
// VarRunArgoPath itself is guaranteed to exist because the emissary has
// already created /var/run/argo/ctr/<name> earlier in main, which MkdirAll'd
// the full parent chain.
func waitForSupervisorReady(ctx context.Context) error {
return waitForSupervisorReadyAt(ctx, common.StatusMarkerPath, supervisorHeartbeatTimeout, supervisorStatusPollInterval)
}
// waitForSupervisorReadyAt is the parameterized form used by tests; production
// calls waitForSupervisorReady with the constants.View on GitHub (pinned to 35bff19146)
Solutions
- Check the wrapped errno in the full error for the precise cause (EEXIST vs EACCES vs EPERM)
- Ensure the parent directory of `path` is writable by the container user or run with a securityContext granting write access
- Pick a destination on a symlink-capable filesystem (emptyDir, overlayfs)
- Avoid concurrent writers to the same artifact path within the container
Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure destination parent is writable and filesystem supports symlinks: # in an init/debug step: test -w "$(dirname /tmp/app/config.yaml)" || echo "parent not writable" mkdir -p /tmp/app && ln -s /tmp/probe /tmp/app/.probe 2>&1 || echo "symlinks unsupported"
Try / catch
err := linkInputArtifacts(ctx, tmpl)
if err != nil {
var perr *os.LinkError
if errors.As(err, &perr) {
log.Printf("symlink failed op=%q old=%q new=%q err=%v", perr.Op, perr.Old, perr.New, perr.Err)
// EACCES -> fix permissions; EEXIST -> concurrent writer; EPERM -> fs lacks symlink support
}
return err
} Prevention
- Run containers with a user that owns the destination directory
- Avoid FAT/exFAT or fuse/network mounts as artifact destinations
- Don't run concurrent processes that touch the same artifact path
- Check the wrapped errno in the error message to pick the right fix
When it happens
Trigger: os.Symlink(src, dst) errors during linkInputArtifactsAt: dst reappeared between RemoveAll and Symlink, the parent is not writable, or dst sits on a filesystem that does not support symlinks (e.g. some network filesystems).
Common situations: Non-root container writing to a root-owned directory; FAT/exFAT or certain fuse/network mounts lacking symlink support; concurrent processes recreating the destination path; SELinux/AppArmor denial.
Related errors
- failed to create parent directory for artifact %q at %s: %w
- failed to stat artifact path %q at %s: %w
- failed to resolve parent of artifact path %q at %s: %w
- failed to clear existing path for artifact %q at %s: %w
- failed to leave working directory before staging input artif
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/c7220cd41af5e5b1.
Report an issue: GitHub.