EveryInc/compound-engineering-plugin · error · Error

Local skill mode cannot represent default Codex hooks at hoo

Error message

Local skill mode cannot represent default Codex hooks at hooks/hooks.json. Update the developer workflow before using it.

What it means

Even if the manifest declares no hooks, the library additionally checks whether a default hooks file exists at <repoRoot>/hooks/hooks.json. If that file exists, local skill mode could not represent it once installed, so it throws this error. ENOENT (no such file) is treated as the happy path and swallowed.

Source

Thrown at src/dev/codex-dev.ts:148

    throw new Error(`${repoRoot} is not the compound-engineering repository`)
  }

  const skills = pluginJson.skills
  if (typeof skills !== "string" || path.resolve(repoRoot, skills) !== path.join(repoRoot, "skills")) {
    throw new Error("The Codex plugin manifest does not point at this repository's skills directory")
  }

  const unsupported = ["apps", "hooks", "mcpServers"].filter((key) => pluginJson[key] !== undefined)
  if (unsupported.length > 0) {
    throw new Error(
      `Local skill mode cannot represent Codex runtime components: ${unsupported.join(", ")}. Update the developer workflow before using it.`,
    )
  }

  try {
    const defaultHooks = await fs.stat(path.join(repoRoot, "hooks", "hooks.json"))
    if (defaultHooks.isFile()) {
      throw new Error(
        "Local skill mode cannot represent default Codex hooks at hooks/hooks.json. Update the developer workflow before using it.",
      )
    }
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error
  }
}

async function listSkills(skillsRoot: string): Promise<string[]> {
  const entries = await fs.readdir(skillsRoot, { withFileTypes: true })
  const names: string[] = []
  for (const entry of entries) {
    if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
    try {
      const stat = await fs.stat(path.join(skillsRoot, entry.name, "SKILL.md"))
      if (stat.isFile()) names.push(entry.name)
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error

View on GitHub (pinned to c9c10f8c75)

Solutions

  1. Check the file: ls hooks/hooks.json — if it is experimental leftovers, remove it or move it out of the checkout.
  2. git restore/rebase away the branch that added hooks/hooks.json if it is not intended on this branch.
  3. Use bun run codex:dev -- remote while the repo carries default hooks.

Example fix

// before: repo layout
hooks/hooks.json   // present
skills/
// after
hooks/             // no hooks.json (or directory removed)
skills/
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from "node:fs/promises"
try {
  const s = await stat("hooks/hooks.json")
  if (s.isFile()) throw new Error("hooks/hooks.json present; local mode unavailable")
} catch (e) {
  if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e
}

Try / catch

try {
  await switchToLocal()
} catch (error) {
  if ((error as Error).message.includes("default Codex hooks at hooks/hooks.json")) {
    console.error("Remove or move hooks/hooks.json, or use codex:dev -- remote")
  } else throw error
}

Prevention

When it happens

Trigger: fs.stat(<repoRoot>/hooks/hooks.json) succeeds and isFile() is true during assertCompoundEngineeringRepo (src/dev/codex-dev.ts:148); the repo contains a hooks/hooks.json file while running codex:dev -- local.

Common situations: A developer prototyped Codex hooks in the checkout and left hooks/hooks.json in place; an upstream branch introduced default hooks while a local-mode workflow is in use; hooks directory restored from an old branch during a rebase.

Related errors


AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31). Data as JSON: /api/errors/601ee2859e87ec1a. Report an issue: GitHub.