All skills
Skillintermediate

Cross-Platform Compatibility Checklist

Master reference for ensuring features work across Claude Code, OpenCode, Codex CLI, and Cursor.

Claude Code Knowledge Pack7/10/2026

Overview

Cross-Platform Compatibility Checklist

Master reference for ensuring features work across Claude Code, OpenCode, Codex CLI, and Cursor.

Quick Reference

AspectClaude CodeOpenCodeCodex CLICursor
Config formatJSONJSON/JSONCTOMLMDC (YAML frontmatter + MD)
Config location~/.claude/settings.json~/.config/opencode/opencode.json~/.codex/config.toml.cursor/{skills,commands,rules}/ (project)
State directory (per-project).claude/.opencode/.codex/.cursor/
Commands location (global)Plugin commands/~/.config/opencode/commands/N/A (use skills).cursor/commands/*.md (project)
Skills location (global).claude/skills/~/.config/opencode/skills/~/.codex/skills/.cursor/skills/*/SKILL.md (project)
Agents location (global)Plugin agents/~/.config/opencode/agents/N/A (use MCP)N/A (use rules)
Invocation prefix/command/command$skillAuto-applied
Project instructionsCLAUDE.mdAGENTS.md (reads CLAUDE.md)AGENTS.md.cursor/rules/*.mdc
Env var for plugin rootCLAUDE_PLUGIN_ROOTPLUGIN_ROOTPLUGIN_ROOTN/A (paths inlined)
Env var for state dirN/AAI_STATE_DIRAI_STATE_DIRN/A
Label char limitNo strict limit30 chars (enforced)No strict limitNo strict limit

Platform-Specific Transformations

1. Environment Variables

In all command/agent files, transform:

${CLAUDE_PLUGIN_ROOT} → ${PLUGIN_ROOT}
$CLAUDE_PLUGIN_ROOT   → $PLUGIN_ROOT

Reason: OpenCode and Codex use PLUGIN_ROOT, not CLAUDE_PLUGIN_ROOT.

CRITICAL: Windows Path Handling in require() statements

When using require() with plugin paths, ALWAYS normalize Windows backslashes:

// CORRECT - Works on all platforms (Windows, Linux, Mac)
const module = require('${CLAUDE_PLUGIN_ROOT}'.replace(/\\\\/g, '/') + '/lib/module.js');

// WRONG - Breaks on Windows (backslashes become escape sequences)
const module = require('${CLAUDE_PLUGIN_ROOT}/lib/module.js');

Why: Windows paths like C:\\Users\\... have backslashes that JavaScript interprets as escape sequences (\\UU, etc.), breaking the path. Forward slashes work on all platforms including Windows.

Note: The .replace(/\\\\/g, '/') normalization works with both ${CLAUDE_PLUGIN_ROOT} and ${PLUGIN_ROOT} after platform transformations.

2. State Directory

Never hardcode .claude/. Use platform-aware paths:

// In JavaScript code
const stateDir = process.env.AI_STATE_DIR || '.claude';
const statePath = path.join(projectRoot, stateDir, 'tasks.json');
PlatformAI_STATE_DIR Value
Claude CodeNot set (defaults to .claude)
OpenCode.opencode
Codex CLI.codex
Cursor.cursor

3. Command Frontmatter

Claude Code format:

---
description: Task description
argument-hint: "[args]"
allowed-tools: Bash(git:*), Read, Write, Task
---

OpenCode format:

---
description: Task description
agent: general
model: opencode/claude-opus-4-5  # Optional
subtask: true                     # Optional
---

Transformation in installer:

  • Remove allowed-tools (permissions handled by agent definition)
  • Add agent: general
  • Optionally add model for complex commands

4. Agent Frontmatter

Claude Code format:

---
name: my-agent
description: Agent description
tools: Bash(git:*), Read, Edit, Task
model: sonnet
---

OpenCode format:

---
name: my-agent
description: Agent description
mode: subagent
model: anthropic/claude-sonnet-4
permission:
  read: allow
  edit: allow
  bash: ask
---

Transformation rules:

Claude CodeOpenCode
tools: Bash(git:*)permission: bash: allow
tools: Readpermission: read: allow
tools: Edit, Writepermission: edit: allow
tools: Taskpermission: task: allow
model: sonnetmodel: anthropic/claude-sonnet-4
model: opusmodel: anthropic/claude-opus-4
model: haikumodel: anthropic/claude-haiku-3-5

5. Skill Format (Codex)

Codex skills require SKILL.md with trigger-phrase descriptions:

---
name: skill-name
description: "Use when user asks to \\"trigger phrase 1\\", \\"trigger phrase 2\\". Description of what it does."
---

Best practices:

  • Description MUST include trigger phrases
  • Wrap description in quotes
  • Escape internal quotes: \\"phrase\\"
  • Keep SKILL.md under 500 lines
  • Split large content into references/ subdirectory

6. Rule Format (Cursor)

Cursor rules use .mdc files with YAML frontmatter:

---
description: "Use when user asks to \\"trigger phrase\\". Description of what it does."
globs: "*.js"
alwaysApply: true
---

Best practices:

  • Rules live in .cursor/rules/ (project-scoped, auto-loaded)
  • File naming: agentsys-<plugin>-<name>.mdc
  • alwaysApply: true makes the rule active for all conversations
  • globs restricts the rule to matching files (optional)
  • No environment variables - paths are inlined at install time
  • No Task tool, require(), or plugin namespacing syntax

UI/Question Constraints

OpenCode 30-Character Label Limit

CRITICAL: OpenCode enforces a 30-character maximum on option labels.

Affected code:

  • AskUserQuestion tool calls
  • Task selection labels
  • Policy question labels
  • Any user-facing options

Pattern to use:

function truncateLabel(prefix, text, maxLen = 30) {
  const available = maxLen - prefix.length;
  return text.length > available
    ? prefix + text.substring(0, available - 1) + '…'
    : prefix + text;
}

// Example: "#123: " = 6 chars, leaving 24 for title
const label = truncateLabel(`#${issue.number}: `, issue.title);

Template for task selection:

// CORRECT - All labels under 30 chars
AskUserQuestion({
  questions: [{
    header: "Select Task",  // max 30 chars
    question: "Which task to work on?",
    options: [
      { label: "#123: Fix login bug", description: "Full title here..." },
      { label: "#456: Add dark mode", description: "Full title here..." }
    ],
    multiSelect: false
  }]
});

Files requiring label truncation:

  • lib/sources/policy-questions.js - Cached source labels
  • plugins/next-task/agents/task-discoverer.md - Task selection
  • plugins/next-task/commands/next-task.md - Existing task question

Question Format Differences

AspectClaude CodeOpenCodeCodexCursor
Multi-selectmultiSelect: truemultiple: trueNot supportedN/A
Custom inputAlways availablecustom: true"Other" optionN/A
Max questions4Unlimited5N/A
Header max12 chars30 charsNo limitN/A
Label maxNo limit30 charsNo limitN/A
Tool nameAskUserQuestionAskUserQuestionrequest_user_input (auto-transformed by gen-adapters)N/A
id fieldNot requiredNot requiredRequired (gen-adapters injects reminder note)N/A

MCP Configuration

Claude Code

{
  "mcpServers": {
    "agentsys": {
      "command": "node",
      "args": ["path/to/mcp-server/index.js"],
      "env": {
        "PLUGIN_ROOT": "path/to/plugin"
      }
    }
  }
}

OpenCode

{
  "mcp": {
    "agentsys": {
      "type": "local",
      "command": ["node", "path/to/mcp-server/index.js"],
      "environment": {
        "PLUGIN_ROOT": "path/to/plugin",
        "AI_STATE_DIR": ".opencode"
      },
      "enabled": true
    }
  }
}

Codex CLI

[mcp_servers.agentsys]
command = "node"
args = ["path/to/mcp-server/index.js"]

[mcp_servers.agentsys.env]
PLUGIN_ROOT = "path/to/plugin"
AI_STATE_DIR = ".codex"

Installation Locations

Claude Code (via Marketplace)

~/.agentsys/           # Package copy
Plugin loaded via marketplace

OpenCode

~/.agentsys/                      # Package copy
~/.config/opencode/commands/    # Transformed commands
~/.config/opencode/agents/                    # Transformed agents (22 files)
~/.config/opencode/opencode.json       # MCP config added

Codex CLI

~/.agentsys/           # Package copy
~/.codex/skills/            # Transformed skills (9 directories)
~/.codex/config.toml        # MCP config added

Cursor

~/.agentsys/                       # Package copy
<project>/.cursor/skills/          # Skills (SKILL.md, minimal transform)
<project>/.cursor/commands/        # Commands (light transform, no frontmatter)
<project>/.cursor/rules/           # Rules (.mdc, coding standards)

Checklist: New Feature Release

Before Starting

  • Read this checklist completely
  • Identify which platforms the feature affects
  • Check if feature uses AskUserQuestion (label limits apply)

Code Changes

  • Use ${PLUGIN_ROOT} not ${CLAUDE_PLUGIN_ROOT} in new code
  • Use AI_STATE_DIR env var for state directory paths
  • All AskUserQuestion labels ≤30 chars
  • No hardcoded .claude/ paths

Command Files

  • Create command in plugins/{plugin}/commands/
  • Add to bin/cli.js OpenCode commandMappings array
  • Add to bin/cli.js Codex skillMappings array with trigger-phrase description

Agent Files

  • Create agent in plugins/{plugin}/agents/
  • Agent will be auto-installed to OpenCode (installer handles transformation)
  • Codex uses MCP tools instead of agents (no additional work)

Installer Updates (bin/cli.js)

For new commands:

// Search: OPENCODE_COMMAND_MAPPINGS
const commandMappings = [
  // [destFile, plugin, sourceFile]
  ['new-command.md', 'plugin-name', 'new-command.md'],
];

For new skills (Codex):

// Search: CODEX_SKILL_MAPPINGS
const skillMappings = [
  // [skillName, plugin, sourceFile, triggerDescription]
  ['new-skill', 'plugin-name', 'new-command.md',
    'Use when user asks to "trigger1", "trigger2". Description of capability.'],
];

Testing

  • npm test passes (all 1307+ tests)
  • Test on Claude Code: /new-command
  • Test on OpenCode: /new-command
  • Test on Codex CLI: $new-skill
  • Test on Cursor: rule auto-loaded from .cursor/rules/
  • Verify state files created in correct directory

Documentation

  • Update agent-docs/OPENCODE-REFERENCE.md if OpenCode-specific
  • Update agent-docs/CODEX-REFERENCE.md if Codex-specific
  • Add to CHANGELOG.md

Checklist: New Agent

Code Changes

  • Create in plugins/{plugin}/agents/{agent-name}.md
  • Use ${PLUGIN_ROOT} in all paths
  • Keep description concise with clear purpose

Installer Handles Automatically

The installer (bin/cli.js) automatically:

  • Copies agent to ~/.config/opencode/agents/
  • Transforms frontmatter (tools → permissions, model names)
  • No manual OpenCode agent creation needed

Codex Compatibility

Codex doesn't have a native agent system. Our agents work via:

  1. MCP tools that invoke agent logic
  2. Skills that contain agent instructions inline

No additional Codex work needed for most agents.


Checklist: Library Module Changes

  • Update lib/{module}/
  • Export from lib/index.js
  • Run ./scripts/sync-lib.sh (or agentsys-dev sync-lib) to copy to all plugins
  • Test on all platforms

Common Pitfalls

1. Hardcoded State Directory

// WRONG
const statePath = '.claude/tasks.json';

// CORRECT
const stateDir = process.env.AI_STATE_DIR || '.claude';
const statePath = `${stateDir}/tasks.json`;

2. Long Labels in Questions

// WRONG - Will fail in OpenCode
{ label: "Resume and ship #220 (ProfileScreen component)", ... }

// CORRECT - Under 30 chars
{ label: "Resume task #220", description: "ProfileScreen component" }

3. Missing Trigger Phrases (Codex)

# WRONG - Codex won't know when to trigger
description: Master workflow orchestrator

# CORRECT - Includes trigger phrases
description: "Use when user asks to \\"find next task\\", \\"automate workflow\\". Orchestrates task-to-production."

4. Wrong Environment Variable

// WRONG - Only works in Claude Code
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;

// CORRECT - Works everywhere
const pluginRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;

5. Proactive Directory Creation

// WRONG - Creates directory even if not needed
fs.mkdirSync('.opencode', { recursive: true });

// CORRECT - Only create when writing
function ensureStateDir() {
  const dir = process.env.AI_STATE_DIR || '.claude';
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  return dir;
}

Reference Documents

DocumentContent
agent-docs/OPENCODE-REFERENCE.mdComplete OpenCode integration guide
agent-docs/CODEX-REFERENCE.mdComplete Codex CLI integration guide
lib/cross-platform/RESEARCH.mdPlatform comparison research
bin/cli.jsInstaller implementation (transformations)

File Size Recommendations

File TypeRecommended MaxNotes
Command .md500 linesSplit into references/ if larger
Agent .md300 linesKeep focused on single responsibility
Skill SKILL.md500 linesUse progressive disclosure

Note: Check large files with wc -l plugins/*/commands/*.md and consider splitting if over limits.