Why Claude skills break across platforms

A catalog of 36 documented runtime pitfalls when writing Claude skills (SKILL.md) — hidden tool limits, cross-platform differences, sandbox constraints, and authoring traps. Each entry has a cause, a fix, and a verification label.

tool-constraints (5)tool-behaviors (9)cross-platform (12)environment (5)skill-structure (5)

tool-constraints

Hidden limits on a single tool — schema-enforced caps and undocumented ceilings.

ask_user_input_v0 on Claude.ai enforces a schema-level ceiling of 3 questions and 4 options per question; exceeding either limit fails with a validation error.
image_search.max_results is bounded to the range [3, 5]; values outside that window are rejected by the tool's schema validation.
recent_chats.n is bounded at 20 — values above the cap are rejected; paginate with before/after cursors instead of asking for a larger page.
Claude Code's Bash tool description states timeout maxes at 600000ms (10 minutes). Empirically, passing a higher value is not rejected — the call is silently accepted (clamped or ignored), so the documented ceiling is not a hard gate.
Claude Desktop's bash tool caps timeout_ms at 45000 — values above 45 seconds are silently clamped, and long-running commands die mid-execution.

tool-behaviors

Undocumented runtime behaviors — prerequisite calls, path resolution, side effects.

conversation_search does keyword matching, not semantic search; meta-words like 'conversation' or 'chat' in your query poison the results and surface unrelated hits.
Claude Code's Read returns content in cat -n format (<line>\t<text>). If a skill copies a line from that output straight into Edit's old_string, the leading line number and tab are not part of the file, so the match fails with "String to replace not found in file."
Claude Code's Edit errors unless Read was called on the same file earlier in the same conversation — even if your skill just wrote it.
Claude Code's Write refuses to overwrite an existing file unless Read was called on that path earlier in the conversation — even when the overwrite is intentional.
On Claude Code, each Bash call effectively starts in the project working directory with a fresh shell — a cd in one call, and exported variables, do not carry over to the next. The harness resets cwd back to the project directory after every call.
Claude Code's Bash tool description embeds a git safety policy — refusing --amend, --no-verify, reset --hard, force-push, etc. without explicit user permission.
Claude Code's Read resolves relative paths against the current working directory, not the skill's own directory — Read('references/foo.md') from a skill rarely finds the file.
On Claude Desktop, each bash call runs in a fresh shell — exported variables, cd, and background jobs from a previous call do not carry over to the next.
Claude.ai's view tool rejects relative paths and bare filenames with a validation error; only absolute paths starting at / work.

cross-platform

Divergences across Claude.ai, Claude Code, and Claude Desktop.

Telling the model 'don't assume which tool to use' in SKILL.md doesn't reliably prevent assumption — only an explicit AskUserQuestion/ask_user_input_v0 picker does.
AskUserQuestion (Code/Desktop) and ask_user_input_v0 (Claude.ai) serve the same purpose but have different schemas, different limits, and different question/option caps.
Claude Code and Desktop's Edit accepts a replace_all flag; Claude.ai's str_replace_based_edit_tool has no equivalent and requires unique matches.
On Claude.ai the view tool can list a directory's contents; on Claude Code and Desktop the Read tool errors on directory paths.
Claude Code's Write overwrites existing files silently; Claude.ai's create_file refuses to overwrite and errors. The most dangerous cross-platform divergence in the catalog.
Inline SVG/HTML rendering works on Claude.ai and Desktop but not on Claude Code; skills that emit visualizations there produce raw markup instead of a rendered image.
search_mcp_registry and suggest_connectors exist on Claude.ai but not on Claude Code; skills that rely on runtime MCP discovery silently misbehave there.
MCP tool names appear as server__tool on some surfaces and mcp__server__tool on others — hardcoding either form breaks the skill on the other.
MCP tool schemas are not loaded by default on Claude Code and Desktop — calling them without first fetching the schema fails with an InputValidationError.
Searching past conversations uses conversation_search on Claude.ai, a different shape on Desktop, and isn't available on Claude Code — same intent, three implementations.
When a skill names a tool that doesn't exist on the current platform, the model silently substitutes an approximation instead of erroring — output looks plausible but is wrong.
Claude.ai, Claude Code, and Claude Desktop each expose a structurally different bash tool — different parameter names, different timeouts, different state semantics.

environment

Sandbox, filesystem, and network constraints.

/mnt/user-data/uploads/ on Claude.ai is read-only — skills that try to write there for round-tripping uploaded files fail with a permission error. Use /mnt/user-data/outputs/.
Claude Desktop's bash runs in a Docker-based sandbox, not the user's real macOS/Windows shell — ~, host paths, and host-installed binaries are not present.
Claude.ai's bash environment can only reach an allowlisted set of HTTPS hosts; curl to arbitrary domains fails with a network error, not a DNS error.
Claude.ai's sandbox filesystem is wiped between conversation turns — files your skill wrote in turn N are gone in turn N+1 unless persisted to /mnt/user-data/outputs/.
Plain pip install <pkg> on Claude.ai's bash sandbox fails with 'externally-managed-environment' (PEP 668); add --break-system-packages or use a venv.

skill-structure

SKILL.md authoring traps — file paths, references, hot-reload.

The references/foo.md pattern recommended by skill-creator does not resolve relative to the skill's own directory at runtime — you must construct the absolute path yourself.
On Claude Code, *new* skill directories hot-load on the next turn — but edits to the body of an already-loaded SKILL.md are not picked up mid-session. The content is cached at first load; a session restart is required to see changes.
Hardcoding tool names like 'use the Write tool' in SKILL.md breaks the skill on platforms where that tool name doesn't exist — write tool-agnostic instructions instead.
Positive finding: dropping a new skill directory into the skills folder on Claude Code is picked up on the next turn without restarting the session.
The 'Base directory for this skill: <path>' line Claude Code injects above SKILL.md is informational only — tools do not resolve relative paths against it.

No pitfalls match your search.