affaan-m/ECC · error

Refusing to manage Claude hooks for a non-Claude target.

Error message

Refusing to manage Claude hooks for a non-Claude target.

What it means

assertClaudeSettingsDestination guards hook-management operations so they only ever touch Claude settings files (settings.json) for Claude targets ('claude' or 'claude-project'). If the operation's target is a different harness (e.g. codex, cursor) the library refuses, and it additionally verifies the destination path is the canonical Claude settings path inside the trusted root. This prevents merging Claude hook metadata into another product's config.

Solutions

  1. Pass target='claude' (user-level) or 'claude-project' (project-level) when invoking Claude hook-management functions.
  2. Ensure the operation's destinationPath points at the canonical Claude settings.json inside the trusted root.
  3. If you meant to manage hooks for another harness, use that harness's own adapter/operation kind instead of the Claude settings path.
  4. Fix typo'd target values in install-state or calling code (e.g. 'claude-code' -> 'claude').

Example fix

// before
await updateSettingsAtomic(operation, trustedRoot, { target: 'claude-code' })

// after
await updateSettingsAtomic(operation, trustedRoot, { target: 'claude' })
Defensive patterns

Strategy: validation

Validate before calling

function assertClaudeTarget(target) {
  if (target !== 'claude' && target !== 'claude-project') {
    throw new Error(`Claude hook management requires target 'claude' or 'claude-project', got: ${target}`);
  }
}

Type guard

function isClaudeTarget(t) {
  return t === 'claude' || t === 'claude-project';
}

Try / catch

try {
  await updateSettingsAtomic(op, trustedRoot, { target });
} catch (err) {
  if (/non-Claude target/.test(err.message)) {
    console.error(`Use a Claude target or the adapter for '${target}' instead.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling materializeManagedHooks/repairManagedHooks/validateManagedHooks/uninstallManagedHooks/updateSettingsAtomic with target set to a non-Claude value (any string other than 'claude' or 'claude-project'), or with an operation whose destinationPath is not the Claude settings path.

Common situations: Wiring the Claude hooks workflow into a multi-harness installer and passing target='codex' by mistake; install-state records copied from a Claude operation into a Cursor/Codex operation; typos in target like 'claude-code' which is not accepted.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/cf41464330cb32b9. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install-lifecycle.js:560

 * carry, because Claude Code reports unknown keys when the plugin loads. A
 * sidecar that does not line up with hooks.json is rejected before repair can
 * reconcile matchers under the wrong ids.
 *
 * @param {string} hooksPath - Path to the source hooks.json.
 * @returns {object} the hooks configuration with ids and descriptions restored.
 */
function readHooksConfigNoFollow(hooksPath) {
  const hooksConfig = readJsonNoFollow(hooksPath);
  const metadataPath = metadataPathFor(hooksPath);
  if (!fs.existsSync(metadataPath)) {
    return hooksConfig;
  }
  return mergeHooksMetadata(hooksConfig, readJsonNoFollow(metadataPath), hooksPath);
}

function assertClaudeSettingsDestination(operation, trustedRoot, target = null) {
  if (target && target !== 'claude' && target !== 'claude-project') {
    throw new Error('Refusing to manage Claude hooks for a non-Claude target.');
  }
  assertClaudeSettingsPath(operation.destinationPath, trustedRoot);
}

function writeContainedFile(destinationPath, content, trustedRoot, action, mode) {
  const preparedDestination = prepareContainedWriteDestination(destinationPath, trustedRoot, action);
  const finalDestination = getManagedDestination(
    preparedDestination,
    trustedRoot,
    action
  ).managedPath;
  writeFileNoFollow(
    finalDestination,
    content,
    mode,
    trustedRoot,
    action
  );

View on GitHub (pinned to 8321021c54)