fix(config): fall back to legacy path when migration fails and use canonical basename everywhere (#3133)

Root cause: loadPluginConfig() unconditionally switched userConfigPath to the
canonical name after calling migrateLegacyConfigFile(), even when migration
failed (e.g. file lock on Windows, permission denied). This left the config
path pointing to a non-existent file, so the plugin config silently loaded
as empty defaults.

Additionally, several fallback/default paths were hardcoded to the legacy
'oh-my-opencode' basename instead of using CONFIG_BASENAME ('oh-my-openagent'),
causing CLI config commands (writeOmoConfig, detectCurrentConfig) to write to
the wrong filename.

Changes:
- plugin-config.ts: check migrateLegacyConfigFile() return value; only switch
  to canonical path if migration succeeded OR the canonical file already exists
- opencode-config-dir.ts: use CONFIG_BASENAME for omoConfig path in
  getOpenCodeConfigPaths()
- config-context.ts: getOmoConfigPath() now uses detectPluginConfigFile() to
  find whichever name variant actually exists on disk
- plugin-config.ts: default fallback paths use CONFIG_BASENAME instead of
  hardcoded legacy name
- Added test: loadPluginConfig still loads config when migration fails
  (read-only directory simulation)
This commit is contained in:
YeonGyu-Kim
2026-04-05 14:26:54 +09:00
parent 2ae9a757fb
commit d7c2b6249b
5 changed files with 62 additions and 11 deletions
+16 -6
View File
@@ -175,7 +175,7 @@ export function loadPluginConfig(
let userConfigPath =
userDetected.format !== "none"
? userDetected.path
: path.join(configDir, "oh-my-opencode.json");
: path.join(configDir, `${CONFIG_BASENAME}.json`);
if (userDetected.legacyPath) {
log("Canonical plugin config detected alongside legacy config. Remove the legacy file to avoid confusion.", {
@@ -186,11 +186,16 @@ export function loadPluginConfig(
// Auto-copy legacy config file to canonical name if needed
if (userDetected.format !== "none" && path.basename(userDetected.path).startsWith(LEGACY_CONFIG_BASENAME)) {
migrateLegacyConfigFile(userDetected.path);
userConfigPath = path.join(
const migrated = migrateLegacyConfigFile(userDetected.path);
const canonicalPath = path.join(
path.dirname(userDetected.path),
`${CONFIG_BASENAME}${path.extname(userDetected.path)}`
);
// Only switch to canonical path if migration succeeded OR canonical file already exists
if (migrated || fs.existsSync(canonicalPath)) {
userConfigPath = canonicalPath;
}
// Otherwise keep loading from the legacy path that was detected
}
// Project-level config path - prefer .jsonc over .json
@@ -199,7 +204,7 @@ export function loadPluginConfig(
let projectConfigPath =
projectDetected.format !== "none"
? projectDetected.path
: path.join(projectBasePath, "oh-my-opencode.json");
: path.join(projectBasePath, `${CONFIG_BASENAME}.json`);
if (projectDetected.legacyPath) {
log("Canonical plugin config detected alongside legacy config. Remove the legacy file to avoid confusion.", {
@@ -210,11 +215,16 @@ export function loadPluginConfig(
// Auto-copy legacy project config file to canonical name if needed
if (projectDetected.format !== "none" && path.basename(projectDetected.path).startsWith(LEGACY_CONFIG_BASENAME)) {
migrateLegacyConfigFile(projectDetected.path);
projectConfigPath = path.join(
const projectMigrated = migrateLegacyConfigFile(projectDetected.path);
const canonicalProjectPath = path.join(
path.dirname(projectDetected.path),
`${CONFIG_BASENAME}${path.extname(projectDetected.path)}`
);
// Only switch to canonical path if migration succeeded OR canonical file already exists
if (projectMigrated || fs.existsSync(canonicalProjectPath)) {
projectConfigPath = canonicalProjectPath;
}
// Otherwise keep loading from the legacy path that was detected
}
// Load user config first (base). Parse empty config through Zod to apply field defaults.