anomalyco / anomalyco/opencode
Bug: slash-invoked skills replace user prompt with full SKILL.md (arguments swallowed / lost at end)
@jlongster is already working on this.
Since Aug 4, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
When invoking a skill via slash command (/skill-name my actual request), OpenCode replaces the entire user message with the skill's full SKILL.md body. The user's trailing arguments are either:
- Swallowed if the skill body happens to contain
$ARGUMENTS/$1/$2(common in skill docs that show examples), or - Appended at the very end of a long skill document when no placeholders exist — so the model effectively only "sees" the skill manual and responds with something like "what do you mean?" / asks for clarification.
This makes slash-invoked skills unusable for any non-trivial skill that includes documentation examples or is longer than a short checklist.
Environment
- OpenCode version:
1.18.13(opencode-ai/opencode-darwin-arm64) - Invocation: TUI slash command
/<skill-name> <user prompt> - Skills loaded from:
~/.config/opencode/skills,~/.claude/skills, project.opencode/skills, etc.
Reproduction
- Create a skill with a non-trivial body, e.g.
.opencode/skills/demo-skill/SKILL.md:
---
name: demo-skill
description: Demo skill that reproduces argument swallowing
---
# Demo Skill
Do the following steps carefully.
### Example (from docs)
When the user says something like:
Input: $ARGUMENTS
you should parse it.
Also support `$1` positional args in examples.
- In a new session, run:
/demo-skill please fix the login bug in src/auth.ts
- Observe the user message that actually reaches the model:
- The message is the entire skill template, not the user's request.
- Because the body contains
$ARGUMENTS/$1, the substitution logic fills those placeholders inside the documentation examples, not as a dedicated "user request" section. - The model often replies as if no concrete task was given.
- Variant without placeholders: remove
$ARGUMENTS/$1from the skill body and re-run. User text is appended after the full skill markdown. With long skills, the real request is buried at the bottom and frequently ignored.
Expected behavior
Slash-invoking a skill should:
- Load/inject the skill instructions, and
- Preserve the user's request as a first-class, clearly labeled section that is not subject to accidental placeholder collisions inside the skill body.
Something like:
Follow the skill instructions below for skill "demo-skill".
<skill>
...skill body, NOT scanned for $ARGUMENTS/$N...
</skill>
## User request
please fix the login bug in src/auth.ts
Alternatively: keep slash skills as thin wrappers that always use an outer template with $ARGUMENTS, and never run $ARGUMENTS/$N replacement against the raw skill body.
Actual behavior (from 1.18.13 binary)
Skills are registered as slash commands with source: "skill" and template = full skill content (+ base directory footer):
// Command registration for each skill
s[o.name] = {
name: o.name,
description: o.description,
source: "skill",
get template() {
if (!t) return o.content
return [
o.content,
"",
`Base directory for this skill: ${t}`,
"Relative paths in this skill (e.g., scripts/, references/) are relative to this base directory.",
].join("\n")
},
}
Command execution then does placeholder substitution on that template:
// SessionPrompt.command (simplified from 1.18.13)
let args = (t.arguments.match(tokenRe) ?? []).map(...)
let template = await V.template
// replace $1, $2, ...
template = template.replaceAll(/\$(\d+)/g, ...)
// replace $ARGUMENTS
let hasArguments = template.includes("$ARGUMENTS")
template = template.replaceAll("$ARGUMENTS", t.arguments)
// only if NEITHER $N nor $ARGUMENTS existed, append user args
if (no $N placeholders && !hasArguments && t.arguments.trim()) {
template = template + "\n" + t.arguments
}
So:
| Skill body | What happens to user args |
|---|---|
Contains $ARGUMENTS (even in examples) |
Injected into those example spots; no dedicated user-request section |
Contains $1/$2 only |
Same — fills doc examples |
| Contains neither | Appended after entire skill MD (easy to miss) |
Contrast with the skill tool path (skill({ name })), which correctly returns <skill_content> as a tool result and leaves the original user prompt intact. The bug is specifically in slash → command → template substitution, not in skill discovery/loading itself.
Impact
- Any skill whose docs mention
$ARGUMENTS/$1(or copy Claude Code / command examples) silently corrupts the user request. - Long skills (common for project workflows) push the real task past the model's attention window for the user turn.
- Users learn to avoid
/skillentirely and instead write "use X skill: …" so the agent loads via the skill tool — defeating the purpose of slash invocation.
Suggested fix
- Do not treat raw skill body as a command template subject to
$ARGUMENTS/$Nreplacement. - Wrap skill slash invocation in a fixed outer template, e.g.:
const SKILL_SLASH_TEMPLATE = `
Follow skill "$NAME".
<skill_content name="$NAME">
$SKILL_BODY
</skill_content>
Base directory: $SKILL_DIR
## User request
$ARGUMENTS
`.trim()
- Only substitute
$ARGUMENTS(and optional$NAME/$SKILL_DIR) on the wrapper, never on$SKILL_BODY. - Optionally strip or escape
$ARGUMENTS/$Ninside skill bodies when registering, so docs cannot collide. - Add a regression test:
- skill body contains the literal string
$ARGUMENTSin an example - invoke
/skill-name do the thing - assert the model-facing user text contains a clear
User requestsection withdo the thing, and the example$ARGUMENTSremains literal (or is escaped), not replaced.
- skill body contains the literal string
Workaround (current)
Until fixed:
- Prefer natural language:
use demo-skill: please fix ...so the agent calls theskilltool. - Or put
$ARGUMENTSat the top of every SKILL.md under a## User requestheading, and remove other$ARGUMENTS/$Noccurrences from the body (or write them as`$ARGUMENTS`only if the engine still matches — currently it matches raw$ARGUMENTSanywhere).
Related
- Skills docs: https://opencode.ai/docs/skills/
- Commands use
$ARGUMENTSintentionally; skills should not inherit that template semantics when the body is free-form markdown documentation.
Happy to test a PR against 1.18.x / dev.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.