ruvnet/ruflo · error

build evidence path is not a file or symlink: ${path}

Error message

build evidence path is not a file or symlink: ${path}

What it means

To hash a declared build input, digestPath lstat's the (realpath-resolved) entry and reads content only for regular files and symlinks — a symlink contributes its target string as content. Any other filesystem node (directory, fifo, socket, device) yields no content and throws this error. A missing path fails earlier with the underlying ENOENT from lstatSync, not this error.

Source

Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:90

  ) {
    throw new Error(`unsafe build input path: ${value}`);
  }
  return path;
}

function sha256(value: string): string {
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
}

function digestPath(path: string, followSymlink: boolean): { digest: string; bytes: number } {
  const resolved = followSymlink ? realpathSync(path) : path;
  const stat = lstatSync(resolved);
  const content = stat.isSymbolicLink()
    ? Buffer.from(readlinkSync(resolved), 'utf8')
    : stat.isFile()
      ? readFileSync(resolved)
      : undefined;
  if (!content) throw new Error(`build evidence path is not a file or symlink: ${path}`);
  return {
    digest: `sha256:${createHash('sha256').update(content).digest('hex')}`,
    bytes: content.byteLength,
  };
}

/**
 * Bind explicitly declared non-Git inputs and toolchains to one Git-visible
 * source state. This does not claim completeness; policy decides which
 * declarations are required for a release profile.
 */
export function createBuildEvidence(
  sourceState: ExactSourceState,
  buildInputs: readonly DeclaredBuildInput[],
  toolchains: readonly DeclaredToolchain[],
): BuildEvidence {
  const inputs = buildInputs.map((input) => ({
    name: requireText(input.name, 'build input name'),

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Point the declaration at a concrete file — a manifest, archive, or lockfile — rather than a directory
  2. If the artifact is a directory, first archive or manifest it and declare that file
  3. For executables or images outside the repo, declare them as toolchains instead — toolchain paths may be absolute

Example fix

// before
const buildInputs = [{ name: 'dist', path: 'dist' }]; // directory

// after
const buildInputs = [{ name: 'dist-manifest', path: 'dist/manifest.json' }];
Defensive patterns

Strategy: validation

Validate before calling

import { lstatSync } from 'node:fs';
function isHashableInput(absolutePath: string): boolean {
  const stat = lstatSync(absolutePath);
  return stat.isFile() || stat.isSymbolicLink();
}

Prevention

When it happens

Trigger: Declaring a directory as a build input (e.g. 'dist' or 'node_modules'); a path that is a fifo or socket created by a running dev server; a symlink chain whose final target is a directory.

Common situations: Declaring folder-shaped artifacts such as 'dist' or 'build' instead of a concrete file; running captureBuildEvidence while a dev server holds socket files inside the repo; symlink chains that resolve to a directory.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/e335a7d3fd625059. Report an issue: GitHub.