You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
core: restore non-experimental environment variable compatibility in v2 #36990
V2 does not preserve a number of non-experimental environment-variable behaviors supported by V1. Some variables remain declared in packages/core/src/flag/flag.ts but have no active V2 consumer; others disappeared with their V1 subsystem.
This is the umbrella issue for the broader compatibility gap reported narrowly in #36837.
OPENCODE_CONFIG_DIR changes the global config root in V2 instead of acting as the additional explicit config source it was in V1. Confirm and restore the intended compatibility semantics.
OPENCODE_SERVER_PASSWORD is still accepted, but the V2 CLI prefers OPENCODE_PASSWORD and managed-server credential propagation differs from V1.
OPENCODE_SERVER_USERNAME only affects manually configured server authentication, not the new managed-service credential path.
Intentional change
OPENCODE_TUI_CONFIG is not included in the compatibility checklist. V2 intentionally moved to one global cli.json and does not support arbitrary or project-local TUI configuration.
Expected behavior
Per the V1-to-V2 migration contract, non-API V1 functionality is intended to remain compatible. For every item above, either:
restore the V1 behavior in V2 and add coverage, or
explicitly decide that the behavior is obsolete, remove stale declarations/documentation, and document the migration path.
Adding one data point on OPENCODE_DISABLE_CLAUDE_CODE_SKILLS, from V2 2.0.18 on Linux:
V2 always loads ~/.claude/skills. With the variable set, the claude.ai-synced skills that Claude Code writes under ~/.claude/skills/synced/ still show up in skill.list and in the model's skill catalog.
A plugin can't work around this. ctx.skill.transform((editor) => ...) only sees the built-in skills (opencode, report) in editor.list(). Skills from compatibility sources are added outside the transform, so editor.remove(id) can't hide them.
So for now there's no config key, env var or plugin hook that turns off one compatibility source. A config switch (for example, per-source toggles next to the skills array) would cover it, and so would honoring the V1 variable.
Confirming this on 2.0.26 (Windows 11): OPENCODE_DISABLE_CLAUDE_CODE_SKILLS is still ignored, and the claude.ai skills the Claude desktop app syncs into ~/.claude/skills/synced/<account>/ (23 here) all appear in GET /api/skill and in the model's skill catalog. There is no config key for it either: ~/.claude is always a compatibility root while global config is on (packages/core/src/config/discovery.ts:71-76).
One correction to the comment above: a plugin can remove them, but only if its transform runs last. Compatibility skills are added through a transform too (packages/core/src/config/plugin/compatibility.ts:72-75), but that plugin registers its transform only after it has scanned the folders, and transforms run in registration order (packages/core/src/state.ts:172-194). A transform registered in setup therefore runs first and only sees opencode and report, which matches what you saw.
Workaround that works here: when a Claude skill is visible, register the transform again (and dispose the old one), which moves it to the end of the order. Measured in a throwaway opencode serve on 2.0.26: 29 skills, 23 of them from ~/.claude/skills, became 6. On the live service, opencode api get /api/skill then listed 7 skills, none from .claude.
// ~/.config/opencode/plugin/no-claude-skills.tsimport{Plugin}from"@opencode/plugin"constfromClaude=(path: unknown)=>String(path??"").replace(/\\/g,"/").toLowerCase().includes("/.claude/skills/")exportdefaultPlugin.define({id: "local.no-claude-skills",asyncsetup(ctx){constdrop=(editor: {list(): ReadonlyArray<{id: string;path: string}>;remove(id: string): void})=>{for(constskillofeditor.list())if(fromClaude(skill.path))editor.remove(skill.id)}letregistration=awaitctx.skill.transform(drop)letstopped=falseletbusy=false// Our transform ran before the compatibility one: register it again so it moves to the end.constcheck=async()=>{if(stopped||busy)returnbusy=truetry{constanswer: any=awaitctx.skill.list()constlist: Array<{path?: string}>=Array.isArray(answer) ? answer : Array.isArray(answer?.data) ? answer.data : []if(list.some((skill)=>fromClaude(skill.path))){constold=registrationregistration=awaitctx.skill.transform(drop)awaitold.dispose()}}finally{busy=false}}void(async()=>{forawait(consteventofctx.event.subscribe()asAsyncIterable<{type?: string}>){if(stopped)breakif(event?.type==="skill.updated")awaitcheck()}})().catch(()=>{})setTimeout(()=>voidcheck(),0)returnasync()=>{stopped=trueawaitregistration.dispose()}},})
This depends on registration order, so it is a workaround, not a fix. A supported switch would be better: honoring the V1 variable, or a per-source toggle in config as suggested above. A way for a plugin to have its transform run after the built-in ones would also make this kind of filter reliable.
Status for anyone following: #51888 restores OPENCODE_DISABLE_CLAUDE_CODE (open, mergeable, pending review). Once merged, OPENCODE_DISABLE_CLAUDE_CODE=1 gates the .claude sources in config discovery, so .claude/skills (and their file watches) stop loading while .agents sources stay.
Scope note: the granular OPENCODE_DISABLE_CLAUDE_CODE_SKILLS / OPENCODE_DISABLE_CLAUDE_CODE_PROMPT variables are not wired by that PR. v2 only consumes .claude for skill discovery (it does not load a CLAUDE.md prompt), so _SKILLS would map to the same gate and _PROMPT would be a no-op today. If maintainers want the granular names restored too, it can be added on top.
For the claude vars, the supported v2 switch is OPENCODE_DISABLE_CLAUDE_CODE=1 (restored by #51888) — it drops .claude/skills discovery, which is the only .claude surface v2 reads. The granular _SKILLS / _PROMPT names remain unsupported in v2: _SKILLS was never declared there, and _PROMPT is a no-op because v2 does not load a CLAUDE.md prompt.
Correction to the note above: #55774 (superseding #51888) does keep OPENCODE_DISABLE_CLAUDE_CODE_SKILLS working — it maps both the umbrella and _SKILLS to the same claude gate at the CLI boundary, since v2 only reads .claude for skills. _PROMPT stays a no-op because v2 doesn't load a CLAUDE.md prompt.
Activity
malarahfelipe commentedon Aug 27, 2026
done a partial MR #44725 for that – can you take a look once you got time @rekram1-node ?
Xarth-Mai commentedon Sep 22, 2026
One more v2 path seems worth including here.
On v2.0.12, project instruction discovery can be disabled, but a successful
readstill performs its own nestedAGENTS.mddiscovery:packages/core/src/tool/plugin/read.tscallsfs.up()andsessionInstructions.load()after an internal file read/listing.InstructionDiscoveryproject setting.packages/core/src/session/instructions.tsthen reads those candidates directly and publishes them as durable synthetic instructions.So the project-config / project-instruction opt-out currently does not cover read-time nested instruction discovery.
It would be useful if the same project discovery policy covered this path too, while keeping global
AGENTS.mdand ordinary file reads available.Related context: #13550 discussed an opt-out for built-in dynamic AGENTS.md loading; #36837 covers project config discovery more broadly.
fbaltor commentedon Sep 28, 2026
Adding one data point on
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS, from V2 2.0.18 on Linux:~/.claude/skills. With the variable set, the claude.ai-synced skills that Claude Code writes under~/.claude/skills/synced/still show up inskill.listand in the model's skill catalog.ctx.skill.transform((editor) => ...)only sees the built-in skills (opencode,report) ineditor.list(). Skills from compatibility sources are added outside the transform, soeditor.remove(id)can't hide them.So for now there's no config key, env var or plugin hook that turns off one compatibility source. A config switch (for example, per-source toggles next to the
skillsarray) would cover it, and so would honoring the V1 variable.Abdo-Eid commentedon Oct 11, 2026
Confirming this on 2.0.26 (Windows 11):
OPENCODE_DISABLE_CLAUDE_CODE_SKILLSis still ignored, and the claude.ai skills the Claude desktop app syncs into~/.claude/skills/synced/<account>/(23 here) all appear inGET /api/skilland in the model's skill catalog. There is no config key for it either:~/.claudeis always a compatibility root while global config is on (packages/core/src/config/discovery.ts:71-76).One correction to the comment above: a plugin can remove them, but only if its transform runs last. Compatibility skills are added through a transform too (
packages/core/src/config/plugin/compatibility.ts:72-75), but that plugin registers its transform only after it has scanned the folders, and transforms run in registration order (packages/core/src/state.ts:172-194). A transform registered insetuptherefore runs first and only seesopencodeandreport, which matches what you saw.Workaround that works here: when a Claude skill is visible, register the transform again (and dispose the old one), which moves it to the end of the order. Measured in a throwaway
opencode serveon 2.0.26: 29 skills, 23 of them from~/.claude/skills, became 6. On the live service,opencode api get /api/skillthen listed 7 skills, none from.claude.This depends on registration order, so it is a workaround, not a fix. A supported switch would be better: honoring the V1 variable, or a per-source toggle in config as suggested above. A way for a plugin to have its transform run after the built-in ones would also make this kind of filter reliable.
malarahfelipe commentedon Oct 15, 2026
Status for anyone following: #51888 restores
OPENCODE_DISABLE_CLAUDE_CODE(open, mergeable, pending review). Once merged,OPENCODE_DISABLE_CLAUDE_CODE=1gates the.claudesources in config discovery, so.claude/skills(and their file watches) stop loading while.agentssources stay.Scope note: the granular
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS/OPENCODE_DISABLE_CLAUDE_CODE_PROMPTvariables are not wired by that PR. v2 only consumes.claudefor skill discovery (it does not load aCLAUDE.mdprompt), so_SKILLSwould map to the same gate and_PROMPTwould be a no-op today. If maintainers want the granular names restored too, it can be added on top.malarahfelipe commentedon Oct 15, 2026
For the claude vars, the supported v2 switch is
OPENCODE_DISABLE_CLAUDE_CODE=1(restored by #51888) — it drops.claude/skillsdiscovery, which is the only.claudesurface v2 reads. The granular_SKILLS/_PROMPTnames remain unsupported in v2:_SKILLSwas never declared there, and_PROMPTis a no-op because v2 does not load aCLAUDE.mdprompt.malarahfelipe commentedon Oct 15, 2026
Correction to the note above: #55774 (superseding #51888) does keep
OPENCODE_DISABLE_CLAUDE_CODE_SKILLSworking — it maps both the umbrella and_SKILLSto the same claude gate at the CLI boundary, since v2 only reads.claudefor skills._PROMPTstays a no-op because v2 doesn't load aCLAUDE.mdprompt.rekram1-node commentedon Oct 16, 2026
@malarahfelipe plz stop spamming our issues
feat(core): restore OPENCODE_DISABLE_CLAUDE_CODE for compatibility roots