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

  1. Read the full worker/job logs above this message — pip's real error (resolver conflict, build failure, network) is printed there
  2. Simplify/relax requirement version pins so the resolver can find a compatible set
  3. Retry after checking worker disk space and network access to the package index
  4. Use a prebuilt python-based worker image that already contains your dependencies to skip install at run time
  5. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/db681d9af64b7c6a. Report an issue: GitHub.