anomalyco / anomalyco/opencode

[FEATURE]:Allow V1 and V2 to use fully separate configuration roots (and enable strict validation for the V2 root)

Open
#46,757 0 comments 0 reactions 1 assignee View on GitHub

@nexxeln is already working on this.

Since Sep 2, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Feature hasn't been suggested before.
  • I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Context

Design feedback based on the published V2 documentation and on existing issues,
not a bug report. I am currently on V1 (1.18.26) and have not run V2 — the
concern below is what is keeping me from trying it. Please treat the
observations as questions about intended design.

Problem

V1 and V2 are installed side by side and run as opencode and opencode2, but
they discover configuration from the same locations. The compatibility is
asymmetric: V2 reads the V1 format, V1 cannot read the native V2 format.

The consequence is that a shared configuration is pinned to the V1 format for as
long as V1 is used anywhere. That includes a project's checked-in .opencode/,
where part of a team may still be on V1. Side-by-side installation exists to
enable gradual migration, but the shared configuration root prevents exactly
that.

The docs already acknowledge the collision, advising users not to point V1 at
configuration converted to the native V2 shape. That is guidance where a
mechanism would do.

What already exists, and why it is not enough

OPENCODE_CONFIG_DIR exists, but its semantics are inconsistent and contested:
per #32825, the old app loader treats it as an additive config directory while
the v2/core loader resolves it as a replacement for the XDG global directory.
PR #32824, which would have made it additive, is closed.

Two problems for this use case:

  1. The semantics are unsettled, so the isolation it provides cannot be relied on.
  2. It only addresses the global layer. Project configuration is discovered by
    walking up from the working directory to the project root, so an absolute
    path in an environment variable cannot express it.
This is a tree, not a file

Renaming the JSON file would only address a fraction of it. The configuration
root also contains agents/, commands/, skills/, plugins/, and
AGENTS.md, both globally and under a project .opencode/. Agent frontmatter
has V1 and V2 semantics (prompt/system, permission/permissions), and
plugins are a declared breaking change — V1 plugins do not work in V2 — yet both
runtimes still read the same plugins/ directory.

Proposal

A profile selector rather than a file rename or a bare path — proposed as a new
variable OPENCODE_PROFILE_NAME, not as another meaning for
OPENCODE_CONFIG_DIR. One value resolving to both layers:

  • global: ~/.config/<profile>/
  • project: <project>/.<profile>/

Default opencode, so existing behavior is unchanged and this is not a breaking
change. A beta user sets OPENCODE_PROFILE_NAME=opencode2 (or any name) and
gets a fully isolated tree. V1 does not recognize the variable and stays on its
default paths, so isolation works during side-by-side operation without any V1
change.

Advantages over the alternatives:

  • Covers the whole tree, not just the JSON file.
  • Expresses the project layer, which an absolute path cannot.
  • A separate variable avoids adding a fourth interpretation to
    OPENCODE_CONFIG_DIR, whose semantics are already inconsistent between
    loaders (#32825) and unresolved (#32824 closed).
  • _NAME rather than _DIR signals that the value is an identifier, not a
    path.
  • Version-neutral: the default name stays opencode and needs no rename once V1
    is retired, and a future change to the V2 directory layout does not require
    renaming anything.
The main benefit: strict validation becomes possible

This is the part I care about most, and it is not achievable without separation.

As long as one tree must be readable by both runtimes, V2 cannot validate it
strictly. It has to accept V1 keys, normalize them in memory, and stay silent —
so unknown or misplaced keys cannot be rejected. The result today is that a key
can fall into one of three states, and only two of them are visible to the user:

  1. Recognized and applied — silent, as expected.
  2. Recognized but unsupported — ignored with a warning
    (compaction.tail_turns, compaction.prune, logLevel). Honest.
  3. Recognized, preserved, but not applied by the runner — silent. The Agents
    doc documents this for per-agent request overlays: the schema accepts them,
    the session runner preserves them on the agent definition, but does not apply
    them to model requests.

With an isolated V2 root, the V2 loader owes nothing to V1 compatibility and can
reject unknown keys outright instead of quietly normalizing or ignoring them.
Strict validation of valid and effective keys becomes a design option rather
than something structurally excluded. Category 3 could then be a load-time error
or an explicit "recognized, not currently applied" notice instead of silence.

Note on the desktop client

Since V2 is a service plus clients, and cli.json is owned by the terminal
client and not loaded by the background service, the configuration root has to
resolve in the service. A CLI flag would not propagate on its own. The cleanest
shape is probably a service startup option, with clients selecting which
instance to connect to — which also gives the desktop app something it can
express as an instance switcher rather than a command-line flag.

Related: #32825, #32824, #39431
Prior discussion, closed without a mechanism: #41081, #12607

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.