How to Debug Conversion Failures and Missing Mappings in the Compound-Engineering Plugin
Conversion failures in the Compound-Engineering Plugin typically stem from unmapped Claude hook events or missing tool specifications in the mapping tables, which you can diagnose by inspecting the generated bundle comments and extending the HOOK_EVENT_MAP or TOOL_MAP in the converter source.
The CLI converts Claude Code plugins into other agent-platform formats such as OpenCode and Codex. When debugging conversion failures and missing mappings, you must understand the deterministic conversion pipeline implemented in src/converters/ to pinpoint whether the issue lies in hook event translation or tool specification normalization.
Understanding the Conversion Pipeline Architecture
The conversion process is pure functional, producing no side effects beyond file writes. This determinism makes debugging straightforward once you understand the four core stages.
Parsing the Claude Plugin Manifest
The process begins in src/parsers/claude.ts, which reads the JSON manifest (.claude-plugin/plugin.json) and constructs a typed ClaudePlugin object. This parser validates the schema and normalizes input before passing it to the target-specific converters.
Converting to Target Formats
Each target platform has a dedicated converter:
- OpenCode:
src/converters/claude-to-opencode.tshandles agents, commands, hooks, and MCP servers. - Codex:
src/converters/claude-to-codex.tsmanages task calls and slash commands.
These converters walk the parsed plugin structure and produce platform-specific bundles.
Hook Event Mapping Logic
Claude hook names are translated to target platform events via the HOOK_EVENT_MAP constant defined in src/converters/claude-to-opencode.ts (lines 48-62). This Record<string, HookEventMapping> maps source events to their target equivalents, specifying event names, types ("tool", "session", "permission", "message"), and optional notes.
When a hook lacks a mapping entry, the converter collects it in an unmappedEvents array and emits it as a comment at the top of the generated converted-hooks.ts file.
Tool Specification Normalization
Tool identifiers used in Claude (such as bash, read, or custom tools) are normalized through TOOL_MAP (lines 24-39 in claude-to-opencode.ts) and the parseToolSpec function (lines 99-106). The TOOL_MAP is a Record<string, string> that translates Claude tool names to OpenCode tool identifiers.
The parseToolSpec function extracts tool names and optional patterns from strings like "write(src/**)". Missing entries or malformed specs cause tools to be ignored or trigger warnings during conversion.
Identifying Common Conversion Failure Modes
| Symptom | Likely Cause | Verification Method |
|---|---|---|
| "Unmapped Claude hook events: …" comment appears in generated files | Hook name missing from HOOK_EVENT_MAP |
Compare the comment list in the generated bundle with the map definition in claude-to-opencode.ts |
Missing tool in generated tools object or tool appears as null |
Tool string absent from TOOL_MAP or parseToolSpec failure |
Cross-reference the Claude plugin's tool definitions with TOOL_MAP entries |
| Runtime permission errors ("tool not allowed") | applyPermissions omitted the tool due to malformed allowedTools syntax |
Inspect config.permission in the final bundle against the original Claude command's allowedTools array |
| Warning about bare model alias | normalizeModel could not resolve the alias in CLAUDE_FAMILY_ALIASES |
Check console output for the specific alias and verify against the alias list near line 55 |
Step-by-Step Debugging Workflow
-
Re-run the CLI with verbose output.
The CLI prints lists of unmapped events and tool-spec warnings directly to stderr, giving you immediate visibility into what the converter could not translate. -
Locate the generated bundle.
For OpenCode, find the output underplugins/<your-plugin>/config.jsonandconverted-hooks.ts. For Codex, look in thecodex/directory. -
Inspect the unmapped-event comment.
Openconverted-hooks.tsand look for the header comment:// Unmapped Claude hook events: MyCustomHook, AnotherHookIf present, these hooks were collected in
unmappedEventsbecause they lack entries inHOOK_EVENT_MAP. -
Validate tool mappings.
Search your Claude plugin JSON for tool definitions ("tool": "myTool"). Verify thatmyToolexists as a key inTOOL_MAP. If missing, the converter cannot normalize the specification. -
Check permission parsing.
If a command definesallowedTools, ensure each entry follows the patterntoolortool(pattern). TheparseToolSpecfunction extracts these components; malformed syntax (missing parentheses or stray spaces) causes the tool to be omitted fromconfig.permission. -
Run the test suite.
Executebun testto validate your changes. The repository includes unit tests for converters, permission handling, and hook rendering that will catch regressions. -
Confirm the fix.
After updatingHOOK_EVENT_MAPorTOOL_MAP, re-run the conversion. Verify that the unmapped comment disappears and the tool appears in the generatedtoolslist.
Code Examples for Fixing Mappings
Extending HOOK_EVENT_MAP for New Hooks
When the converter encounters a hook not present in the mapping table, it emits a comment rather than code. To fix this, extend the map in src/converters/claude-to-opencode.ts:
// Lines 48-62 in claude-to-opencode.ts
const HOOK_EVENT_MAP: Record<string, HookEventMapping> = {
// existing mappings...
MyCustomHook: {
events: ["my.custom.event"],
type: "tool", // "tool" | "session" | "permission" | "message"
note: "Custom hook for audit logging",
},
};
After adding the entry, the convertHooks function will generate a proper event handler instead of the unmapped comment.
Adding Missing Tools to TOOL_MAP
If a tool is missing from the generated configuration, add it to the normalization table:
// Lines 24-39 in claude-to-opencode.ts
const TOOL_MAP: Record<string, string> = {
// existing entries...
bash: "bash",
read: "read",
myCustomTool: "myCustomTool", // <-- add this line
};
Now parseToolSpec("myCustomTool") returns { tool: "myCustomTool" }, and the tool will be included in the config.tools array.
Diagnosing Permission Parsing Issues
Permission errors often trace back to malformed allowedTools entries. The parseToolSpec function (lines 99-106) expects strict syntax:
// Valid specifications
"write" // -> { tool: "write" }
"write(src/**)" // -> { tool: "write", pattern: "src/**" }
// Invalid (will be ignored)
"write (src/**)" // space before parenthesis
"write[src]" // wrong brackets
To verify, inspect the config.permission object in the generated bundle. If a tool is missing, cross-reference with the original Claude command's allowedTools array and correct the syntax.
Key Source Files for Debugging
| File | Role | Location |
|---|---|---|
src/parsers/claude.ts |
Parses Claude JSON manifest into typed ClaudePlugin objects. |
View on GitHub |
src/converters/claude-to-opencode.ts |
Core OpenCode conversion logic, contains HOOK_EVENT_MAP (lines 48-62) and TOOL_MAP (lines 24-39). |
View on GitHub |
src/converters/claude-to-codex.ts |
Codex-specific transformations for task calls and slash commands. | View on GitHub |
src/types/claude.ts |
TypeScript definitions for Claude plugin schema. | View on GitHub |
src/types/opencode.ts |
OpenCode bundle type definitions. | View on GitHub |
tests/*.test.ts |
Unit tests for converters, permission handling, and hook rendering. | e.g., opencode-writer.test.ts |
Summary
- Unmapped hooks trigger a comment in generated files; fix by extending
HOOK_EVENT_MAPinsrc/converters/claude-to-opencode.ts. - Missing tools result from absent
TOOL_MAPentries or malformedparseToolSpecinput; normalize tool identifiers in the mapping table. - Permission errors originate in
applyPermissionswhenallowedToolssyntax deviates from the expectedtool(pattern)format. - Debugging workflow: run verbose conversion, inspect generated bundle comments, validate mappings against source tables, run
bun test, and re-convert to confirm.
Frequently Asked Questions
What causes the "Unmapped Claude hook events" comment in generated files?
This comment appears when the converter encounters a hook name in the Claude plugin manifest that does not exist in the HOOK_EVENT_MAP defined in src/converters/claude-to-opencode.ts (lines 48-62). The converter collects these unmapped events into an unmappedEvents array and emits them as a header comment in the generated converted-hooks.ts file rather than generating handler code.
How do I add support for a custom Claude hook that isn't converting?
To support a custom hook, extend the HOOK_EVENT_MAP in src/converters/claude-to-opencode.ts with a new entry mapping the Claude hook name to the target platform's event structure. For example, add MyCustomHook: { events: ["my.custom.event"], type: "tool" } to the map. After saving, re-run the conversion; the generator will now produce a proper event handler instead of the unmapped comment.
Why is a specific tool missing from the generated configuration?
A tool disappears from the output when its identifier is absent from the TOOL_MAP (lines 24-39 in claude-to-opencode.ts) or when the parseToolSpec function (lines 99-106) fails to parse the tool string from the allowedTools array. Verify that the tool name exists as a key in TOOL_MAP and that any tool specifications in the Claude plugin follow the strict tool(pattern) syntax without extra spaces or incorrect brackets.
How can I verify that permission mappings are working correctly?
Inspect the config.permission object in the generated bundle (e.g., config.json for OpenCode) and compare it against the original Claude command's allowedTools array. The applyPermissions function builds this object by parsing each allowed tool entry; if a tool is missing from the output, check that the source entry uses valid syntax like "write(src/**)" rather than malformed variants like "write (src/**)" or "write[src]".
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →