ruvnet/ruflo · error · Error

Invalid schema name: "${schema}". Must contain only letters,

Error message

Invalid schema name: "${schema}". Must contain only letters, digits, and underscores, and start with a letter or underscore.

What it means

Thrown by validateSchemaName() when the schema fails VALID_PG_IDENTIFIER (`^[a-zA-Z_][a-zA-Z0-9_]*$`). This is a primary SQL-injection guard: schemas are interpolated into DDL/DML, so any character outside the identifier grammar (quotes, semicolons, dashes, dots) is refused rather than escaped.

Source

Thrown at v3/@claude-flow/cli/src/commands/ruvector/pg-utils.ts:28

 * Allows only ASCII letters, digits, and underscores.
 * Must start with a letter or underscore.
 */
const VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

/**
 * Validate a PostgreSQL schema name.
 * Throws if the name contains characters that could enable SQL injection.
 * Safe names are returned as-is (no quoting needed since they match the identifier pattern).
 */
export function validateSchemaName(schema: string): string {
  if (!schema || schema.length === 0) {
    throw new Error('Schema name must not be empty');
  }
  if (schema.length > 63) {
    throw new Error(`Schema name too long (${schema.length} chars, max 63): "${schema}"`);
  }
  if (!VALID_PG_IDENTIFIER.test(schema)) {
    throw new Error(
      `Invalid schema name: "${schema}". Must contain only letters, digits, and underscores, and start with a letter or underscore.`
    );
  }
  return schema;
}

/**
 * Validate a PostgreSQL timestamp string.
 * Only allows ISO 8601 format to prevent SQL injection via timestamp fields.
 */
const VALID_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/;

export function validateTimestamp(value: string): string {
  if (!VALID_TIMESTAMP.test(value)) {
    throw new Error(`Invalid timestamp format: "${value}". Expected ISO 8601.`);
  }
  return value;
}

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Use only ASCII letters, digits, and underscores; start with a letter or underscore.
  2. Convert kebab-case to snake_case: `name.replace(/-/g,'_')`.
  3. Split qualified names and validate each segment separately rather than passing `a.b`.

Example fix

// before
validateSchemaName('my-org')
// after
validateSchemaName('my_org')
Defensive patterns

Strategy: validation

Validate before calling

const VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
function toPgIdentifier(name: string): string {
  const cleaned = name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]+/, '_');
  if (!VALID_PG_IDENTIFIER.test(cleaned)) {
    throw new Error(`Invalid schema name: ${name}`);
  }
  return cleaned;
}

Type guard

const isPgIdentifier = (v: unknown): v is string =>
  typeof v === 'string' && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v);

Try / catch

try {
  validateSchemaName(schema);
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (msg.startsWith('Invalid schema name')) {
    schema = schema.replace(/-/g, '_');
    validateSchemaName(schema); // retry once, cleaned
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a schema containing dots (namespace.schema), dashes (kebab-case tenant), quotes, semicolons, spaces, or non-ASCII characters; or a schema starting with a digit.

Common situations: Tenant identifiers in kebab-case ('my-org'), fully-qualified names ('ruvector.public'), user input containing whitespace, or internationalized names with non-ASCII characters.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/dec5f9bd84e85a30. Report an issue: GitHub.