JuliusBrussee/caveman · error
unknown directive
Error message
unknown directive '${id}' What it means
directiveBlock() looks up a directive id in the DIRECTIVES table and fails closed when the id is absent (or only present via prototype keys, which Object.hasOwn excludes). The comment notes callers normally validate first, so this is a defensive internal error.
Solutions
- Validate the id against the exported DIRECTIVES keys before calling
- Fix the directive name typo in the invoking code/config
- Use Object.hasOwn(DIRECTIVES, id) checks in your own code mirroring the library's guard
- Upgrade/downgrade the CLI if the directive existed in a different version
Example fix
// before
directiveBlock(userInput);
// after
if (!Object.hasOwn(DIRECTIVES, userInput)) throw new Error(`unknown directive '${userInput}'`);
directiveBlock(userInput); Defensive patterns
Strategy: validation
Validate before calling
const isValidDirective = (id: string) => Object.hasOwn(DIRECTIVES, id);
if (!isValidDirective(id)) throw new Error(`unknown directive '${id}'`); Type guard
const isValidDirective = (id: string): id is keyof typeof DIRECTIVES => Object.hasOwn(DIRECTIVES, id);
Try / catch
try { installDirectiveNote(file, id) } catch (e) {
if (String(e).startsWith('unknown directive')) { /* list valid ids and correct input */ }
} Prevention
- Always validate user-supplied directive ids against DIRECTIVES keys
- Use Object.hasOwn, not truthy indexing, to avoid prototype keys
- Whitelist directive names in config parsing
When it happens
Trigger: Calling directiveBlock/installDirectiveNote/removeDirectiveNote with an id not present as an own property of DIRECTIVES (e.g. 'constructor', '__proto__', a typo, or a directive removed in a newer version).
Common situations: Passing a user-supplied directive name straight through without validating against the known set; prototype-pollution style ids; config referencing a directive from an older CLI release.
Related errors
- bedrock request path
- cave_claude_terminal_missing
- cave_harness_model_identity_missing
- cave_sandbox_child_process_containment_unavailable
- cave_sandbox_network_egress_unbounded
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/b3cb1281e347981b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:14826
"the exact file or symbol.",
],
},
"deferred-tool-loading": {
summary: "load tool descriptions on demand instead of the full catalog",
lines: [
"Directive: defer tool loading.",
"Do not pre-declare every available tool on every request. Keep the task's core",
"tool set declared, and load other tools' full descriptions through a",
"search-then-load surface (e.g. tool search) only when a task needs them.",
],
},
};
function directiveBlock(id: string): string {
// Object.hasOwn, not a truthiness read: id is user input, and a bare
// DIRECTIVES[id] would resolve prototype keys ('constructor', '__proto__')
// to non-directive objects (review C8).
const d = Object.hasOwn(DIRECTIVES, id) ? DIRECTIVES[id] : undefined;
if (!d) throw new Error(`unknown directive '${id}'`); // callers validate first; fail closed anyway
return [directiveBegin(id), ...d.lines, directiveEnd(id)].join("\n");
}
function installDirectiveNote(file: string, id: string): boolean {
return installManagedBlock(file, directiveBegin(id), directiveEnd(id), directiveBlock(id));
}
function removeDirectiveNote(file: string, id: string): "removed" | "absent" | "corrupt" | "failed" {
return removeManagedBlock(file, directiveBegin(id), directiveEnd(id));
}
// ── hard tier: opencode plugin ───────────────────────────────────────────────
// opencode exposes a real pre-exec command rewrite: a plugin's `tool.execute.before`
// hook can mutate `output.args.command` for the bash tool before it runs (the docs'
// own example does exactly this). We ship a small plugin that routes a noisy command
// through `caveman shrink` — reusing this very CLI's `shrink-hook` decision so the
// allowlist/skip rules stay in one place. Byte-safe: any failure leaves the command
// unchanged. (opencode's hook does not fire for subagent/MCP tool calls — sst/opencode
// #5894/#2319 — so the soft note remains a useful complement there.)
function opencodePluginPath(): string {View on GitHub (pinned to 3ee70a1026)