core: restore non-experimental environment variable compatibility in v2 · Issue #36990 · anomalyco/opencode · GitHub
Skip to content

core: restore non-experimental environment variable compatibility in v2 #36990

Open

Description

@opencode-agent

Summary

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.

Audited against:

  • V1 / dev: b6478dcebf1da4a559055c702c62b9294c41afdb
  • V2: ba1f3d3d32690879fbed156d53570b8652e254ed

Completely ignored in V2

Configuration and permissions

  • OPENCODE_CONFIG — explicit config-file override
  • OPENCODE_CONFIG_CONTENT — inline config injection
  • OPENCODE_PERMISSION — JSON permission override
  • OPENCODE_DISABLE_AUTOCOMPACT — force-disable automatic compaction
  • OPENCODE_DISABLE_PRUNE — force-disable compaction pruning
  • OPENCODE_FAKE_VCS — override detected VCS

Sharing and authentication

  • OPENCODE_AUTO_SHARE — force automatic sharing
  • OPENCODE_DISABLE_SHARE — globally disable sharing
  • OPENCODE_AUTH_CONTENT — in-memory auth JSON override

Plugins, skills, and compatibility discovery

  • OPENCODE_PURE — suppress external plugins/runtime integrations
  • OPENCODE_DISABLE_DEFAULT_PLUGINS — suppress built-in plugins
  • OPENCODE_DISABLE_EXTERNAL_SKILLS — suppress external skill discovery
  • OPENCODE_DISABLE_CLAUDE_CODE — suppress Claude compatibility discovery
  • OPENCODE_DISABLE_CLAUDE_CODE_PROMPT — suppress Claude prompt discovery
  • OPENCODE_DISABLE_CLAUDE_CODE_SKILLS — suppress Claude skill discovery
  • OPENCODE_PLUGIN_META_FILE — override plugin metadata path

Other runtime controls

  • OPENCODE_DISABLE_LSP_DOWNLOAD — prevent language-server installation
  • OPENCODE_ENABLE_QUESTION_TOOL — control question-tool availability
  • OPENCODE_DISABLE_EMBEDDED_WEB_UI — suppress embedded UI routes
  • OPENCODE_AUTO_HEAP_SNAPSHOT — enable automatic heap snapshots
  • OPENCODE_ALWAYS_NOTIFY_UPDATE — force update notifications

Partially handled or changed

  • OPENCODE_DISABLE_PROJECT_CONFIG only suppresses project AGENTS.md. V2 still loads project opencode.json(c) and project .opencode, .claude, and .agents directories. See core: OPENCODE_DISABLE_PROJECT_CONFIG is ignored in v2 #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:

  1. restore the V1 behavior in V2 and add coverage, or
  2. explicitly decide that the behavior is obsolete, remove stale declarations/documentation, and document the migration path.

Activity

  1. malarahfelipe commented on Aug 27, 2026

    @malarahfelipe
    Contributor

    done a partial MR #44725 for that – can you take a look once you got time @rekram1-node ?

  2. Xarth-Mai commented on Sep 22, 2026

    @Xarth-Mai

    One more v2 path seems worth including here.

    On v2.0.12, project instruction discovery can be disabled, but a successful read still performs its own nested AGENTS.md discovery:

    • packages/core/src/tool/plugin/read.ts calls fs.up() and sessionInstructions.load() after an internal file read/listing.
    • That path does not check the InstructionDiscovery project setting.
    • packages/core/src/session/instructions.ts then 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.md and 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.

  3. fbaltor commented on Sep 28, 2026

    @fbaltor

    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.

  4. Abdo-Eid commented on Oct 11, 2026

    @Abdo-Eid

    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.ts
    import { Plugin } from "@opencode/plugin"
    
    const fromClaude = (path: unknown) => String(path ?? "").replace(/\\/g, "/").toLowerCase().includes("/.claude/skills/")
    
    export default Plugin.define({
      id: "local.no-claude-skills",
      async setup(ctx) {
        const drop = (editor: { list(): ReadonlyArray<{ id: string; path: string }>; remove(id: string): void }) => {
          for (const skill of editor.list()) if (fromClaude(skill.path)) editor.remove(skill.id)
        }
        let registration = await ctx.skill.transform(drop)
        let stopped = false
        let busy = false
    
        // Our transform ran before the compatibility one: register it again so it moves to the end.
        const check = async () => {
          if (stopped || busy) return
          busy = true
          try {
            const answer: any = await ctx.skill.list()
            const list: Array<{ path?: string }> = Array.isArray(answer) ? answer : Array.isArray(answer?.data) ? answer.data : []
            if (list.some((skill) => fromClaude(skill.path))) {
              const old = registration
              registration = await ctx.skill.transform(drop)
              await old.dispose()
            }
          } finally {
            busy = false
          }
        }
    
        void (async () => {
          for await (const event of ctx.event.subscribe() as AsyncIterable<{ type?: string }>) {
            if (stopped) break
            if (event?.type === "skill.updated") await check()
          }
        })().catch(() => {})
        setTimeout(() => void check(), 0)
    
        return async () => {
          stopped = true
          await registration.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.

  5. malarahfelipe commented on Oct 15, 2026

    @malarahfelipe
    Contributor

    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.

  6. malarahfelipe commented on Oct 15, 2026

    @malarahfelipe
    Contributor

    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.

  7. malarahfelipe commented on Oct 15, 2026

    @malarahfelipe
    Contributor

    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.

  8. rekram1-node commented on Oct 16, 2026

    @rekram1-node
    Collaborator

    @malarahfelipe plz stop spamming our issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions