EveryInc/compound-engineering-plugin · error · Error
Local skill mode cannot represent default Codex hooks at…
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.
Solutions
- Check the file: ls hooks/hooks.json — if it is experimental leftovers, remove it or move it out of the checkout.
- git restore/rebase away the branch that added hooks/hooks.json if it is not intended on this branch.
- 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
- Keep hook prototypes outside the repo (or in an untracked scratch dir) while using local mode.
- After rebasing onto branches that add hooks/, re-check hooks/hooks.json existence.
- Add hooks/hooks.json to a pre-flight check in scripts that automate local mode.
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
- Local skill mode cannot represent Codex runtime components
- Cleanup currently supports only the compound-engineering…
- Could not validate after taking it. The entry is preserved…
- Dropping unsafe Pi install-manifest entry in
- Invalid : . Paths must stay within the plugin root.
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/601ee2859e87ec1a.
Report an issue: GitHub.
Appendix: 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 errorView on GitHub (pinned to c9c10f8c75)