ruvnet/ruflo · error

Unknown permissions preset

Error message

Unknown permissions preset: ${name}. Valid presets: ${valid}

What it means

resolvePreset(name) in permission-set.ts looks the name up in the PRESETS map, which defines exactly three presets: 'strict', 'standard', 'permissive'. An unknown name throws with the valid list appended. The throw is deliberate — fail loud so a typo in configuration cannot silently degrade to a permissive permission set.

Solutions

  1. Set the preset to one of the three valid names: 'strict', 'standard', or 'permissive' (exact, lowercase).
  2. Check for typos and case — the lookup is a plain object-key check, so 'Standard' fails.
  3. If upgrading, diff your config against the PRESETS keys in the installed src/permission/permission-set.ts.
  4. If you truly need a custom set, build PermissionSet objects explicitly instead of relying on a preset name.

Example fix

// before
{ permissions: { preset: 'safe' } } // throws: Unknown permissions preset: safe. Valid presets: strict, standard, permissive

// after
{ permissions: { preset: 'strict' } }
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_PRESETS = ['strict', 'standard', 'permissive'] as const;
function assertPreset(name: string): void {
  if (!VALID_PRESETS.includes(name as any)) {
    throw new Error(`Unknown permissions preset: ${name}. Valid presets: ${VALID_PRESETS.join(', ')}`);
}
// Run this on config load, before resolvePreset is ever reached.

Type guard

type PresetName = 'strict' | 'standard' | 'permissive';
const PRESET_NAMES: ReadonlySet<string> = new Set(['strict', 'standard', 'permissive']);
function isPresetName(name: unknown): name is PresetName {
  return typeof name === 'string' && PRESET_NAMES.has(name);
}

Try / catch

try {
  const sets = resolvePreset(config.permissions.preset);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Unknown permissions preset:')) {
    failConfigStartup(e.message); // refuse to boot — never substitute a default preset silently
  }
  throw e;
}

Prevention

When it happens

Trigger: Loading a config that sets the permissions preset to anything besides strict|standard|permissive — e.g. 'default', 'safe', 'locked', 'read-only', 'Standard' (case-sensitive), or a preset name from an older/newer version that was renamed or removed.

Common situations: Config files written against different documentation than the installed version; CI configs with a typo; downstream tools guessing preset names; presets renamed between releases leaving stale configs.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at v3/@claude-flow/cli/src/permission/permission-set.ts:138

      deniedTools: [],
      allowedPaths: ['**/*'],
      deniedPaths: [],
      allowedNetworkHosts: ['*'],
      notes: 'No restriction. Kept for opt-in compatibility; see #2768 for why this is not the default.',
    },
  ],
};

export type PresetName = keyof typeof PRESETS;

/**
 * Resolve a preset name to its per-role sets. Throws on unknown preset —
 * fail loud so a typo doesn't silently degrade to permissive.
 */
export function resolvePreset(name: string): PermissionSet[] {
  if (!(name in PRESETS)) {
    const valid = Object.keys(PRESETS).join(', ');
    throw new Error(`Unknown permissions preset: ${name}. Valid presets: ${valid}`);
  }
  return PRESETS[name as PresetName];
}

/**
 * Sanity-check a permission set: role name shape, no null entries.
 * Path validation is deferred to the enforcement side (PathValidator)
 * so this module has no @claude-flow/security runtime dep beyond a
 * type-only import.
 */
export function validatePermissionSet(set: PermissionSet, _validator?: PathValidator): string[] {
  const errors: string[] = [];
  if (!set.role || typeof set.role !== 'string') errors.push('role must be a non-empty string');
  const arrays: [keyof PermissionSet, unknown][] = [
    ['allowedTools', set.allowedTools],
    ['deniedTools', set.deniedTools],
    ['allowedPaths', set.allowedPaths],
    ['deniedPaths', set.deniedPaths],

View on GitHub (pinned to fa13ee4ad6)