affaan-m/ECC · error
INVALID_HOOK_MODE
INVALID_HOOK_MODE
Error message
Invalid hook mode: ${options.hooks} What it means
ClaudeSetupError with code INVALID_HOOK_MODE thrown by migrateClaudePluginScope in scripts/lib/claude-scope-migration.js:254, immediately after scope validation. When `options.hooks` is provided it must be one of VALID_HOOK_MODES (imported from claude-plugin-setup); any other hook-mode string is rejected before paths are resolved or settings are read. This guards the hook-installation strategy chosen during migration.
Solutions
- Run `ecc setup --help` (or inspect VALID_HOOK_MODES in scripts/lib/claude-plugin-setup.js) to list accepted hook modes and use one exactly.
- Omit the --hooks flag entirely to use the default hook mode — validation only runs when hooks is defined.
- Fix shell quoting/variable expansion if the value is coming from an environment variable that is empty or malformed.
- Update ECC if you are using hook-mode names from a different version's documentation.
Example fix
// before // ecc migrate-scope --scope project --hooks always-on // after (using a valid mode, or omitting the flag) // ecc migrate-scope --scope project --hooks standard // ecc migrate-scope --scope project
Defensive patterns
Strategy: validation
Validate before calling
const { VALID_HOOK_MODES } = require('./claude-plugin-setup');
if (hooks !== undefined && !VALID_HOOK_MODES.has(hooks)) {
throw new Error(`Invalid hook mode: ${hooks}. Valid: ${[...VALID_HOOK_MODES].join(', ')}`);
} Type guard
function isValidHookMode(v, validModes) { return v === undefined || (typeof v === 'string' && validModes.has(v)); } Try / catch
try { await migrateClaudePluginScope({ scope, hooks }) }
catch (e) {
if (e.code === 'INVALID_HOOK_MODE') { console.error(`${e.message} — omit --hooks for the default`); }
else throw e;
} Prevention
- Omit --hooks unless you specifically need a non-default hook mode.
- Check VALID_HOOK_MODES (or --help) for the exact accepted values in your ECC version.
- Quote shell variables holding the hook mode to avoid empty-string passes.
- Keep wrapper scripts in sync with the ECC version's accepted modes.
When it happens
Trigger: Passing `options.hooks` (e.g. `--hooks <value>`) with a value not in VALID_HOOK_MODES — misspellings, unsupported modes like 'off' or 'auto' if the tool only accepts e.g. 'standard'/'strict', or a hooks value supplied as a non-string/undefined-shaped argument.
Common situations: Copy-pasting a hook mode from an older ECC version whose accepted values changed; guessing the mode name instead of checking `ecc setup --help`; passing `--hooks` with an empty value from an unset shell variable.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- INVALID_SCOPE
- --no-hooks cannot be combined with an explicit…
- all overlays must be readable local files
- all takes must be readable local files
- Arguments must not contain NUL bytes.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a232d02a8c4018dc.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/claude-scope-migration.js:254
const plugins = readPluginInventory(run, paths.projectRoot, 'final-verification');
return validateExpectedScopes(plugins, [destinationScope], {
code: 'FINAL_VERIFICATION_FAILED',
destinationScope,
message: `Could not verify destination-only ${CURRENT_PLUGIN_ID} state after source cleanup.`,
phase: 'final-verification',
recovery: recoveryCommands(null, destinationScope),
});
}
function migrateClaudePluginScope(options = {}, dependencies = {}) {
if (!VALID_SCOPES.has(options.scope)) {
throw migrationError(
'INVALID_SCOPE',
'Scope migration requires --scope user, project, or local.'
);
}
if (options.hooks !== undefined && !VALID_HOOK_MODES.has(options.hooks)) {
throw migrationError('INVALID_HOOK_MODE', `Invalid hook mode: ${options.hooks}`);
}
const paths = resolveClaudePaths(options);
const settingsPath = path.join(paths.configDir, 'settings.json');
const settings = readSettings(settingsPath);
assertSafeLocalInventory(paths);
assertGitAvailable(
{ cwd: paths.projectRoot },
{ spawnSync: dependencies.spawnSync }
);
const providerRun = dependencies.runClaude || runClaude;
const run = options.dryRun
? createDryRunClaudeRunner(providerRun, paths, options)
: providerRun;
const plugins = readPluginInventory(run, paths.projectRoot, 'inventory');
const migration = assertMigrationInventory(plugins, options.scope);
const hooks = options.hooks === undefined
? deriveHookMode(settings)View on GitHub (pinned to 8321021c54)