Skip to content

fix(config): migrate legacy config.yaml after config dir move#596

Closed
skevetter wants to merge 2 commits into
mainfrom
fix/config-dir-migration
Closed

fix(config): migrate legacy config.yaml after config dir move#596
skevetter wants to merge 2 commits into
mainfrom
fix/config-dir-migration

Conversation

@skevetter

@skevetter skevetter commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

v1.4.0-beta.1 broke every existing workspace with provider "docker" is not initialized.

Root cause: PR #580 moved the unix config directory from ~/.devsy to ~/.config/devsy without a migration. Each provider's Initialized flag lives in config.yaml (Contexts[ctx].Providers[...].Initialized), which is read from ConfigDir. After upgrade, devsy read an empty config from the new (empty) directory — LoadConfig silently returns an empty config when the file is absent — so provider.State == nil and the check at pkg/workspace/workspace.go failed for all providers.

Fix

ConfigFilePath() now transparently relocates a legacy DataDir/config.yaml (~/.devsy/config.yaml) into the new ConfigDir when the new location is empty. The operation is idempotent and self-limiting:

  • Upgrading user: legacy file exists, new path empty → renamed once, carries provider/context state forward.
  • Subsequent launches: file already at new path → no-op.
  • Fresh install / Windows (where ConfigDir always differed from DataDir) → no-op; never clobbers an existing config.

Added migration and no-clobber tests for darwin and linux path managers.

Summary by CodeRabbit

  • Bug Fixes
    • Existing configuration files are now automatically moved to the new standard location when first accessed.
    • If a configuration file is already present in the new location, it is left unchanged.
    • Missing legacy config files are handled gracefully without causing errors.

PR #580 moved unix ConfigDir from ~/.devsy to ~/.config/devsy without a
migration. config.yaml holds each provider's Initialized flag, so after
upgrade devsy read an empty config from the new dir and every workspace
failed with "provider is not initialized".

ConfigFilePath now relocates a legacy DataDir/config.yaml into the new
ConfigDir when the new location is empty. No-op on Windows and for fresh
installs.
@netlify

netlify Bot commented Jul 5, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit 7d36b8c
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6a4a842bc4401300084eb6f0

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds automatic migration of a legacy config file to a new config path within ConfigFilePath(), implemented via a new migrateLegacyConfigFile helper that renames the legacy file when the target doesn't exist. Adds corresponding Darwin and Linux test coverage.

Changes

Legacy config migration

Layer / File(s) Summary
Migration helper and ConfigFilePath wiring
pkg/config/pathmanager.go
ConfigFilePath() computes configPath, calls new migrateLegacyConfigFile to rename an existing legacy config from the data dir to the new path, returns an error on migration failure; several section-header comments removed.
Darwin and Linux migration tests
pkg/config/pathmanager_darwin_test.go, pkg/config/pathmanager_linux_test.go
New tests verify legacy config is migrated to the new path with contents preserved and legacy file removed, and that an existing current config is not overwritten.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • devsy-org/devsy#74: The main PR extends the ConfigFilePath() logic introduced in this PR with legacy migration and tests.
  • devsy-org/devsy#580: Changes the Unix ConfigDir/ConfigFilePath to the XDG layout that this PR's migration logic now targets.

Suggested labels: size/m

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: migrating the legacy config.yaml after the config directory move.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@netlify

netlify Bot commented Jul 5, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh canceled.

Name Link
🔨 Latest commit 7d36b8c
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6a4a842bef55db000868ed38

@github-actions github-actions Bot added the size/l label Jul 5, 2026
Move migrateLegacyConfigFile after exported methods (funcorder), annotate
test reads with #nosec G304, and drop section-divider comments.
@skevetter
skevetter marked this pull request as ready for review July 5, 2026 21:07
@skevetter skevetter closed this Jul 5, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/config/pathmanager_darwin_test.go (1)

99-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider also asserting the legacy file's fate in the no-clobber test.

The test verifies the current config isn't overwritten, but doesn't assert whether legacyPath is left in place afterward (since migrateLegacyConfigFile short-circuits without touching it). Adding that assertion would fully pin down the no-clobber contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/config/pathmanager_darwin_test.go` around lines 99 - 135, Update
TestDarwinConfigFilePath_KeepsExistingConfig to also assert the legacy config at
legacyPath remains present after pm.ConfigFilePath() is called. Use the existing
ConfigFilePath and migrateLegacyConfigFile behavior as the reference point, and
verify both that the current file content stays unchanged and the legacy file is
not removed or modified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/config/pathmanager.go`:
- Around line 365-378: The migrateLegacyConfigFile logic in basePathManager is
eagerly calling DataDir() just to look for an old config, which creates the
legacy directory on fresh installs. Update migrateLegacyConfigFile to avoid that
side effect by deriving the legacy config path without invoking DataDir() unless
a migration is actually needed, or by otherwise deferring the DataDir() call
until you have confirmed a legacy file exists.
- Around line 366-368: In the config migration logic around os.Stat(configPath),
make the os.Rename call tolerate a concurrent migration race: if the rename
fails with ENOENT, treat it as already migrated and continue instead of
returning an error. Update the migration path to explicitly check for that error
case, alongside the existing os.IsNotExist handling, so startup does not fail
when another process moves the legacy file first.

