-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Description
Bug Summary
OpenCode's permission system scans multiple skill directories (~/.claude/skills/, ~/.agents/skills/, ~/.config/opencode/skills/) and registers external_directory permission rules for each discovered skill. However, it does not deduplicate symlinks — if ~/.claude/skills/foo is a symlink to ~/.agents/skills/foo, OpenCode registers two separate rules for the same physical directory, doubling the permission ruleset size with zero security or functional benefit.
Combined with the fact that the full ruleset is serialized into every service=permission log line at INFO level (see #17218), this symlink duplication directly doubles the log bloat rate — contributing to 50GB+ log accumulation in hours.
Environment
| Component | Version |
|---|---|
| OpenCode | 1.1.53 |
| OS | macOS 15 (darwin-arm64) |
Skills in ~/.agents/skills/ |
613 (actual files) |
Skills in ~/.claude/skills/ |
613 (all symlinks → ~/.agents/skills/) |
Skills in ~/.config/opencode/skills/ |
10 |
Reproduction
Setup
# Install skills via any skill manager — they land in ~/.agents/skills/
# The skill installer also creates symlinks in ~/.claude/skills/
ls -la ~/.claude/skills/ | head -5
# Output:
# lrwxr-xr-x accessibility-compliance -> ../../.agents/skills/accessibility-compliance
# lrwxr-xr-x add-educational-comments -> ../../.agents/skills/add-educational-comments
# ...
# Verify same inode (same physical file):
stat -f "%i" ~/.agents/skills/accessibility-compliance/SKILL.md
# 110667057
stat -f "%i" ~/.claude/skills/accessibility-compliance/SKILL.md
# 110667057 ← identicalSteps
- Have skills in both
~/.agents/skills/and~/.claude/skills/(symlinked) - Start OpenCode
- Trigger any tool call
- Inspect the permission log line
Expected
OpenCode resolves symlinks and deduplicates, registering one external_directory rule per unique physical skill directory:
ruleset=[
{"permission":"external_directory","pattern":"/Users/xuyu/.agents/skills/foo/*","action":"allow"},
...
]
# Total: ~660 external_directory rules for 613 skills + other paths
Actual
OpenCode registers two rules per skill — one for each scanned path, regardless of symlink resolution:
ruleset=[
{"permission":"external_directory","pattern":"/Users/xuyu/.agents/skills/foo/*","action":"allow"},
{"permission":"external_directory","pattern":"/Users/xuyu/.claude/skills/foo/*","action":"allow"},
...
]
# Total: 1,262 external_directory rules for 613 unique skills (2x duplication)
Impact
Quantified
| Metric | With symlink duplication | Without (deduplicated) |
|---|---|---|
| Permission rules | 1,278 | 660 |
| Ruleset size per log line | ~148KB | ~76KB |
| Log bytes per tool call (2 lines) | ~296KB | ~152KB |
| Log growth rate (active session) | ~29MB/min | ~15MB/min |
The duplication alone accounts for ~50% of the total log volume — turning a bad situation (full-ruleset logging, #17218) into a catastrophic one.
Why this happens
The skill ecosystem uses a common pattern:
- Primary store:
~/.agents/skills/— actual SKILL.md files - Compatibility layer:
~/.claude/skills/— symlinks to primary store (for Claude Code compatibility)
The skill installer (tracked via ~/.agents/.skill-lock.json) creates both, and OpenCode's permission scanner treats them as independent directories.
Proposed Fix
Option A: Resolve symlinks before rule registration (Recommended)
Before registering an external_directory rule, call fs.realpathSync() (or equivalent) on the skill directory. If the resolved path already has a rule, skip the duplicate.
// Pseudocode
const resolvedPaths = new Set<string>();
for (const skillDir of allSkillDirectories) {
const realPath = fs.realpathSync(skillDir);
if (resolvedPaths.has(realPath)) continue; // skip symlink duplicate
resolvedPaths.add(realPath);
rules.push({ permission: "external_directory", pattern: `${skillDir}/*`, action: "allow" });
}Option B: Deduplicate by skill name
If two directories contain a skill with the same name, only register one rule (prefer the primary/non-symlink path).
Option C: Register a single wildcard rule per skill root
Instead of one rule per skill, register one rule per skill directory root:
{"permission":"external_directory","pattern":"/Users/xuyu/.agents/skills/*","action":"allow"}
This would reduce 618 rules to 1 rule — a 618x reduction. The security granularity loss is minimal since all skills in the directory are already trusted.
Workaround
Remove the symlink directory and recreate it empty:
rm -rf ~/.claude/skills
mkdir -p ~/.claude/skills
# Reduces permission rules from 1,278 to 660 immediately
# Takes effect on next OpenCode session startRelated
- Permission service logs full ruleset on every tool call, causing 50GB+ log bloat #17218 — Permission service logs full ruleset on every tool call (the root logging issue)
- Together, these two issues (full-ruleset logging + symlink duplication) account for the 50GB+ log bloat reports