ruvnet/ruflo · error · Error

contains shell metacharacters

Error message

${label} contains shell metacharacters

What it means

Thrown by validatePath() in the daemon command when a path argument (e.g. the --workspace flag) contains any of the shell metacharacters ; & | ` $ < >. The value is later embedded in a forked child's argv and compared against ps/tasklist output, so metacharacters are rejected as defence-in-depth against command injection. This is pure fail-fast input validation: the path is never used or written before the check.

Solutions

  1. Remove or rename path components containing ; & | ` $ < > — use plain alphanumerics, dashes, and underscores only
  2. If the $ comes from an unexpanded variable, expand it before invoking the CLI (double quotes or interpolate the value yourself)
  3. Pass a plain absolute path with no metacharacters, e.g. /home/user/project/.claude-flow
  4. Do not look for an escape hatch — validatePath intentionally offers none; file an issue if the constraint is blocking

Example fix

# before
claude-flow daemon start --workspace '/opt/data$2026'

# after
claude-flow daemon start --workspace '/opt/data-2026'
Defensive patterns

Strategy: validation

Validate before calling

import { resolve } from 'node:path';
function assertSafeDaemonPath(p: string, label = 'path'): void {
  if (p.includes('\0')) throw new Error(`${label}: null bytes`);
  if (/[;&|`$<>]/.test(p)) throw new Error(`${label}: shell metacharacters not allowed`);
}
assertSafeDaemonPath(workspace); // before invoking the daemon command

Type guard

function isSafeDaemonPath(p: unknown): p is string {
  return typeof p === 'string' && !p.includes('\0') && !/[;&|`$<>]/.test(p);
}

Try / catch

try {
  await cli.daemon.start({ workspace });
} catch (err) {
  if (err instanceof Error && err.message.endsWith('contains shell metacharacters')) {
    // reject/repair the path, then retry with a sanitized value
  } else throw err;
}

Prevention

When it happens

Trigger: Invoking `claude-flow daemon ... --workspace '/tmp/my$dir'` (literal $ from an unexpanded variable), or any daemon path argument containing ;, &, |, a backtick, $, <, or >. Single-quoted shell arguments that keep $VAR unexpanded are the classic producer.

Common situations: CI systems that create directories containing $ (some runner temp dirs), paths copy-pasted from markdown wrapped in backticks, scripts that single-quote variables so they arrive literally, or test fixtures with shell-looking strings.

Related errors


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

Appendix: source

Thrown at v3/@claude-flow/cli/src/commands/daemon.ts:377

    }
  },
};

/**
 * Validate path for security - prevents path traversal and injection
 */
function validatePath(path: string, label: string): void {
  // Must be absolute after resolution
  const resolved = resolve(path);

  // Check for null bytes (injection attack)
  if (path.includes('\0')) {
    throw new Error(`${label} contains null bytes`);
  }

  // Check for shell metacharacters in path components
  if (/[;&|`$<>]/.test(path)) {
    throw new Error(`${label} contains shell metacharacters`);
  }

  // Prevent path traversal outside expected directories
  if (!resolved.includes('.claude-flow') && !resolved.includes('bin')) {
    // Allow only paths within project structure
    const cwd = process.cwd();
    if (!resolved.startsWith(cwd)) {
      throw new Error(`${label} escapes project directory`);
    }
  }
}

/**
 * #1914: Resolve the `--workspace` flag to an absolute path, or return null
 * if it is absent / not a usable string. Rejects values with null bytes or
 * shell metacharacters (defence-in-depth — the value is later embedded in a
 * forked child's argv and compared against `ps`/`tasklist` output).
 */

View on GitHub (pinned to fa13ee4ad6)