---

Nitpick comments:
In `@pkg/config/pathmanager_darwin_test.go`:
- Around line 99-135: Update TestDarwinConfigFilePath_KeepsExistingConfig to
also assert the legacy config at legacyPath remains present after
pm.ConfigFilePath() is called. Use the existing ConfigFilePath and
migrateLegacyConfigFile behavior as the reference point, and verify both that
the current file content stays unchanged and the legacy file is not removed or
modified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27980dd8-7e66-4c7a-bead-1005f87c40d2

📥 Commits

Reviewing files that changed from the base of the PR and between 9c72fb1 and 7d36b8c.

📒 Files selected for processing (3)
  • pkg/config/pathmanager.go
  • pkg/config/pathmanager_darwin_test.go
  • pkg/config/pathmanager_linux_test.go

Comment thread pkg/config/pathmanager.go
Comment on lines +365 to +378
func (b *basePathManager) migrateLegacyConfigFile(configPath string) error {
if _, err := os.Stat(configPath); err == nil || !os.IsNotExist(err) {
return err
}

dataDir, err := b.pm.DataDir()
if err != nil {
return err
}

legacyPath := filepath.Join(dataDir, ConfigFile)
if legacyPath == configPath {
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether ensureDir creates the directory as a side effect.
rg -n -A6 'func ensureDir' pkg/config

Repository: devsy-org/devsy

Length of output: 704


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant path manager implementations and migration flow.
sed -n '1,120p' pkg/config/pathmanager.go
printf '\n--- darwin ---\n'
sed -n '1,120p' pkg/config/pathmanager_darwin.go
printf '\n--- linux ---\n'
sed -n '1,120p' pkg/config/pathmanager_linux.go
printf '\n--- call sites ---\n'
rg -n 'migrateLegacyConfigFile|ConfigFilePath\(|DataDir\(' pkg/config

Repository: devsy-org/devsy

Length of output: 8540


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the migration function and adjacent code.
sed -n '340,410p' pkg/config/pathmanager.go

Repository: devsy-org/devsy

Length of output: 1616


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the migration function and adjacent logic.
sed -n '340,410p' pkg/config/pathmanager.go

printf '\n--- call sites ---\n'
rg -n 'migrateLegacyConfigFile|ConfigFilePath\(|DataDir\(' pkg/config

Repository: devsy-org/devsy

Length of output: 3556


Avoid creating the legacy data dir during config migration
migrateLegacyConfigFile calls DataDir() whenever the config file is missing, and the platform implementations create that directory with ensureDir/os.MkdirAll. On a fresh install, ConfigFilePath() still recreates ~/.devsy just to check for an old config file. Compute the legacy path without the side effect, or defer the DataDir() call until migration is actually needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/config/pathmanager.go` around lines 365 - 378, The
migrateLegacyConfigFile logic in basePathManager is eagerly calling DataDir()
just to look for an old config, which creates the legacy directory on fresh
installs. Update migrateLegacyConfigFile to avoid that side effect by deriving
the legacy config path without invoking DataDir() unless a migration is actually
needed, or by otherwise deferring the DataDir() call until you have confirmed a
legacy file exists.

Comment thread pkg/config/pathmanager.go
Comment on lines +366 to +368
if _, err := os.Stat(configPath); err == nil || !os.IsNotExist(err) {
return err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and inspect the relevant section with line numbers.
git ls-files pkg/config/pathmanager.go
wc -l pkg/config/pathmanager.go
sed -n '330,410p' pkg/config/pathmanager.go

# Find related uses of legacy/config paths and migration logic.
rg -n "legacyPath|configPath|os\.Rename|os\.Stat|ConfigFilePath|DataDir|ConfigDir" pkg/config/pathmanager.go

Repository: devsy-org/devsy

Length of output: 2748


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the path manager interface and platform implementations.
sed -n '1,220p' pkg/config/pathmanager.go

# Find concrete implementations of ConfigDir/DataDir.
rg -n "func .*ConfigDir|func .*DataDir|ConfigDir\(\)|DataDir\(\)" pkg/config -g '*.go'

# Inspect any platform-specific path manager files.
fd -a 'pathmanager*.go' pkg/config

Repository: devsy-org/devsy

Length of output: 8225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect platform-specific ConfigDir/DataDir implementations.
sed -n '1,120p' pkg/config/pathmanager_linux.go
sed -n '1,120p' pkg/config/pathmanager_darwin.go
sed -n '1,120p' pkg/config/pathmanager_windows.go

# Inspect any tests that pin the directory layout.
sed -n '1,180p' pkg/config/pathmanager_linux_test.go
sed -n '1,180p' pkg/config/pathmanager_darwin_test.go

Repository: devsy-org/devsy

Length of output: 11532


Handle the concurrent migration race
os.Rename can still return ENOENT if another process migrates the legacy file first; treat that as already migrated instead of failing startup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/config/pathmanager.go` around lines 366 - 368, In the config migration
logic around os.Stat(configPath), make the os.Rename call tolerate a concurrent
migration race: if the rename fails with ENOENT, treat it as already migrated
and continue instead of returning an error. Update the migration path to
explicitly check for that error case, alongside the existing os.IsNotExist
handling, so startup does not fail when another process moves the legacy file
first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant