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.File
orhudson.FilePath
are 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.