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
- Set the preset to one of the three valid names: 'strict', 'standard', or 'permissive' (exact, lowercase).
- Check for typos and case — the lookup is a plain object-key check, so 'Standard' fails.
- If upgrading, diff your config against the PRESETS keys in the installed src/permission/permission-set.ts.
- 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
- Validate preset names at config load with the exact three-value list; treat anything else as a startup error.
- Add a JSON-schema enum for the preset field if configs are machine-generated.
- After upgrading @claude-flow/cli, re-check the PRESETS keys in permission-set.ts before shipping old configs.
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
- Dangerous key segment rejected
- Dual-mode config must export a workers array
- Key exceeds maximum nesting depth of
- localCompute: no adapter for graphId=
- Model is required for
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)