Platform-Agnostic Path Handling in Jenkins Pipelines

Dhruvakumar Jonnagaddala
This guide provides practical guidance and examples for writing cross-platform Jenkins Pipelines that avoid sandbox approval issues and remain maintainable.
Platform-Agnostic Path Handling in Jenkins Pipelines
When you're shipping the same pipeline across Linux and Windows agents, the smallest thing—like a path separator; can trip you up. If you try to "fix it in Groovy" with new File(...), hudson.FilePath, or static calls like File.separator, Jenkins' Groovy Sandbox will often block those calls by default. That's on purpose and for good reasons.
This guide provides practical guidance and examples for writing cross-platform Jenkins Pipelines that avoid sandbox approval issues and remain maintainable. We avoid any methods that require @NonCPS because its presence makes the pipeline un-serializable. If Jenkins tries to pause or restart during that method, it cannot resume from the middle; it has to re-run it.
Why the Sandbox Blocks File APIs
Script Security: Jenkins runs untrusted Pipeline Groovy in a restricted sandbox. Calls to
java.io.Fileorhudson.FilePathare blocked unless approved by an admin.FilePath is powerful: It can run remote operations on agents or controllers—too risky to allow in untrusted Pipelines.
sh / bat are whitelisted: These steps spawn external processes on the agent and don't give Groovy access to Jenkins internals.
Best Practices
Treat Pipelines as ephemeral– avoid persisting state.
Prefer pipeline steps over APIs (
findFiles,readFile,writeFile).Normalize paths only when needed (before
sh/bat).Use shared libraries to abstract complexity.
Handle secrets safely with
withCredentials.
Examples
Simple Separator Handling
def sep = isUnix() ? '/' : '\\'
def dmPath = "${env.WORKSPACE}${sep}dev-tools${sep}dependency-managers"
def binPath = "${dmPath}${sep}bin"Discover Paths with findFiles
def matches = findFiles(glob: '**/bin/**')
def binDirs = matches.collect { it.path.replaceFirst(/\/bin\/.*$/, '/bin') }.unique()
def absPath = isUnix() ? "${env.WORKSPACE}/${binDirs[0]}" : ("${env.WORKSPACE}/${binDirs[0]}").replace('/', '\\')Triple-Quoted Scripts
if (isUnix()) {
sh """
"${absPath}/mytool" arg1
"""
} else {
bat """
"${absPath}\\mytool.exe" arg1
"""
}Shared Library Helpers
vars/osPath.groovy
def join(Object... parts) {def sep = isUnix() ? '/' : '\\'parts.findAll { it }.join(sep)}
Usage
def binAbs = osPath.join(env.WORKSPACE, "tools", "bin")References
Key Takeaway
Keep Groovy minimal and sandbox-friendly. Use findFiles + isUnix() for paths, normalize before calling sh/bat, and use a shared library when it gets repetitive. This ensures your pipelines remain portable, safe, and maintainable.

