Bug
ensureTuiPluginEntry() in tui-config.ts writes to tui.json even when the user's real TUI config is in tui.jsonc, creating a shadowing file.
Root cause
resolveTuiConfigPath() checks tui.jsonc first (correct), then tui.json. But on first run when neither file exists, it defaults to creating tui.json. Once tui.json exists, subsequent runs keep writing to it — even though the user may have created tui.jsonc with their real config (keybinds, comments, etc.).
Since OpenCode resolves .json before .jsonc, the tui.json with just {} shadows the user's tui.jsonc with real content.
Current code
function resolveTuiConfigPath(): string {
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
const jsoncPath = join(configDir, "tui.jsonc");
const jsonPath = join(configDir, "tui.json");
if (existsSync(jsoncPath)) return jsoncPath;
if (existsSync(jsonPath)) return jsonPath;
return jsonPath; // default: create tui.json ← BUG
}
Reproduction
- User has
~/.config/opencode/tui.jsonc with keybinds and comments
- magic-context runs for the first time (neither
tui.json nor tui.jsonc exists yet)
- It creates
~/.config/opencode/tui.json with { "plugin": ["@cortexkit/opencode-magic-context@latest"] }
- User creates
~/.config/opencode/tui.jsonc with their real config
- On next startup,
resolveTuiConfigPath() finds tui.jsonc first → reads from .jsonc ✓
- But
tui.json still shadows tui.jsonc in OpenCode's config resolution (.json takes precedence)
Actual user impact
My current files:
~/.config/opencode/tui.json → { } ← created by magic-context, shadows .jsonc
~/.config/opencode/tui.jsonc → real config with keybinds, comments, etc.
Proposed fix
Two changes:
1. Default to .jsonc instead of .json
When neither file exists, create tui.jsonc (not tui.json). JSONC supports comments and is a superset of JSON, so it's strictly more user-friendly:
function resolveTuiConfigPath(): string {
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
const jsoncPath = join(configDir, "tui.jsonc");
const jsonPath = join(configDir, "tui.json");
if (existsSync(jsoncPath)) return jsoncPath;
if (existsSync(jsonPath)) return jsonPath;
return jsoncPath; // default: create tui.jsonc (supports comments)
}
2. Prefer .jsonc over .json when both exist
When both tui.json and tui.jsonc exist, the current code prefers .jsonc (correct for reading). But ensureTuiPluginEntry() writes back to whatever path resolveTuiConfigPath() returns. If it returns .jsonc, the write is correct. If .jsonc doesn't exist but .json does, it returns .json — which is fine if .jsonc genuinely doesn't exist.
The remaining edge case: if tui.json exists (from a prior buggy run) but the user's real config is in tui.jsonc, the code already prefers .jsonc on read. But the stale tui.json still shadows it at the OpenCode level. A cleanup step could be added:
// In ensureTuiPluginEntry(), after writing to the resolved path:
// If we wrote to .jsonc and a stale .json exists with only magic-context's entry,
// remove it to avoid shadowing.
if (configPath.endsWith('.jsonc')) {
const jsonSibling = configPath.replace(/\.jsonc$/, '.json');
if (existsSync(jsonSibling)) {
try {
const sibling = parse(readFileSync(jsonSibling, 'utf-8')) as Record<string, unknown>;
const siblingPlugins = Array.isArray(sibling.plugin) ? sibling.plugin : [];
if (Object.keys(sibling).length <= 1 && siblingPlugins.every(isMagicContextEntry)) {
unlinkSync(jsonSibling);
log(`[magic-context] removed stale shadowing ${jsonSibling}`);
}
} catch { /* leave it alone if parse fails */ }
}
}
Same pattern as context-mode issue
This is the same class of bug as mksglu/context-mode#849 — .json files shadowing .jsonc user configs. The fix pattern is consistent: prefer .jsonc for both default creation and write targets.
Affected file
src/shared/tui-config.ts — resolveTuiConfigPath() and ensureTuiPluginEntry()
Bug
ensureTuiPluginEntry()intui-config.tswrites totui.jsoneven when the user's real TUI config is intui.jsonc, creating a shadowing file.Root cause
resolveTuiConfigPath()checkstui.jsoncfirst (correct), thentui.json. But on first run when neither file exists, it defaults to creatingtui.json. Oncetui.jsonexists, subsequent runs keep writing to it — even though the user may have createdtui.jsoncwith their real config (keybinds, comments, etc.).Since OpenCode resolves
.jsonbefore.jsonc, thetui.jsonwith just{}shadows the user'stui.jsoncwith real content.Current code
Reproduction
~/.config/opencode/tui.jsoncwith keybinds and commentstui.jsonnortui.jsoncexists yet)~/.config/opencode/tui.jsonwith{ "plugin": ["@cortexkit/opencode-magic-context@latest"] }~/.config/opencode/tui.jsoncwith their real configresolveTuiConfigPath()findstui.jsoncfirst → reads from.jsonc✓tui.jsonstill shadowstui.jsoncin OpenCode's config resolution (.jsontakes precedence)Actual user impact
My current files:
Proposed fix
Two changes:
1. Default to
.jsoncinstead of.jsonWhen neither file exists, create
tui.jsonc(nottui.json). JSONC supports comments and is a superset of JSON, so it's strictly more user-friendly:2. Prefer
.jsoncover.jsonwhen both existWhen both
tui.jsonandtui.jsoncexist, the current code prefers.jsonc(correct for reading). ButensureTuiPluginEntry()writes back to whatever pathresolveTuiConfigPath()returns. If it returns.jsonc, the write is correct. If.jsoncdoesn't exist but.jsondoes, it returns.json— which is fine if.jsoncgenuinely doesn't exist.The remaining edge case: if
tui.jsonexists (from a prior buggy run) but the user's real config is intui.jsonc, the code already prefers.jsoncon read. But the staletui.jsonstill shadows it at the OpenCode level. A cleanup step could be added:Same pattern as context-mode issue
This is the same class of bug as mksglu/context-mode#849 —
.jsonfiles shadowing.jsoncuser configs. The fix pattern is consistent: prefer.jsoncfor both default creation and write targets.Affected file
src/shared/tui-config.ts—resolveTuiConfigPath()andensureTuiPluginEntry()