Skip to content

feat(core): [v2] restore OPENCODE_DISABLE_CLAUDE_CODE - #44725

Open
malarahfelipe wants to merge 6 commits into
anomalyco:v2from
malarahfelipe:fix/opencode-disable-claude-code
Open

malarahfelipe wants to merge 6 commits into
anomalyco:v2from
malarahfelipe:fix/opencode-disable-claude-code

Conversation

@malarahfelipe

Copy link
Copy Markdown

Issue for this PR

Part of #36990

Type of change

  • New feature

What does this PR do?

Brings back OPENCODE_DISABLE_CLAUDE_CODE support on the v2 branch.

V1 honored this flag to keep OpenCode from reading ~/.claude (the prompt and skills). On v2 the env var was declared in the flag module but never wired into config discovery, so the flag had no effect: config kept pulling in the global ~/.claude directory plus any .claude dirs found walking up from the project.

The change gates the claude source in Config.discover() on the flag. The global ~/.claude directory and project .claude dirs are both skipped when OPENCODE_DISABLE_CLAUDE_CODE is set, matching V1 behavior. Setting it stops .claude skills and the CLAUDE.md prompt from loading, which is useful when you don't use Claude Code and don't want its content appearing in OpenCode.

I also added disableClaudeCode to Config.Options so callers can control this at the API level as well, not just through the env var. A test covers the discovery behavior (claude sources dropped, .agents untouched).

How did you verify your code works?

  • Ran the config test suite for the core package: all 36 tests pass, including the new one.
  • The new test sets up a global ~/.claude + .agents and a project .agents, then asserts that with the flag on, no claude entries are discovered while agents still are.
  • Lint (oxlint) is clean on the touched files.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@malarahfelipe

Copy link
Copy Markdown
Author

Did this as a partial implementation for the #36990 – can you take a look once you got time @rekram1-node ?

@malarahfelipe
malarahfelipe force-pushed the fix/opencode-disable-claude-code branch 2 times, most recently from 2a354d3 to ccabcb6 Compare September 11, 2026 11:47
@malarahfelipe
malarahfelipe force-pushed the fix/opencode-disable-claude-code branch from ccabcb6 to e2fcbd5 Compare September 11, 2026 11:55
@malarahfelipe

Copy link
Copy Markdown
Author

just rebased and solved conflicts, can you approve the workflows? @rekram1-node

@malarahfelipe malarahfelipe changed the title feat(core): restore OPENCODE_DISABLE_CLAUDE_CODE support in v2 feat(core): v2 – restore OPENCODE_DISABLE_CLAUDE_CODE Sep 15, 2026

@holny holny left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read this against current v2 — the placement looks right to me. discovery.ts is the only place that produces .claude roots, and everything downstream of sources.claude derives from that array: the skills scan in config/plugin/compatibility.ts:40 (via config.compatibility()) and the watch list in config/watch.ts:16. Emptying the array stops both the skill loading and the extra fs watches, and the AGENTS.md instruction plugin never touches .claude anyway, so this covers the whole surface on its own.

One gap: the env var is the part people actually set, but the new test only drives the disableClaudeCode option, so nothing pins the Config.boolean(...) wiring. It does work — I ran a probe against the effect version in this repo from packages/core and OPENCODE_DISABLE_CLAUDE_CODE=1 / yes gives true while 0 / unset gives false — but the config tests don't provide a ConfigProvider, so discovery now reads the ambient environment: with that variable exported in the shell, the assertions at config.test.ts:917 and :1573 flip to an empty claude list. Pinning a provider in one test, e.g. ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OPENCODE_DISABLE_CLAUDE_CODE: "1" } })) the way packages/ai tests do, would cover the env path and keep the suite hermetic.

Minor: config.ts:47 documents what global: false skips; a one-liner on disableClaudeCode noting the env var as the fallback would help the next person wiring a CLI flag.

…_CODE

- testLayer now provides ConfigProvider (empty env by default) so config
  discovery never reads the ambient process environment.
- Add an env-path test alongside the disableClaudeCode option test, with a
  no-flag assertion so it cannot pass trivially.
- Assert via Config.compatibility(); the claude/agents entry types were
  removed on v2, which left the option test red after the rebase.
