Symptom
When multiple .opencode/ directories exist in a project tree (e.g. <worktree>/.opencode/ and <workspace>/.opencode/), the outer directory's opencode.json / pawwork.json overrides the inner one at merge time. The expected invariant across the rest of the config system is "innermost wins".
Location
packages/opencode/src/config/config.ts:1425-1442 — loadAll iterates directories with last-wins merge
packages/opencode/src/config/paths.ts — ConfigPaths.directories() concatenates Filesystem.up(...) outputs
packages/opencode/src/util/filesystem.ts:264-277 — Filesystem.up yields from start (innermost) → parents
Trace
// directories() returns:
[
Global.Path.config, // global (correctly merged first)
...Array.fromAsync(Filesystem.up({ // PROJECT: start → root
start: ctx.directory, // innermost first in the array
stop: ctx.worktree, // outermost last in the array
})),
...Array.fromAsync(Filesystem.up({ // HOME (single)
start: Global.Path.home, stop: Global.Path.home,
})),
...(Flag.OPENCODE_CONFIG_DIR ? [...] : []),
]
Merge loop iterates this list in order; merge() is last-wins. So within the project segment:
- Innermost
.opencode/ is merged first → overwritten by each outer dir
- Outermost
.opencode/ is the final write → wins
This is the opposite of the "innermost wins" convention used by the project-root pawwork.json/opencode.json cascade (fixed in PR #18 via rootFirst: true in findUp).
Why not fixed in PR #18
PR #18 only added pawwork.json / pawwork.jsonc to the filename candidate array at line 1435. The directory iteration order was not modified. The bug is pre-existing upstream behavior and would affect anyone with multiple .opencode/ directories in their tree (common when working inside a monorepo subproject or a git worktree).
Suggested fix
Either:
- Reverse the project segment order in
ConfigPaths.directories(): Array.fromAsync(up(...)).then(r => r.reverse()) so outermost loads first, innermost last — then merge last-wins gives innermost victory.
- Add
rootFirst to Filesystem.up (mirror findUp API) and flip the project walk to start at worktree-root and end at ctx.directory.
Option (1) is a one-line caller fix. Option (2) is a small API extension that could benefit other callers.
Also verify directories() global segment (Global.Path.config) should remain at index 0 — it should be overridden by project dirs, so global-first + last-wins is correct there.
Acceptance
- With
<workspace>/.opencode/pawwork.json setting model: A and <workspace>/subpkg/.opencode/pawwork.json setting model: B, running from subpkg/ should load model: B.
- Regression test fixture in
packages/opencode/test/config/ if tests cover this area.
Scope
Not part of PR #18 (brand/scope cleanup). File separately because the fix touches config cascade semantics and deserves its own review + regression coverage.
Symptom
When multiple
.opencode/directories exist in a project tree (e.g.<worktree>/.opencode/and<workspace>/.opencode/), the outer directory'sopencode.json/pawwork.jsonoverrides the inner one at merge time. The expected invariant across the rest of the config system is "innermost wins".Location
packages/opencode/src/config/config.ts:1425-1442—loadAlliteratesdirectorieswith last-wins mergepackages/opencode/src/config/paths.ts—ConfigPaths.directories()concatenatesFilesystem.up(...)outputspackages/opencode/src/util/filesystem.ts:264-277—Filesystem.upyields fromstart(innermost) → parentsTrace
Merge loop iterates this list in order;
merge()is last-wins. So within the project segment:.opencode/is merged first → overwritten by each outer dir.opencode/is the final write → winsThis is the opposite of the "innermost wins" convention used by the project-root
pawwork.json/opencode.jsoncascade (fixed in PR #18 viarootFirst: trueinfindUp).Why not fixed in PR #18
PR #18 only added
pawwork.json/pawwork.jsoncto the filename candidate array at line 1435. The directory iteration order was not modified. The bug is pre-existing upstream behavior and would affect anyone with multiple.opencode/directories in their tree (common when working inside a monorepo subproject or a git worktree).Suggested fix
Either:
ConfigPaths.directories():Array.fromAsync(up(...)).then(r => r.reverse())so outermost loads first, innermost last — then merge last-wins gives innermost victory.rootFirsttoFilesystem.up(mirrorfindUpAPI) and flip the project walk to start at worktree-root and end atctx.directory.Option (1) is a one-line caller fix. Option (2) is a small API extension that could benefit other callers.
Also verify
directories()global segment (Global.Path.config) should remain at index 0 — it should be overridden by project dirs, so global-first + last-wins is correct there.Acceptance
<workspace>/.opencode/pawwork.jsonsettingmodel: Aand<workspace>/subpkg/.opencode/pawwork.jsonsettingmodel: B, running fromsubpkg/should loadmodel: B.packages/opencode/test/config/if tests cover this area.Scope
Not part of PR #18 (brand/scope cleanup). File separately because the fix touches config cascade semantics and deserves its own review + regression coverage.