ruvnet/ruflo · error
build input escapes repository: ${path}
Error message
build input escapes repository: ${path} What it means
Each declared build input is resolved to its realpath and required to stay under repoRoot (itself realpath'd). If the entry — or any symlink chain — resolves outside the repository, relative(repoRoot, real) starts with '..' (or is absolute) and the path is rejected. This prevents declared inputs from binding bytes the repository snapshot does not contain. Toolchain declarations are exempt by design: their paths may be absolute.
Source
Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:161
* declaration set is complete and does not sign or authorize a release.
*/
export function captureBuildEvidence(
repoPath: string,
sourceState: ExactSourceState,
buildInputs: readonly BuildInputDeclaration[],
toolchains: readonly ToolchainDeclaration[],
): BuildEvidence {
const repoRoot = realpathSync(resolve(repoPath));
if (captureRepositorySourceState(repoRoot).sourceStateId !== sourceState.sourceStateId) {
throw new Error('Git-visible source state does not match the build evidence request');
}
const inputs = buildInputs.map((declaration): DeclaredBuildInput => {
const path = normalizePath(declaration.path);
const absolute = resolve(repoRoot, path);
const real = realpathSync(absolute);
const rel = relative(repoRoot, real);
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
throw new Error(`build input escapes repository: ${path}`);
}
return { name: declaration.name, path, ...digestPath(real, true) };
});
const tools = toolchains.map((declaration): DeclaredToolchain => ({
name: declaration.name,
version: declaration.version,
digest: digestPath(
isAbsolute(declaration.path) ? declaration.path : resolve(repoRoot, declaration.path),
true,
).digest,
}));
return createBuildEvidence(sourceState, inputs, tools);
}
export function recomputeBuildEvidence(
repoPath: string,
expected: BuildEvidence,
toolchains: readonly ToolchainDeclaration[],View on GitHub (pinned to fa13ee4ad6)
Solutions
- Replace the symlink with the real file stored inside the repository (vendor it) so the input is self-contained
- Declare the external artifact as a ToolchainDeclaration instead — toolchain paths may be absolute and are hashed rather than embedded as inputs
- Repoint the symlink at a copy kept under the repo root
Example fix
// before
// config/shared.json is a symlink to ~/.config/shared.json (outside the repo)
const buildInputs = [{ name: 'shared-config', path: 'config/shared.json' }];
// after
// copy the file into the repo, then declare the real file
const buildInputs = [{ name: 'shared-config', path: 'config/shared.json' }]; // regular file now
// or declare it as a toolchain with an absolute path:
const toolchains = [{ name: 'shared-config', version: '1', path: '/home/me/.config/shared.json' }]; Defensive patterns
Strategy: validation
Validate before calling
import { realpathSync } from 'node:fs';
import { relative, isAbsolute, resolve, sep } from 'node:path';
function inputStaysInRepo(repoRoot: string, relativePath: string): boolean {
const real = realpathSync(resolve(repoRoot, relativePath));
const rel = relative(realpathSync(repoRoot), real);
return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
} Try / catch
Catch and classify as a declaration defect: either vendor the file into the repo or move the declaration to toolchains (absolute paths allowed); retrying the same declaration always fails.
Prevention
- Audit inputs for symlinks (lstat) before declaring them, especially in pnpm/yarn layouts
- Keep a policy of repo-relative inputs and toolchain-only external artifacts
- Run capture in a fresh clone to surface environment-specific links early
When it happens
Trigger: A declared input that is a symlink into node_modules or a global cache outside the repo; a symlinked monorepo package directory pointing at a sibling checkout; a build step that created convenience links to /tmp artifacts which are then declared as inputs.
Common situations: pnpm/yarn-style symlinked dependency layouts inside the repo; developers symlinking shared config from a dotfiles repo; declaring a file inside a linked local package.
Related errors
- localSingleEntryPageRank: sourceIndex ${src} out of range [0
- unsafe build input path: ${value}
- Path traversal blocked: ${realResolved}
- Path traversal blocked: ${resolved}
- unknown strategy "${name}". Available: ${roster.map((r) => r
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/864016e3c1d88030.
Report an issue: GitHub.