Skip to content

[FEATURE]: Remove or truncate <location> path in system prompt available_skills — wastes ~3.3K tokens/turn #39294

Description

@ghostbody

Description

The system prompt's <available_skills> block includes a <location> field with the full absolute file path for every skill. This field is:

  1. Unused by the LLM — skills are loaded via skill(name="foo"), not by path. The tool's internal registry already maps namelocation during discovery, so the LLM never needs to know the path.
  2. Extremely verbose — macOS paths like <location>/Users/yejq/Library/Application Support/com.xxx.zzz/mmmm/.opencode-config/skills/art-requirement/SKILL.md</location> average ~136 characters each.
  3. Made worse by pathToFileURL — spaces become %20, # becomes %23, and a file:// prefix adds 7 chars on top (as documented in fix(skill): skill directory context missing or URL-encoded in system prompt and slash-command paths #33786).

Token waste measurement (real-world data)

Measured from an actual system prompt dump with 84 skills:

Total <location> blocks:        84
Total characters in locations:  11,382
Average per location:           ~136 chars
Estimated tokens wasted:        ~3,252 tokens per turn

3.2K tokens burned on every API call on paths the LLM never reads, never uses, and doesn't need.

Single sample entry:

<location>/Users/yejq/Library/Application Support/com.xxx.zzz/mmmm/.opencode-config/skills/art-requirement/SKILL.md</location>

= 141 characters, for a skill whose name (art-requirement) is the only thing the LLM needs to invoke it.

Why <location> is unnecessary

The skill loading flow:

  1. System prompt tells the LLM: "these skills exist, here are their names + descriptions"
  2. LLM decides to use a skill → calls skill(name="ppt-master")
  3. The tool internally maps namelocation (already known from discovery)
  4. The tool reads SKILL.md and returns the content + a base directory hint

At no point does the LLM need the file path. The tool's internal registry already knows where every skill lives. The <location> field in the system prompt is pure context pollution.

For skills that reference relative resources (scripts/, references/), the base directory is already provided in the tool output when the skill is loaded — again, no need for the LLM to pre-know it.

Where the waste happens

In packages/opencode/src/tool/skill.ts (as documented in #13188):

...accessibleSkills.flatMap((skill) => [
  `  <skill>`,
  `    <name>${skill.name}</name>`,
  `    <description>${skill.description}</description>`,
  `    <location>${pathToFileURL(skill.location).href}</location>`,  // ← waste
  `  </skill>`,
])

And in the system prompt via Skill.fmt(list, { verbose: true }) (as documented in #22236):

Injection point Format Includes <location>?
System prompt body (verbose: true) XML Yes
Skill tool description (verbose: false) Compact markdown No

The verbose: false path already excludes <location>, proving it's not essential for the tool description. The system prompt just inherited it.

Proposed solutions

Option A (minimal, safe): Strip <location> from the system prompt only

  • Keep the verbose: true XML format but omit the <location> tag
  • The tool description already omits it (verbose: false)
  • The full location is still returned in the tool output when a skill is actually loaded (where it's genuinely useful for resolving relative resource paths)

Option B (configurable): Add an includeLocation flag to Skill.fmt()

Skill.fmt(list, { verbose: true, includeLocation: false })

Option C (root cause): Use the skill name as the identifier instead of the full path

<location>art-requirement</location>

This preserves the field for any consumer that depends on its presence, without the token bloat.

Related issues

Impact

For users with many skills (common in plugin-heavy setups), this is pure waste — 3K+ tokens that could be used for actual conversation context, tool results, or just cost savings. The fix is effectively a one-line change with zero functional impact, since the skill tool resolves namelocation internally regardless of what's in the system prompt.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions