thedotmack/claude-mem · critical · Error

plugin/hooks/codex-hooks.json contains unsupported Codex roo

Error message

plugin/hooks/codex-hooks.json contains unsupported Codex root key: ${rootKey}

What it means

After loading plugin/hooks/codex-hooks.json, the build verifies every top-level key is in the allowed set {hooks}. Codex ignores unknown root keys (or errors opaquely), so any extraneous key would silently break the plugin under Codex. The guard names the offending key.

Source

Thrown at scripts/build-hooks.js:730

      'plugin/scripts/bun-runner.js',
      'plugin/sqlite/SessionStore.js',
      'plugin/sqlite/observations/files.js',
      'plugin/.claude-plugin/plugin.json',
      'plugin/.codex-plugin/plugin.json',
      'plugin/.mcp.json',
      '.codex-plugin/plugin.json',
      '.agents/plugins/marketplace.json',
    ];
    for (const filePath of requiredDistributionFiles) {
      if (!fs.existsSync(filePath)) {
        throw new Error(`Missing required distribution file: ${filePath}`);
      }
    }
    const codexHooks = JSON.parse(fs.readFileSync('plugin/hooks/codex-hooks.json', 'utf-8'));
    const validCodexHookRootKeys = new Set(['hooks']);
    for (const rootKey of Object.keys(codexHooks)) {
      if (!validCodexHookRootKeys.has(rootKey)) {
        throw new Error(`plugin/hooks/codex-hooks.json contains unsupported Codex root key: ${rootKey}`);
      }
    }
    for (const eventName of Object.keys(codexHooks.hooks ?? {})) {
      if (!validCodexHookEvents.has(eventName)) {
        throw new Error(`plugin/hooks/codex-hooks.json contains unknown Codex hook event: ${eventName}`);
      }
    }
    const codexMarketplace = JSON.parse(fs.readFileSync('.agents/plugins/marketplace.json', 'utf-8'));
    const claudeMemMarketplaceEntry = (codexMarketplace.plugins ?? []).find((plugin) => plugin.name === 'claude-mem');
    if (claudeMemMarketplaceEntry?.source?.path !== './plugin') {
      throw new Error('.agents/plugins/marketplace.json must point claude-mem source.path at ./plugin so Codex loads the bundled plugin root');
    }
    const bundledMcp = JSON.parse(fs.readFileSync('plugin/.mcp.json', 'utf-8'));
    const mcpSearchCommand = bundledMcp.mcpServers?.['mcp-search']?.args?.join(' ') ?? '';
    if (!mcpSearchCommand.includes('.codex/plugins/cache/claude-mem-local/claude-mem')) {
      throw new Error('plugin/.mcp.json mcp-search launcher must include Codex cache fallback for hosts that do not inject PLUGIN_ROOT');
    }
    if (!mcpSearchCommand.includes('plugins/cache/thedotmack/claude-mem')) {

View on GitHub (pinned to d768ba3643)

Solutions

  1. Open plugin/hooks/codex-hooks.json and remove or nest the named root key — only 'hooks' may sit at the top level.
  2. If you need metadata, put it under a Codex-supported nested location or a separate sidecar file, not a new root key.
  3. Re-run the build; the check at scripts/build-hooks.js:728-731 must pass before downstream event validation.

Example fix

// before: plugin/hooks/codex-hooks.json
{
  "hooks": { ... },
  "events": { "SessionStart": [...] }   // unsupported root key
}

// after — only 'hooks' at the root
{
  "hooks": {
    "SessionStart": [...]   // moved under hooks
  }
}
Defensive patterns

Strategy: validation

Validate before calling

const codexHooks = JSON.parse(fs.readFileSync('plugin/hooks/codex-hooks.json','utf8'));
const badRoots = Object.keys(codexHooks).filter(k => k !== 'hooks');
if (badRoots.length) throw new Error('Unsupported codex root keys: ' + badRoots.join(','));

Type guard

function isCodexHooksRoot(obj): obj is { hooks: Record<string, unknown> } {
  return obj && typeof obj === 'object' && 'hooks' in obj && Object.keys(obj).every(k => k === 'hooks');
}

Try / catch

// Build-time only. Remove the unsupported root key (or nest it under 'hooks').
// Do not suppress — Codex would silently ignore the unknown key at runtime.

Prevention

When it happens

Trigger: Editing plugin/hooks/codex-hooks.json and adding a top-level field (e.g. a copied 'events', 'config', or Claude-specific key) that Codex doesn't recognise. Cross-pasting from a Claude hooks JSON that has a different schema.

Common situations: Treating the Codex hooks file like the Claude settings.json schema. Adding metadata fields at the root. Merge that brings in a sibling key from another hook system.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/0b6626bbeb974f06. Report an issue: GitHub.