The Portability Trap: When 'It Loads' Doesn't Mean 'It Works' A developer's audit of migrating Claude Code skills to OpenCode reveals that skills often degrade silently, with harness-specific frontmatter fields such as allowed-tools and model pinning being dropped without warning. The developer found that only skills using the portable subset of name, description, and markdown body port cleanly, while others require rebuilding. The audit framework and a five-minute test help identify which skills port cleanly and which break. I ran a skill migration and nothing broke. Or so I thought. Break 3 in my migration diary noted that skills degraded silently. This is the deep dive. I copied six Claude Code skills to OpenCode. Every file loaded. Every name appeared in the available skills list. Every description matched. No errors, no warnings, no friction. Weeks later, I noticed things were off — skills behaving differently than expected, models I didn't choose running expensive operations, context bleeding where it shouldn't have. Nothing told me anything was wrong. I had to read the frontmatter side by side to figure out why. Here's the audit framework I built to prevent that, and the five-minute test that tells you exactly which skills port cleanly and which ones break. Tools exist to port skills between agents. farmage/opencode-skills https://github.com/farmage/opencode-skills 111★ bulk-ported 66 Claude Code skills to OpenCode. crosstrain https://github.com/fwdslsh/crosstrain converts Claude Code skills into OpenCode TypeScript plugin tools. entireio/skills https://github.com/entireio/skills 210★ includes a session-to-skill transformation pipeline. Every one of them assumes portability — copy the file, map the frontmatter, done. But the SkillsBench paper https://arxiv.org/abs/2602.12670 analyzed 47,150 published agent skills and found an average quality score of 6.2 out of 12 — meaning the baseline is already fragile, and silent degradation on top of that compounds fast. The converters move files. They don't tell you what breaks. One of them — crosstrain — converts skills into TypeScript plugin tools, trading portability for tighter integration. That's a valid choice. But it's a choice the converter made for you, not one you made yourself. That's the gap this post fills. Before diving into the audit, here's the math: name , description , license , compatibility , metadata , and allowed-tools model , context: fork , hooks , disable-model-invocation , and more name , description , license , compatibility , and metadata That gap is where your skills break. The truly portable subset is: name + description + markdown body. Everything else is harness-specific. I had six skills in Claude Code. I invoked each one in a fresh OpenCode session to see what happened: | Skill | Frontmatter beyond portable? | Verdict | Verified in OpenCode | |---|---|---|---| | brainstorming | No | Port | ✅ Works as-is | | skill-creator | No | Port | ✅ Works as-is | | gsd | No | Port | ✅ Works as-is | | frontend-design | No | Port | ✅ Works as-is | | receiving-code-review | No | Port | ✅ Works as-is | | morning-brief | Yes — custom dependencies, model pinning | Rebuild | ✅ Rebuilt via session-first method | Five of six ported cleanly — because they happened to use only the portable subset. The one that didn't morning-brief needed model pinning and dependency declarations that OpenCode doesn't read. The pattern: simple skills name + description + body port. Skills with harness-specific fields break. When you move a Claude Code skill to OpenCode, the SKILL.md body transfers intact. The name and description transfer. But any harness-specific frontmatter field is dropped without a word. None of my six skills used fields 1–5 — which is exactly why five of them ported cleanly. Yours probably do. allowed-tools — safety constraints vanish In Claude Code, allowed-tools restricts which tools a skill can call. It's a safety boundary. Claude Code — skill restricted to read-only GitHub ops allowed-tools: mcp github list issues, mcp github get issue In OpenCode, this field is dropped. The skill loads and runs, but the tool restriction is gone. If the skill was designed to be read-only, it can now write files. agensi.io's cross-agent compatibility test confirmed this pattern: skills that relied on allowed-tools for safety constraints lost those constraints when tested across agents. The output was correct, but the boundary was gone. There's also a naming problem: Claude Code uses double-underscore MCP tool names mcp github create issue , while OpenCode uses single-underscore github create issue . Even if OpenCode supported the field, the tool names wouldn't match. context: fork — isolated execution disappears In Claude Code, context: fork runs the skill as an isolated subagent — separate context window, no contamination of the parent conversation. In OpenCode, this field is dropped. The skill body loads into the main context, consuming tokens and polluting the conversation. model , hooks , disable-model-invocation — cost, automation, and visibility go Fields 3–5 follow the same pattern: model pins a skill to a cheap model like Haiku , hooks before/after/on error lifecycle , and disable-model-invocation hides a skill from auto-discovery are all dropped by OpenCode. The skill runs, but cost control, automation, and visibility preferences are gone. OpenCode V2 beta adds opencode/autoinvoke as an opt-out from the discovery list — not auto-invoke, just hiding from the list. arguments — this one crashes Unlike the others, arguments doesn't fail silently. It crashes OpenCode with a ConfigFrontmatterError . Unknown fields are ignored; recognized-but-unsupported fields like arguments are validated and rejected. This is actually the best outcome — a hard error is easier to debug than silent degradation. One more practical difference that trips people up: OpenCode requires name to match ^ a-z0-9 + - a-z0-9 + $ — lowercase alphanumeric with hyphens. Claude Code doesn't enforce this. Same SKILL.md — works in Claude Code, crashes in OpenCode name: Pair Programming The name Pair Programming has a space — valid in Claude Code, invalid in OpenCode. Fix it to pair-programming before porting. Check your frontmatter. If the name has spaces, uppercase letters, or underscores, fix it before porting. There's a second difference that doesn't show up in any frontmatter table: how skills are loaded into context. In Claude Code, skills auto-invoke. When your task matches a skill's description, Claude loads the full SKILL.md body into context automatically. You don't ask for it — it just happens. The docs say skills are "automatically invoked when relevant to your task." In OpenCode, skills are on-demand. The agent sees skill names and descriptions in an