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

  1. 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.
  2. Omit the --hooks flag entirely to use the default hook mode — validation only runs when hooks is defined.
  3. Fix shell quoting/variable expansion if the value is coming from an environment variable that is empty or malformed.
  4. 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

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


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)