- Document the OPENCODE_DISABLE_CLAUDE_CODE fallback on the option.
@malarahfelipe

Copy link
Copy Markdown
Author

Thanks @holny — both points addressed in eaa5aa4.

Env path is now pinned. testLayer builds the Config graph with ConfigProvider.layer(ConfigProvider.fromEnv({ env })) (empty env by default), so discovery never reads the ambient environment and the suite is hermetic. I ran test/config/config.test.ts with OPENCODE_DISABLE_CLAUDE_CODE=1, yes, and 0: 42/42 pass in every case (previously 6 failed with the var exported).

New test covers the env var. It pins { OPENCODE_DISABLE_CLAUDE_CODE: "1" }, asserts compatibility.claude === [] with agents untouched, then repeats the same fixture without the flag and asserts the claude source is back — so the empty list can't pass trivially. I also mutation-checked it by renaming the variable in discovery.ts: the test fails, so the Config.boolean(...) wiring is genuinely pinned.

Doc comment added on disableClaudeCode noting the env var fallback.

One thing the rebase surfaced: the option test was still asserting on claude/agents entry types, which v2 removed in favor of Config.compatibility() — it was already red at the branch head. It now asserts through compatibility().

Unrelated heads-up: test/config/reload.test.ts has two failures ("loads skills when ../.claude appears after startup" and the home/.claude variant) that reproduce on base v2; those files are identical to v2, so I left them alone.

@kvnloo

kvnloo commented Sep 15, 2026

Copy link
Copy Markdown

Why this matters

OPENCODE_DISABLE_CLAUDE_CODE was declared on v2 but never gated discovery, so ~/.claude / project .claude still loaded (#36990).

Evidence

Tip eaa5aa4 (base v2, mergeable / blocked):

  • packages/core/src/config/discovery.tsdisableClaudeCode from option or OPENCODE_DISABLE_CLAUDE_CODE; empty claude sources when set
  • packages/core/src/config.ts — wires option through
  • live: bun test test/config/config.test.ts (DISABLE_CLAUDE|claude|OPENCODE_DISABLE) → 3/3 pass on tip; 1/1 on v2 (only the older global-disable case — the two new flag tests are tip-only)
  • CI: standards / compliance success

Confirms the flag actually skips Claude sources. Happy to help land.

Ask (design, light) — prospective

  1. Is “env declared but unwired” a one-off, or should discovery sources be the checklist for every config kill-switch?
  2. For never-again: one gate helper for optional config roots (claude/agents/…), or keep per-source if sites?

Extract the duplicated claude/agents root building into an optionalRoots
helper: roots vanish when disabled, and the global directory joins only
while the global scope is enabled. No behavior change; covered by the
config discovery tests.
@malarahfelipe

Copy link
Copy Markdown
Author

Follow-up, @kvnloo — took the helper option and landed it in 02f41dd.

// Optional roots vanish when disabled; their global directory joins only
// while the global scope is enabled.
const optionalRoots = (name: string, globalPath: AbsolutePath, enabled = true) => 

claude: optionalRoots(".claude", globalClaudeDirectory, !disableClaudeCode),
agents: optionalRoots(".agents", globalAgentsDirectory),

On your two questions:

  1. Discovery should stay the checklist — Sources is the single producer, so a kill-switch that isn't referenced there is visible in one file, and the env test pins the Config.boolean(...) read.
  2. Went with a shared builder rather than a generic gate registry: future roots/switches add one call site. Kept positional params (3, trailing enabled = true) since internal helpers in this package do the same (normalizeSkills(input, encoded, diagnostics), request(daemon, value, start = false)); object options here are reserved for exported/schema-backed surfaces like discover(options?).

Behavior unchanged: 42/42 config tests with the var unset and =1, plus packages/core typecheck and oxlint clean.

@malarahfelipe malarahfelipe changed the title feat(core): v2 – restore OPENCODE_DISABLE_CLAUDE_CODE feat(core): [v2] restore OPENCODE_DISABLE_CLAUDE_CODE Sep 16, 2026
@malarahfelipe
malarahfelipe requested a review from holny September 16, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants