windmill-labs/windmill · error · Error
file path escapes the build directory: ${rel}
Error message
file path escapes the build directory: ${rel} What it means
When a base-env-derived reduced install path fails, Windmill falls back to building the environment and reports this generic error if that fallback did not succeed. It signals the whole python environment preparation failed; the actionable detail is expected to be in the preceding logs.
Source
Thrown at backend/windmill-api/src/apps_raw_bundler.ts:41
// Set unless the server was told to build with a specific command.
prefer_installed_cli: boolean | undefined,
// The app's `value.runnables`, whose policy is derived here for the same
// reason the bundle is built here: it has to match what the editor writes.
runnables: Record<string, unknown> | undefined
): Promise<{ js_gz: string; css_gz: string; triggerables_v2: Record<string, unknown> }> {
const fs = await import('node:fs/promises')
const path = await import('node:path')
const dir = path.join(process.cwd(), 'wm_raw_app')
await fs.rm(dir, { recursive: true, force: true })
// Where a key lands, `path.join` normalising `./` and `..` away. Everything
// that reasons about a file goes through this, so nothing disagrees with what
// was actually written.
const target = (rel: string) => {
const abs = path.join(dir, rel.replace(/^\/+/, ''))
if (!abs.startsWith(dir + path.sep)) {
throw new Error(`file path escapes the build directory: ${rel}`)
}
return abs
}
const write = async (rel: string, content: string) => {
const abs = target(rel)
await fs.mkdir(path.dirname(abs), { recursive: true })
await fs.writeFile(abs, content)
}
for (const [p, content] of Object.entries(files ?? {})) {
await write(p, content)
}
// `ui/` next to the app is where `wmill app bundle` looks for the shared UI.
for (const [p, content] of Object.entries(shared_ui ?? {})) {
await write('ui/' + p.replace(/^\/+/, ''), content)
}
const manifest = path.join(dir, 'package.json')
const hasPackageJson = Object.keys(files ?? {}).some((p) => target(p) === manifest)View on GitHub (pinned to e474e8803c)
Solutions
- Read the full worker/job logs above this message — pip's real error (resolver conflict, build failure, network) is printed there
- Simplify/relax requirement version pins so the resolver can find a compatible set
- Retry after checking worker disk space and network access to the package index
- Use a prebuilt python-based worker image that already contains your dependencies to skip install at run time
- If OOM occurred, increase worker memory or install fewer deps per job
Example fix
// before (conflicting pins) requirements = "pandas==2.0.0 numpy==2.1.0" // after (compatible set) requirements = "pandas==2.2.2 numpy==2.0.1"
Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate requirements resolve (dry run)
const { execSync } = require('child_process');
function requirementsResolve(reqs, pyVer) {
try {
execSync(`uv pip compile -q --python-version ${pyVer} -`, { input: reqs.join('\n') });
return true;
} catch (e) { return false; }
} Try / catch
try {
await deployScript({ requirements, pythonVersion });
} catch (e) {
if (/Env installation did not succeed/.test(e.message)) {
// detail is in the logs — surface them before retrying
const logs = await getJobLogs(jobId);
throw new Error(`env build failed; pip said: ${logs.match(/(ERROR|error):.*/g)?.join('; ')}`);
}
throw e;
} Prevention
- Test-compile requirements locally (pip compile / uv pip compile) before deploying
- Avoid mutually conflicting pins; let a lockfile drive versions
- Prebuild dependencies into a custom worker image
- Check worker disk/network before large installs
When it happens
Trigger: handle_python_reqs attempts an env build (full or reduced limit) and the build/install routine returns Err; also thrown when a retry of the reduced install path fails after the first attempt already failed.
Common situations: pip resolution conflicts among the declared requirements; unsupported python version for a pinned package; network failure reaching PyPI; worker disk full; OOM during wheel build for source-only packages.
Related errors
- could not read the run of job ${id}: ${res.status} ${await r
- could not record run ${experiment_id}: ${res.status} ${await
- ${what} failed:\n${output}
- Failed to generate lockfile: ${message}
- duplicate MCP tool name(s): {', '.join(duplicates)}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/db681d9af64b7c6a.
Report an issue: GitHub.