Skip to content

fix: persist and restore user folder config across restarts [IDE-1816]#1196

Draft
nick-y-snyk wants to merge 2 commits intorefactor/IDE-1786_folder-config-refactoringfrom
fix/IDE-1816-folder-config-deserialization
Draft

fix: persist and restore user folder config across restarts [IDE-1816]#1196
nick-y-snyk wants to merge 2 commits intorefactor/IDE-1786_folder-config-refactoringfrom
fix/IDE-1816-folder-config-deserialization

Conversation

@nick-y-snyk
Copy link
Copy Markdown
Contributor

Description

Fixes folder-level user settings (additional params, env, org, base branch, etc.) not surviving LS restarts after the folder config refactoring.

Root cause: After the refactoring moved folder config values from a monolithic StoredConfig JSON blob to per-key GAF prefix keys (user:folder:<path>:<name>), the values were correctly written to the JSON storage file on save, but never loaded back on startup. Additionally, after JSON round-trip, *configresolver.LocalConfigField deserializes as map[string]interface{} and []string becomes []interface{}, causing type assertion failures.

Changes:

  • RefreshByPrefix in storage — loads all keys matching a prefix from the JSON storage file into configuration on startup
  • SetupStorage in config — calls RefreshByPrefix for user:folder: prefix to restore persisted folder overrides
  • coerceToLocalConfigField — handles both in-memory *LocalConfigField and deserialized map[string]interface{} representations
  • ReadFolderConfigSnapshot — handles []interface{}[]string conversion for AdditionalParameters and LocalBranches
  • getSettingValue / getStringSliceFromSetting / getScanCommandConfigFromSetting — require Changed: true flag before applying updates
  • Tests — unit tests for coercion, round-trip integration tests for string/bool/slice persistence

Known issue: org_set_by_user / preferred_org still gets reset on restart despite being correctly persisted and loaded. Investigation ongoing — the LS should own the Changed determination rather than relying on 4 different IDEs to set it correctly.

Checklist

  • Tests added and all succeed
  • Regenerated mocks, etc. (make generate)
  • Linted (make lint-fix)
  • README.md updated, if user-facing
  • License file updated, if new 3rd-party dependency is introduced

User folder overrides (additional params, env, org, base branch, etc.)
were lost on restart because:
1. The user:folder: keys were never loaded from storage on startup
2. After JSON round-trip, values deserialized as map[string]interface{}
   instead of *configresolver.LocalConfigField, causing silent type
   assertion failures in all reader functions

Add coerceToLocalConfigField to handle both in-memory and deserialized
forms. Add RefreshByPrefix to load user:folder: keys from storage on
startup. Handle []interface{} to []string conversion for slice values.
getSettingValue, getStringSliceFromSetting, and getScanCommandConfigFromSetting
were not checking the Changed flag on ConfigSetting. This caused unchanged
settings sent by the IDE during initialization to overwrite persisted user
values (e.g. org_set_by_user being reset to false on restart).
@snyk-io
Copy link
Copy Markdown

snyk-io bot commented Apr 7, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment on lines +77 to +101
func (s *storage) RefreshByPrefix(config configuration.Configuration, prefix string) error {
s.mutex.Lock()

contents, err := os.ReadFile(s.storageFile)
if err != nil {
s.mutex.Unlock()
return err
}
doc := map[string]interface{}{}
err = json.Unmarshal(contents, &doc)
if err != nil {
s.mutex.Unlock()
return err
}

s.mutex.Unlock()
for key, value := range doc {
if strings.HasPrefix(key, prefix) {
config.PersistInStorage(key)
config.Set(key, value)
}
}
return nil
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

instead of doing this, use the already registered keys in

func RegisterAllConfigurations(fs *pflag.FlagSet) {

When you register a key you can mark it as persistable.

Comment on lines +66 to +67
lf, ok := coerceToLocalConfigField(val)
if !ok {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why can't we directly cast?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants