affaan-m/ECC · error

Unable to resolve CLAUDE_PLUGIN_ROOT in a managed hook…

Error message

Unable to resolve CLAUDE_PLUGIN_ROOT in a managed hook command; the hooks.json command prologue no longer matches the expected form

What it means

Managed hook commands reference CLAUDE_PLUGIN_ROOT through an expected env-var prologue. After rewriting the prologue to an inline assignment, the resolver re-checks the command; if a CLAUDE_PLUGIN_ROOT reference still remains, the prologue did not match the expected form and the placeholder cannot be safely resolved, so it throws rather than emitting a command that would fail or leak an unresolved variable at hook runtime.

Solutions

  1. Restore the command to the standard prologue form the library expects (the exact PLUGIN_ROOT_ENV_PROLOGUE prefix followed by the command using the placeholder)
  2. Regenerate hooks.json from the plugin template instead of hand-editing the command
  3. Inline the absolute plugin path manually if you do not want placeholder resolution
  4. Check the plugin version for hooks.json format drift and update

Example fix

// before
"command": "node \"$CLAUDE_PLUGIN_ROOT/scripts/hook.js\""
// after
"command": "CLAUDE_PLUGIN_ROOT=\"${CLAUDE_PLUGIN_ROOT:-}" node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook.js\"" — or restore the canonical placeholder command from hooks.json template
Defensive patterns

Strategy: try-catch

Validate before calling

const usesPlaceholder = (cmd) => cmd.includes('CLAUDE_PLUGIN_ROOT');
// before materializing: every command containing CLAUDE_PLUGIN_ROOT must match the canonical prologue template — verify against the pristine hooks.json from the plugin.

Type guard

null

Try / catch

try { materializeManagedHooks(cfg, root); } catch (e) { if (e.message.includes('Unable to resolve CLAUDE_PLUGIN_ROOT')) { console.error('hooks.json command deviates from the canonical prologue; restore the template command'); } throw e; }

Prevention

When it happens

Trigger: Calling resolveCommand (via resolveManagedHookCommands/materializeManagedHooks) with a command string containing CLAUDE_PLUGIN_ROOT but not in the exact expected prologue form — e.g. "echo $CLAUDE_PLUGIN_ROOT/x" with no prologue, quoted variants, or a modified prologue after hand-editing hooks.json.

Common situations: A hook command was hand-edited or reformatted (different quoting, missing the standard prefix), a plugin shipped hooks.json deviating from the template, or an older format after a library upgrade changed the expected prologue.

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 affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/d14ea1d3ad83a4e6. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install/claude-settings.js:270

      ])
    );
  }
  return value;
}

function resolveManagedHookCommands(managedHooks, targetRoot) {
  const encodedRoot = Buffer.from(targetRoot, 'utf8').toString('base64');
  const rootExpression = `Buffer.from('${encodedRoot}','base64').toString('utf8')`;
  const resolveCommand = command => {
    // Leave invalid entries intact so managed-hook validation reports them.
    if (typeof command !== 'string') {
      return command;
    }
    const resolved = command
      .split(PLUGIN_ROOT_ENV_PROLOGUE)
      .join(`var e=${rootExpression};`);
    if (PLUGIN_ROOT_ENV_READ.test(resolved)) {
      throw new Error(
        'Unable to resolve CLAUDE_PLUGIN_ROOT in a managed hook command; '
        + 'the hooks.json command prologue no longer matches the expected form'
      );
    }
    return resolved;
  };
  return Object.fromEntries(
    Object.entries(managedHooks).map(([event, entries]) => [
      event,
      entries.map(entry => ({
        ...entry,
        hooks: entry.hooks.map(hook => ({
          ...hook,
          ...(typeof hook.command === 'string' || Array.isArray(hook.command)
            ? {
              command: Array.isArray(hook.command)
                ? hook.command.map(resolveCommand)
                : resolveCommand(hook.command),

View on GitHub (pinned to 8321021c54)