Skip to content

feat(config): centralized configuration [IDE-1786]#1162

Open
bastiandoetsch wants to merge 124 commits intomainfrom
refactor/IDE-1786_folder-config-refactoring
Open

feat(config): centralized configuration [IDE-1786]#1162
bastiandoetsch wants to merge 124 commits intomainfrom
refactor/IDE-1786_folder-config-refactoring

Conversation

@bastiandoetsch
Copy link
Copy Markdown
Collaborator

@bastiandoetsch bastiandoetsch commented Mar 3, 2026

User description

Summary

Phase 2 of the IDE-1786 configuration resolution refactor:

  • GAF flagset-native config resolution: Wire ConfigResolver to read settings from GAF prefix keys instead of struct fields. Inject ConfigResolver into context for scanner access.
  • Unified map-based configuration protocol: Replace Settings struct with map[string]*ConfigSetting for LSP configuration exchange. Add LspFolderConfig with per-folder settings maps.
  • SastSettings migration: Move SastSettings from FolderConfig struct field to GAF configuration with Get/SetSastSettings helpers.
  • Dynamic persistence: Replace hardcoded persistence arrays (persistedUserFolderSettings, persistedFolderMetadataSettings) and RegisterFolderPersistence() with SetFolderUserSetting / SetFolderMetadataSetting helpers that combine Set + PersistInStorage.
  • Migration code removal: Remove all legacy migration code (legacyFolderConfigJSON, MigrateLegacyFolderConfig, MigrateFolderConfigOrgSettings, OrgMigratedFromGlobalConfig, settings_migration.go).
  • CliSettings dissolution: Dissolve CliSettings struct into Config, promoting all fields and methods directly. Remove back-pointer pattern and separate mutex.
  • Precedence smoke tests: Add precedence_smoke_test.go and scanner_precedence_test.go for config resolution precedence validation.

Test plan

  • make lint — 0 issues
  • make test — all unit tests pass
  • SMOKE_TESTS=1 make test — smoke tests pass (except pre-existing Test_InvalidExpiredCredentialsSendMessageRequest flake)
  • INTEG_TESTS=1 make test — integration tests
  • Manual verification of folder config persistence round-trip

PR Type

Enhancement, Refactor


Description

  • Centralize configuration resolution using GAF components.
    • Update core services to use workflow engine and configuration resolver.
    • Remove legacy configuration resolution and migration code.

Diagram Walkthrough

    flowchart LR
      subgraph Previous System
        C["config.Config"]
      end
      subgraph New System
        GAF_Conf["configuration.Configuration (GAF)"]
        CR["ConfigResolver (types)"]
        LS["LS Core Services<br/>(codeaction, scanner, etc.)"]
        NewInterface["New Config Accessors"]
      end
      C -->|Data Loaded Into| GAF_Conf
      GAF_Conf -->|Resolution Logic| CR
      CR -->|Provides Unified Access| LS
      LS -->|Uses| NewInterface
      C -->|Removed| Deprecated
    ```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Additional files</strong></td><td><details><summary>101 files</summary><table>
<tr>
  <td><strong>mcp.json</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-19eb5f2245594c429cc37560d082721fbabff68227984d8d40bad80fd3514b83">[link]</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>coder.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b89595e8329168380c8d0d806f10fe48e0c74d6480bdd36d5f52fb50e60b38e7">+112/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>planner.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-3da7d9f10c1d522c26d34593af5559179e560e5f132e48d11925d54bc7e3ae8c">+95/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>qa.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4399ee622fcd198ee75d7ce0c8f8bf764146afab8534c4b73be17d1b0d2e83c4">+126/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>commit.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-8a7812887cf57f02817a41095c0bc96e39f12a86dcedc8b9c7717843fb9fecff">+217/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>create-implementation-plan.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c1589a6ef920a2e344d943924041069ef37a00f0c35b10b665b908aa4c3fe12e">+126/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>implementation.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b7db59ef37d8544cde3bd3cb4463d2753bbcd1ef3da3e4d743d6d469476ab938">+202/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>verification.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e63ba9b85e4f0149745953751025c71f5b9b3afd6dfa43b55ece2d3b0bfa56e7">+270/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>upload-to-s3.sh</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4ec24b2c614b7504e801da062ef7c0a0ea927a853af71949117f9be1416955bd">+0/-12</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>.goreleaser.yaml</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-7326b55c062b0f46fe9e39aace0a25f4515cf206040fb91a6fd2cae839f5e826">+1/-4</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>.tmp_ignore</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-3a301d08bc19d4e6a95a86d0f5f652932e70406c818acb7c1497c799dce6d2c7">[link]</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>launch.json</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-bd5430ee7c51dc892a67b3f2829d1f5b6d223f0fd48b82322cfd45baf9f5e945">+0/-23</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tasks.json</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-7d76d7533653c23b753fc7ce638cf64bdb5e419927d276af836d3a03fdf1745a">+0/-24</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>coder.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-54b2c28a8e7b934853b4ab315640e41e7d75915418749972b73660a73dd2b8b6">+71/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>planner.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-3de3f79204a2442206f81923eaf79f4784cd2a508dde3722261ae346b527b4ba">+70/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>qa.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4b3ee0d066f86a40697b5a63f015144677bddd2e4afc190c20f1e5d4316b1dbd">+117/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>general.mdc</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-56f646b00fd9a6c0573b57c0974764abd7f601a2deb90984147b1f66f7b4e6af">+112/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>SKILL.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-3eee1259a01cb01a6e29a2472eefcf3975d16e88e4c4841d7cb954dcedc57bdb">+260/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>SKILL.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-1e09e3863ee46b9fdc6f65d71c688175b12b8a2e7f4b0a994925ce4f52095352">+116/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>SKILL.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-773a18b37f68a8d4a8d73eb1a67eb4a70fd56c90d24f00295e36778a199e53b3">+241/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>SKILL.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-01767e331487ad3b20729c5e0747a8f44b4395e7ffdf7038b2fb8eb622a23e9b">+333/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>CLAUDE.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7">+114/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>Makefile</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52">+2/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>README.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5">+26/-22</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>helpers.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-939d219344d7e0ee2fabad9bd93cf499c569729779d098d7d7da97fefc181d15">+1/-15</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e141faafb3f0507956b85e92ac3b584c7c706bd96e7f428c7ef95d0d8b2df564">+752/-756</a></td>

</tr>

<tr>
  <td><strong>ldx_sync_smoke_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e86bba96f063a23a5407b87f1d43156fbf47ff435cf44fc7324550746bc13305">+134/-61</a></td>

</tr>

<tr>
  <td><strong>scan_notifier.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-1b61c83cfc491251abf25e52610132617ca8ea6b0c1d9347b948fde580eaae42">+2/-5</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>scan_notifier_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-5fdc92093964521489fec0d3eb3f9384994e19496fc0d274e0d6a3c3086a975d">+32/-24</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>notification_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-09b80ca28f0c9f8db60c941c24e92dfc65ded48c06249e70e2004faec30b9891">+26/-25</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>parallelization_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-dfb264976fa1c23451dc8d19b018920487af818f524aa24ea49211bd8b6326e9">+30/-24</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>precedence_smoke_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-1c743b9b799604d498e5a5290961ffc135dec375c44150ad820cba81f3926d39">+1077/-0</a></td>

</tr>

<tr>
  <td><strong>secrets_smoke_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c1510bd6a7151e3624d073a590787bd9af09d36b87a53a56067ffc0f3c8603fd">+24/-23</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>server_smoke_treeview_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-abf8d4beebabcdeea5250f1698dc768bfbd744c22da6c343b552b5ed02908147">+11/-10</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>server_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-165bad981a0cc202462a2fea7f236eab9ca3a72bd47fe8bae6b3adad801351fb">+272/-231</a></td>

</tr>

<tr>
  <td><strong>trust_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e429a057cdac11e281a6ee8603bf8a5ab6dfaf0daedaec873982c47b28d7d431">+51/-49</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>parser_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-56f8b9f576282024cef366a3d2024e23c393bc424bd1a7af8917142efcf913e0">+4/-4</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-17ed18489a956f326ec0fe4040850c5bc9261d4631fb42da4c52891d74a59180">+712/-0</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-effective-org.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-973fec236ae811f6a90a055f6b7d0da65dc4d11596911ce8ad89250223805426">+13/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-ide-to-ls.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-d96f7a3876e2a3103d4016e855aeed2eaf4e094231824ade00f1d15863ca2117">+29/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-ldx-sync-triggers.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-df333a4abf24cf3d87db395e0abe48a2faa721094e4c5c85350a3e539f7f5136">+39/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-locked-fields.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-38952676823280ad4b068d0c895c67c0793a9f12baa5fafbc4272e24b92e2188">+23/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-ls-to-ide.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e87e10816f9abd36bfa4484439e3525b9094a79520d362815a983bfedd8f9587">+35/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-precedence-folder.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-cf55942689f0076c1c0d5e590504f623b7d4a2163177bf6a9c38b5fe2abc61e9">+43/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-precedence-machine.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-1a8c5dae9ef9700a8d68771cf78fe1053bd3a2b0d08b7dc6392ae9bce67ddcb8">+27/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration-precedence-org.mmd</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-45c665222948a0e96307bcec7893b2c2eb30a1940831992d8bd16c015a7dca50">+31/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree-view.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-539534acc1cc1dac4c4790bdca3d442787d7e99ae04349bee000213f08300a28">+2/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>ui-rendering.md</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c5a817d72c0cce20d0ef1e30f346d1533d38bf440472d1a0944fb0ad0c82f98a">+3/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>clear_cache_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-f59e398f3890e4a5979f96e46822865c12c0a1c24a04488066e1110e28f217ee">+12/-9</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>code_fix_diffs_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-48255cf8a46398666fde50497322859b28c595de794ffd6670ece78b57ae7f75">+5/-7</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>code_fix_feedback_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-9ecc2e3d8ac8d287a04858a39ff2b791deda6c161b005159f1df9cd201561be2">+24/-22</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>code_fix_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b6e42c27f7465637e31a6b441bb21a325b705c2e602cc5208182658553a53e71">+28/-18</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>command_factory.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b7c8bb6d3fa17d1b86a8de859670a7d849bdcedceb8d9a27f4e7f1a74b5d340d">+35/-31</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>command_service.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-f91e9940ae7491a26a9708f682296ad692d4044fb82fad0d67811bc13f50fa0f">+12/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>command_service_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4e5012d752e62ca48321ab14a65a40b32718552b72d0510af51c9eccfb981154">+4/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>configuration_command_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4e6fcd9606a6e827d54cddeaf143ea61e51516a7e24ebcee13d0968284546bb0">+6/-5</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>connectivity_check.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-bd2f39aac9325739fa4bc8f0086a9dbeee25ae8965cfaaebb59ebaf24bfc5892">+6/-7</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>connectivity_check_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-d099e6ed42cac1be0e693600339935f0954b0ee310f46436ccb1fcc9b7264c46">+7/-7</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>directory_diagnostics.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c063651ad364c31d1c991853bd8e83c7c7dcde73b16f929e878721e11d1df8a5">+12/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>directory_diagnostics_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-cf8506645d2be31c6eb3d3f8ad413863197d6b1793829408240eced9782744cb">+17/-11</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>folder_handler.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-85f453996002ebf21ce07aa3db62fcd3d18a1861f517804721583decb421a90d">+103/-199</a></td>

</tr>

<tr>
  <td><strong>folder_handler_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-657ad7fa06e97a16b4e8eb7cddc64cd5e53343e6513ef874563e83ffd694416b">+181/-344</a></td>

</tr>

<tr>
  <td><strong>generate_issue_description.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-20aaf0cdc220b7a600d894877440f647f93c34bbc4e4f023e33422b4f2e77ccf">+17/-15</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>get_active_user_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c5b8060a5d409b868500ca069b1916a9a35584924e1e61be677148d12a44064d">+18/-16</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>get_feature_flag_status_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-29de3939a8441248dd44aa2f2eb09f23a57909cf126d85840d0a1519580e25f3">+11/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>get_tree_view_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-443abf9c8f12ff271e64a1eed61e5ddc426eca805d6d5be05cffab1a0e176166">+6/-6</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>ignores_integration_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-79ebbec5beb7c426863a36683370db366beb6d364890c7e7155c4a8936527277">+25/-31</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>ignores_request.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-06098845100f93443eaa531928a39a803862d1031ccea9ecccc83b8658a58b52">+61/-51</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>ignores_request_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-592f7e7bcfc9e8bde581d845d4ae40f18b2f5c70aeb9d9f9481c92439c3f0e74">+35/-46</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>ldx_sync_service.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-96079acff1e919961c5c93b80089075c62ed6b1f044fcf18fa0021dcf86fd30b">+149/-119</a></td>

</tr>

<tr>
  <td><strong>ldx_sync_service_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-aca1dc6a8fa97db48aa613e9fe3d8e075ff47071ab18848a1369c1217bf8863e">+438/-208</a></td>

</tr>

<tr>
  <td><strong>login.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-21b93267e7967e84d95e104e3b17f130b4b47b4a37ac176c5bba7dd4c35cee31">+10/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>logout.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-22e316f4a260c54802f276ff85692d92b3c1aba01d3dfd7d73f313c567fb5aab">+5/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>logout_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-e3d549e46654c52dd2506b8941fc83546f129917fb37045d8e1bb4c8eb4d1c91">+12/-8</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>ldx_sync_service_mock.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-3c7bf69a15757a5bbe63c8758c6596bee98f8b3d7d7b6a9664fc7502ee05f7ed">+6/-5</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>navigate_to_range_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-1cde128b3e8be4bc13c1ae238266409330a5bcef310bee386343660b932056b8">+12/-12</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>report_analytics.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-62743cd3ce4566b6111230201a62f4d1fea670f1fd60c593265bde16353d8585">+10/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>report_analytics_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-20363c3fc3625f9eba95434b9eac8946f2aad36f0ca47165d56a412a78da766a">+29/-29</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>sast_enabled.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-66bf16df3a0885d711b00264fad42f006b0346950036f8b48dc7ae6478dc6df4">+1/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>toggle_tree_filter_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-b68b006463ac72f1f5bd3786814cdcd0b103cac0245c1f3fdc0efbd0266c6b63">+25/-24</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>update_folder_config.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-a05ebb1468908293f4d3cfe250b1748079a0b150076d62ba0df69156131ea8c7">+28/-17</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>update_folder_config_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-ded99b4b398d892e849ca2c76051626e1fec77a62c3b6d777e532cf09653f049">+37/-33</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>workspace_folder_scan.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-6cb7a834c5afbe811a8145bd6d12892ca661ae68a983846d9f8077beabd6e757">+10/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>workspace_scan.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-7815afb61f4ee4d064ffc994582c7306f6ba37d1014c9db5d44ee109dbdc6682">+5/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>initializer.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-549679250b6998b23ae6754c4684adc9c107201a71db89c46e227fcc2b6c3a9d">+5/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree_builder.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-13a01423d69e8880eef4d1b357edd592e256151c20fe2e708922ec9f369d258e">+7/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree_html.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-47c0b3a66e860f79eb96752b6c2d226c640efecc95340807757164ca16cf1f85">+8/-7</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree_html_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-a05e59c0bf8dd60c31ffcd4d99f9c41b4e14113da3327539b810a53f4f99c00d">+56/-56</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>tree_node_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-914f0695e35f0166328ffb0e48acb6527945999894fce96f1292a00f29fabc64">+1/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree_scan_emitter.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-c86a1bb522364b4f14326b1a2915a724532c1d4f10d39ed3a51ca4a648715456">+13/-8</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>tree_scan_emitter_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-406a88e6d83f47e407cb1bd5a26bd0967e5866aa11529d659a54afd480d356f0">+23/-19</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>folder_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-bb03ee76aabde8be49355b245229584151a114e49ec791407738dcaeb631acaf">+138/-162</a></td>

</tr>

<tr>
  <td><strong>workspace_trust_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-ac8e52aa69f3b6fb4fd604078ddeef7df2cfe4814df32258a855a7e2254d9af1">+4/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>scan_state_aggregator_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-dacb10414b37097b40d2960a603ee1a3ec84ee95c6d096386bceb1c5aa893b42">+65/-51</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>summary_html_external_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-f9eb5dd6ed9a5462d93db22db8c10746d506824a6f2927b6ef59869a6f53b592">+15/-7</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>issues.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-4bdec45b5c8738e4bf742694b67c57c1102cfa09083bd638e325bfa650f8362a">+0/-3</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>git_persistence_provider_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-d11404eaca9a26e07e5b47c9a72b80378bfadb2ba02238363b7a445faf6ad955">+80/-79</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>base_scan.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-f37e6aa696f750af79d9ed24805074033bcea09bd38405ca384d14e4c63135f4">+43/-20</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>base_scan_test.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-29ac0a06174ae18093e1504044ee8b0ef1574ab063ca1a5c9210e28cba07b6ea">+47/-39</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>pre_scan_command.go</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-eb3aa1acbc37cb13f25f84053ef3e81f4f48f558536a7d5e99a3fe429e4878f8">+10/-4</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>Additional files not shown</strong></td>
  <td><a href="https://github.com/snyk/snyk-ls/pull/1162/changes#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td>

</tr>
</table></details></td></tr></tr></tbody></table>

</details>

___

Loading

… [IDE-1786]

Phase 2.2 of folder config refactoring: wire snyk-ls to use GAF
ConfigResolver with prefix-key-based configuration.

New files:
- RegisterAllConfigurations: 31 settings with pflag annotations
- MigrateSettingsToLocalFields: convert legacy Settings to LocalConfigField
- ConfigResolver GAF delegation tests (FC-046 through FC-058)

Key changes:
- ConfigResolver delegates to GAF ConfigResolver when wired
- SetGlobalSettings stores reference; SyncGlobalSettingsToConfiguration
  writes reconciled values after Config is updated
- FolderConfig dual-writes UserOverrides to GAF prefix keys
- LDX-Sync adapter dual-writes remote config to GAF prefix keys
- clearLockedOverridesFromFolderConfigs clears GAF UserFolderKey
- batchClearOrgScopedOverridesOnGlobalChange uses ModifyStoredConfig
  for atomic read-modify-write with mutex protection
- processSingleLspFolderConfig sets fc.conf for dual-write
- IsSnykSecretsEnabledForFolder delegates to isSettingEnabledForFolder
- Compile-time ConfigResolverInterface satisfaction check
- Remove enforced precedence from resolution (locked + regular only)
- cmp.Equal uses cmpopts.IgnoreUnexported for FolderConfig
Replace builder.WriteString(fmt.Sprintf(...)) with fmt.Fprintf(&builder, ...)
as suggested by golangci-lint.
@bastiandoetsch

This comment was marked as outdated.

@snyk-io
Copy link
Copy Markdown

snyk-io bot commented Mar 3, 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.

…nners [IDE-1786]

Phase 2.3 of folder config refactoring: inject ConfigResolver into scanner
context via enrichContextAndLogger, and migrate all four scanners (OSS, Code,
IaC, Secrets) to read ConfigResolver from context with fallback to struct field.

- Add DepConfigResolver constant and context helpers (ConfigResolverFromContext,
  NewContextWithConfigResolver) in internal/context
- Inject ConfigResolver into deps map in DelegatingConcurrentScanner
- Add getConfigResolver(ctx) helper to each scanner with context-first fallback
- Generate ConfigResolverInterface mock via gomock
- Tests FC-060 through FC-068 cover injection, retrieval, and fallback paths
…786]

Replace NullableField with *LocalConfigField in LSP wire protocol.
Unify $/snyk.folderConfigs into $/snyk.configuration using map-based
ConfigSetting for both global and per-folder settings.

Refactor ConfigResolver to delegate to GAF resolver for all settings
except folder metadata (AutoDeterminedOrg, LocalBranches) which are
read directly from FolderMetadataKey. Remove legacy fallback paths.

Update SyncToConfiguration to correctly write user-settable folder
settings to UserFolderKey and LS-enriched metadata to FolderMetadataKey.

Migrate scanners (base_scan, pre_scan_command, cli_scanner) to use
ConfigResolver for all configurable values instead of direct struct
field access.
…ve migration code [IDE-1786]

- Move SastSettings from FolderConfig struct to GAF configuration with
  Get/SetSastSettings helpers
- Replace hardcoded persistence lists with dynamic Set+PersistInStorage
  via SetFolderUserSetting and SetFolderMetadataSetting helpers
- Remove all legacy migration code: legacyFolderConfigJSON,
  legacyStoredConfig, MigrateLegacyFolderConfig,
  RegisterFolderPersistence, getStoredConfigForMigration,
  MigrateFolderConfigOrgSettings, OrgMigratedFromGlobalConfig flag,
  settings_migration.go
- Fix updateFolderOrgIfNeeded: check orgSettingsChanged before global
  org inheritance to prevent overwriting explicit user org changes
- Rename tests and comments to remove stale migration references
- Add precedence and scanner precedence smoke tests
- Add config resolver delegation tests
Move cliPath, Insecure, AdditionalOssParameters, and ReleaseChannel
fields from CliSettings struct directly into Config. Promote all
CliSettings methods (Installed, IsPathDefined, Path, SetPath,
DefaultBinaryInstallPath) to Config methods with Cli prefix.

Remove CliSettings struct, NewCliSettings constructor, CliSettings()
accessor, SetCliSettings() mutator, and the dedicated
cliPathAccessMutex (Config's existing mutex provides thread safety).

Update all ~80 call sites across 30 files from c.CliSettings().X()
to c.X() pattern.
@bastiandoetsch bastiandoetsch changed the title refactor(types): wire snyk-ls to GAF flagset-native config resolution [IDE-1786] refactor(config): Phase 2 - GAF configuration, dynamic persistence, struct cleanup [IDE-1786] Mar 5, 2026
@bastiandoetsch

This comment was marked as outdated.

Document the complete configuration architecture including:
- Configuration scopes (machine, org, folder) with prefix key storage
- Flag registration via pflag.FlagSet with annotations
- Precedence resolution rules per scope (locked > user > remote > default)
- Effective organization resolution flow
- LDX-Sync remote configuration with 4 sync triggers
- Locked field enforcement (sync-time clearing + edit-time rejection)
- IDE ↔ LS protocol with ConfigSetting wire type
- FolderConfig thin wrapper architecture
- Persistence via SetFolderUserSetting/SetFolderMetadataSetting helpers

Include mermaid sequence diagrams for all major flows:
- Machine and org scope precedence resolution
- Effective org determination
- LDX-Sync trigger lifecycle
- Locked field enforcement
- IDE→LS and LS→IDE configuration exchange
…n [IDE-1786]

Migrate boolean, string, and integer Config struct fields to use GAF
Configuration as the backing store via UserGlobalKey prefix keys. Config
getters/setters now delegate to gafGetBool/gafGetString/gafGetInt helpers,
removing the need for struct fields and mutex locking (GAF is thread-safe).

Key changes:
- Add gafConf/gafGetBool/gafSetBool/gafGetString/gafSetString/gafGetInt/gafSetInt
  helper methods on Config for type-safe GAF access
- Register flags via RegisterAllConfigurations in newConfig() so defaults
  are available immediately after engine init
- Migrate 16 boolean fields (product toggles, feature flags, proxy settings)
- Migrate 6 string fields (cliBaseDownloadURL, proxy*, authenticationMethod, etc.)
- Migrate 2 integer fields (hoverVerbosity, riskScoreThreshold)
- Skip Phase 1 raw writes in processConfigSettings for migrated settings
  (apply functions write validated values via setters)
- Update SetUpEngineMock to copy GAF settings when replacing engine
- Replace MockConfiguration with real configuration.NewWithOpts() in tests
  for simpler, more robust test setup
…ated settings [IDE-1786]

Now that boolean and int settings are backed by GAF UserGlobalKey,
the ConfigResolver's fallback to ConfigProvider methods is redundant.
GetValue already resolves through the full prefix key chain including
UserGlobalKey defaults.

Key changes:
- Shrink ConfigProvider interface to only FilterSeverity + IssueViewOptions
  (composite types still on Config struct)
- Replace isSettingEnabledForFolder(fallback) with GetBool delegation
- Remove r.c nil guards from methods that no longer access ConfigProvider
- Add DefaultConfigResolver test helper with proper prefix key wiring
- Register pflag flags in SetUpEngineMock so GAF ConfigResolver has scope
  metadata for resolution
- Update all defaultResolver functions in tests to use proper wiring
- Replace MockConfiguration with real configuration.NewWithOpts() in tests
@bastiandoetsch

This comment was marked as outdated.

…age ordering, clean up dead code [IDE-1786]

- Define 27 new Setting* constants for all internal configuration keys
  (device_id, offline, format, trusted_folders, etc.) in ldx_sync_config.go
- Replace all raw strings in UserGlobalKey("...") calls across 50+ files
  with the corresponding types.Setting* constants
- Fix PersistInStorage ordering: must be called BEFORE Set, not after,
  in all folder config write paths (5 files)
- Remove stale ConfigProvider mock expectations (FilterSeverity,
  IssueViewOptions) after interface was emptied
- Remove dead StoredConfig struct, FolderConfigsParam, and related
  serialization code from xdg.go
- Remove 125+ thin getter/setter wrapper methods from Config struct
- Extract 9 business logic methods into standalone package-level functions
- Increase test timeout from 45m to 60m for smoke test suite
@bastiandoetsch

This comment was marked as outdated.

…ckFileName, add engine/conf context helpers [IDE-1786]

Phase 3.5 completion:
- Inline SetConfigFile into main.go and ls_extension/main.go, remove method
- Extract SetOrganization to standalone function, update ~35 callers across 17 files
- Inline CLIDownloadLockFileName instance method, update 6 callers to use standalone

Phase 3.6.1:
- Add Engine and Configuration context helpers to internal/context
  (NewContextWithEngine/EngineFromContext, NewContextWithConfiguration/ConfigurationFromContext)
- Add DepEngine and DepConfiguration dependency keys
- Table-driven tests for all dependency-map context helpers (Workspace, Engine, Configuration)
…te NewConfigResolver signature [IDE-1786]

Remove all references to LDXSyncConfigCache from test files across the codebase.
Update all NewConfigResolver(cache, logger) calls to NewConfigResolver(logger).

Replace cache-based org storage with GAF-native equivalents:
- cache.SetOrgConfig -> WriteOrgConfigToConfiguration
- cache.SetFolderOrg -> SetAutoDeterminedOrg
- cache.GetOrgIdForFolder -> ReadFolderConfigSnapshot.AutoDeterminedOrg

Fix Test_processResults_ShouldCountSeverityByProduct: create folder after
SetUpEngineMock so f.conf and workspace storage use the same configuration.

Update Test_RefreshConfigFromLdxSync_EmptyFolderPath: document that empty
paths are skipped by ReadFolderConfigSnapshot (PathKey returns empty string
leading to early return).
@bastiandoetsch

This comment was marked as outdated.

… smoke tests [IDE-1786]

- Replace time.Sleep with require.Never in oss_test.go for
  Test_scheduleNewScanWithProductDisabled_NoScanRun and
  Test_scheduleNewScan_ContextCancelledAfterScanScheduled_NoScanRun
- Replace time.Sleep with require.Never in precedence_smoke_test.go for
  Test_SmokeScanPrecedence_AllDisabled_NoScansRun and
  Test_SmokeScanPrecedence_UserOverrideDisablesProduct
- Use TempDirWithRetry for storage directory in redirectConfigAndDataHome
  to prevent flaky TempDir cleanup failures when goroutines outlive tests
- Call c.SetStorage(s) in redirectConfigAndDataHome so c.Storage() is
  non-nil, fixing nil pointer panic in NewOAuthProvider during OAuth
  smoke tests (Test_InvalidExpiredCredentialsSendMessageRequest)
…DE-1786]

Phase 3.6.2: Update server.Start signature

- Change Start(c *config.Config) -> Start(engine workflow.Engine)
- Derive conf from engine.GetConfiguration() inside Start
- Derive logger from engine.GetLogger() after ConfigureLogging sets it
- Update jrpc2 ServerOptions logger closures to use engine.GetLogger()
- Use config.CurrentConfig().ConfigureLogging(srv) as interim bridge
  until Phase 3.6.7 extracts token scrubbing from Config
- Update main.go and ls_extension/main.go callers to pass c.Engine()
…ow.Engine [IDE-1786]

Replace *config.Config parameter with workflow.Engine across multiple packages
as part of Phase 3.6.6 of the config refactoring (remove LDXSyncConfigCache).

Changes include:
- infrastructure/code: migrate code_html.go, sarif_utils.go, ai_fix_handler.go,
  sast_local_engine.go, snyk_code_http_client.go, autofix.go, code.go
- infrastructure/oss: migrate oss_html.go (NewHtmlRenderer)
- infrastructure/secrets: migrate secrets_html.go (NewHtmlRenderer)
- domain/ide/command: replace config.CurrentConfig() with cmd.engine in
  code_fix_apply_edit.go, code_fix_diffs.go, code_fix_feedback.go,
  navigate_to_range.go, generate_issue_description.go and all other command files
- application/server/configuration.go: thread engine through updateFolderOrgIfNeeded
- Update all callers and test files to pass engine/configuration instead of *config.Config
…ndant calls [IDE-1786]

Introduce types.GetGlobalOrganization(conf) to enforce correct precedence
(UserGlobalKey first, then configuration.ORGANIZATION fallback), replacing
16 direct conf.GetString(configuration.ORGANIZATION) reads across 9 files.

Add types.SettingConfigFileLegacy constant to replace magic "configfile"
string in main.go, ls_extension/main.go, and config.go.

Eliminate redundant engine.GetConfiguration()/GetLogger() calls in ~27
locations by caching conf and logger at function entry and passing to
helpers.
Create types.TokenService interface and TokenServiceImpl to encapsulate
token lifecycle: writing to GAF configuration, adding scrub terms to the
logger, and notifying change listeners.

Update NewAuthenticationService and NewDelegatingScanner constructors to
accept TokenService as a dependency. Auth service uses tokenService.SetToken()
directly instead of config.SetToken(), and scanner uses
tokenService.TokenChangesChannel() instead of config.TokenChangesChannel().

Config.SetToken/TokenChangesChannel now delegate to the internal
TokenService; these will be removed when Config struct is deleted (3.6.8).
…alone functions [IDE-1786]

Create SetupLogging(engine, tokenService, server) as standalone
replacement for Config.ConfigureLogging(). Manage log file and scrubbing
writer as package-level state instead of Config fields.

Add types.IsDefaultEnvReady(conf) and types.WaitForDefaultEnv(ctx, conf)
backed by a channel stored in GAF configuration under
SettingDefaultEnvReadyChannel. Replace config.CurrentConfig().IsDefaultEnvReady()
call in configuration.go with the standalone function.

Add DisableFileLogging(conf, logger) as standalone replacement for
Config.DisableLoggingToFile(). Remove unused logFile field from Config.
Comment on lines +186 to +189
key := configresolver.FolderMetadataKey(dst, name)
conf.PersistInStorage(key)
conf.Set(key, v)
}
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 would we need to persist config for base scan ?

Comment thread internal/types/folder_config_helpers.go Outdated
for _, name := range userSettings {
if v := conf.Get(configresolver.UserFolderKey(src, name)); v != nil {
key := configresolver.UserFolderKey(dst, name)
conf.PersistInStorage(key)
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.

Persist is now being called in many places, it should be centralized, it can be done on init and maybe when a folder is trusted

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PersistInStorage needs to be called once per field per configuration (if a field should be written to storage). If it's not called, the field is ephemeral.

Comment on lines +170 to +174
userSettings := []string{
SettingBaseBranch, SettingReferenceBranch, SettingAdditionalParameters,
SettingAdditionalEnvironment, SettingReferenceFolder, SettingScanCommandConfig,
SettingPreferredOrg, SettingOrgSetByUser,
}
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.

we shouldn't do this, instead load config names via scope and iterate through them

Comment thread application/di/init.go
gafConfiguration := conf

fs := pflag.NewFlagSet("snyk-ls-config", pflag.ContinueOnError)
types.RegisterAllConfigurations(fs)
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.

this is also called when we create a new config so it's duplicated.

logger.Debug().Err(err).Str("method", "clientSettingsFromEnv").Msgf("couldn't parse oss config %s", oss)
}
c.SetSnykOssEnabled(parseBool)
conf.Set(configresolver.UserGlobalKey(types.SettingSnykOssEnabled), parseBool)
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.

The API to set a config is very confusing, when setting a configuration we must know the the scope which is pretty much error prone.
Instead we should simply just call Set and it will determine what the scope of this setting is based on what we already registered.

Comment thread .github/upload-to-s3.sh
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick: Random swap for no reason.

FormatMd = "md"
snykCodeTimeoutKey = "SNYK_CODE_TIMEOUT" // timeout as duration (number + unit), e.g. 10m
DefaultSnykApiUrl = "https://api.snyk.io"
DefaultSnykApiUrl = types.DefaultSnykApiUrl
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Future refactor: Just use the original const everywhere.

folderConfig, err := folderconfig.GetOrCreateFolderConfig(conf, path, logger)
if err != nil {
logger.Err(err).Msg("unable to get or create folder config")
folderConfig = &types.FolderConfig{FolderPath: path}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Now handled in GetFolderConfigWithOptions.
EDIT: Actually I will re-do a lot of it as the error paths just don't exist anymore.

}

// BatchUpdateFolderConfigs validates folder configs for batch update.
func BatchUpdateFolderConfigs(conf configuration.Configuration, folderConfigs []*types.FolderConfig, logger *zerolog.Logger) error {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function now does nothing, as ValidatePathForStorage is a literal no-op with no possible error path.


// RegisterAllConfigurations registers all snyk-ls configuration flags with their annotations
// into the given FlagSet. Flags are annotated with config.scope, config.remoteKey,
// config.displayName, config.description, and config.ideKey for framework integration.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

config.ideKey is no longer a thing.

})
registerFlag(fs, SettingRiskScoreThreshold, 0, "Risk score threshold (0-1000)", map[string][]string{
configresolver.AnnotationScope: {folderScope},
configresolver.AnnotationRemoteKey: {"risk_score_threshold"},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Big nitpick code smell: Should be using string(v20241015.RiskScoreThreshold) or even better create a wrapper in GAF so we aren't having to update all these when we change to a different version of the API.

bastiandoetsch and others added 15 commits April 13, 2026 11:43
…ions (#1183)

* fix(auth): use singleflight to deduplicate concurrent IsAuthenticated() calls

Concurrent callers of IsAuthenticated() with the same token each
independently called the auth provider and each sent a balloon
notification on transient failure (e.g. 400 from /oauth2/token),
resulting in duplicate popups in the IDE (observed 3x).

With singleflight, only one in-flight API call is made per concurrent
wave for the same token. All other callers share the result, so only
one balloon notification is sent regardless of how many goroutines
check simultaneously.

Adds CheckAuthDelay to FakeAuthenticationProvider to allow concurrency
testing, and a regression test that verifies exactly one notification
is sent for 3 concurrent concurrent callers on transient auth failure.

* fix(auth): address QA findings on singleflight dedup

- Extract token once at start of isAuthenticated() — eliminates three
  redundant config.GetToken(conf) calls (cache check, empty-token
  guard, singleflight key) in favour of a single read
- Make type assertion on singleflight result explicit with a panic on
  failure so a future type mismatch surfaces immediately as a
  programming error rather than silently returning unauthenticated
- Apply CheckAuthDelay in both success and failure branches of
  FakeAuthenticationProvider.GetCheckAuthenticationFunction so the
  delay works for both positive and negative test scenarios
- Add AuthCallCount (atomic int32) to FakeAuthenticationProvider to
  let tests assert how many times the check function was invoked
- Add TestIsAuthenticated_ConcurrentCallsOnlyInvokeProviderOnce to
  cover the success-path: three concurrent callers on a cache-miss
  must invoke the provider exactly once
- Add load-bearing comment explaining why the 50ms CheckAuthDelay is
  required for concurrency overlap in both concurrent tests

* fix(cli): use full CPU count for CLI concurrency on CI

On CI runners (2-4 vCPUs), the CLI semaphore was calculated as
max(1, NumCPU-4) = 1, serializing all CLI calls. With ~40 CLI
invocations across smoke tests (working dir + reference branch scans),
this caused the application/server package to exceed its 60m timeout
on Windows consistently.

When the CI env var is set, use NumCPU directly (no reservation).
The -4 reservation is kept for local development where CPUs should be
left for the IDE.

* fix(auth): replace singleflight with time-based notification dedup

singleflight.Do deadlocks when the OAuth refresherFunc in
auth_configuration.go calls IsAuthenticated() recursively with the
same token key — the inner Do waits for the outer Do which waits for
the refresh which waits for the inner Do.

Replace with a 30-second time-based dedup on lastAuthFailNotification.
The first caller within a 30s window sends the balloon notification;
subsequent callers within that window skip it. This avoids the
deadlock while still preventing duplicate popups from concurrent
IsAuthenticated() callers on transient auth failure.

Also removes the panic on type assertion failure (PR review feedback):
a crash in the LS is worse than returning unauthenticated.

* fix(test): return error instead of t.Fatalf in substituteDepGraphFlow callback

The depgraph callback in substituteDepGraphFlow runs in a background
scanner goroutine that can outlive the test. Since Go 1.24, calling
t.Fatal from such a goroutine panics the entire test binary:

  panic: Fail in goroutine after TestX has completed

Return the error through the workflow engine instead, which handles it
gracefully via the scan error path. This fixes the flaky macOS CI
failures in Test_SmokeWorkspaceScan.

* fix(auth): use atomic.Int64 for lastAuthFailNotification to prevent data race

lastAuthFailNotification (time.Time) was read and written in doAuthCheck
without synchronization. Since IsAuthenticated() holds m.RLock (allowing
concurrent readers), multiple goroutines could race on the struct field.

Replace with atomic.Int64 storing UnixNano, which is safe for concurrent
load/store without additional locking.

* fix(auth): reset notification cooldown on credential change

The 30-second lastAuthFailNotification cooldown was global — after a
transient auth failure, no new notification would be shown for 30s
even if the user changed their token or re-authenticated. This left
users unaware of the result of their corrective actions.

Reset the cooldown to zero in updateCredentials when the token
changes, so the next IsAuthenticated check sends immediate feedback.

* fix(auth): use CompareAndSwap for notification cooldown to prevent race

The Load+check+Store sequence on lastAuthFailNotification was not
atomic as a unit — multiple goroutines could all Load the stale value,
pass the 30s check, and all proceed to send a notification.

Use CompareAndSwap so exactly one goroutine wins per cooldown window.
If another goroutine updated the value between Load and CAS, the CAS
fails and that goroutine skips the notification.

* fix(cli): cap CI concurrency limit at 8 to prevent resource exhaustion

On high-core CI runners (32+ cores), spawning one CLI process per core
could exhaust memory since each Snyk CLI process is heavyweight. Cap
at 8 concurrent CLI processes on CI, which is sufficient for test
parallelism without risking OOM.

* fix(auth): make notification dedup message-aware

The 30-second cooldown was global to all error messages — switching
from a connectivity error to an invalid token error would be silenced
if it happened within 30s of the previous notification.

Replace the atomic timestamp with a mutex-protected struct that tracks
both the last message and its send time. Different error messages are
shown immediately; only identical messages are suppressed for 30s.
This ensures the user always sees feedback when the error cause
changes (e.g., after re-authenticating or switching configurations).

* fix(test): skip substituteDepGraphFlow tests on Windows and macOS

The depgraph CLI subprocess is unreliable on non-Linux CI runners:
- macOS: OOM-killed (SIGKILL) by Jetsam on 7GB runners
- Windows: exits with status 1 (path/environment issues)

Add NotOnMacOS helper alongside the existing NotOnWindows, and call
both inside substituteDepGraphFlow so every test that uses it is
automatically skipped on those platforms. Ubuntu is unaffected.

* chore: add CLAUDE.md

* fix(test): skip TestUnifiedTestApiSmokeTest on macOS and Windows

The test uses substituteDepGraphFlow which is OOM-killed on macOS CI
runners (7GB RAM) and exits with status 1 on Windows. When subtests 1
and 2 are skipped via substituteDepGraphFlow, subtest 3 fails with
"No diagnostics to compare" because no diagnostics were collected.

Add NotOnMacOS and NotOnWindows guards at the top of the parent test so
the entire test is skipped on those platforms, consistent with the
existing approach in substituteDepGraphFlow.

* chore: remove substitute depgraph on non-necessary smoke tests

* chore: update osflows

* fix(test): pin risk-score feature flags in precedence smoke tests

Add Override(flag, value) to featureflag.Service so callers can pin
specific flags to false after di.Init, before the LDX-sync scan starts.

The precedence smoke tests were failing on CI because the test org had
useExperimentalRiskScoreInCLI=true returned by the API, which switched
the OSS scanner to the ostest path. That path fails with "failed to get
dependency graph" since the test environment does not provide the
required dep-graph infrastructure.

Override is applied on top of API-fetched values inside
PopulateFolderConfig, ensuring it always wins regardless of API response.

* fix(auth): coalesce concurrent IsAuthenticated() API calls via singleflight

Three or more concurrent IsAuthenticated() calls (each holding m.RLock)
all raced to call the auth provider independently. This caused:
  - Redundant API round-trips on every burst of concurrent callers
  - The existing notification dedup only suppressed the balloon; the
    underlying API calls still multiplied

authCheckGroup.Do(token, ...) now ensures that when multiple goroutines
reach doAuthCheck with the same token, only one API call is in-flight at
a time. All waiters share that single result.

Add an AuthCallCount assertion to the concurrent test to verify that
exactly one API call is made, not one per caller.

* perf(logging): reduce lspWriter channel capacity and eliminate bootstrap allocation

lspWriter.writeChan was sized at 1,000,000 elements, pre-allocating ~8MB
per New() call. With 2-3 New() calls per test setup (InitEngine +
SetupLogging), the smoke test suite was allocating 228MB (26% of total)
in logging.New alone.

- Reduce writeChan capacity from 1_000_000 to 10_000. An LSP session
  does not need to buffer 1M log messages in-flight; 10K is sufficient
  for any realistic burst.

- Replace logging.New(nil) in InitEngine with os.Stderr directly.
  InitEngine creates a temporary bootstrap logger before an LSP server
  exists. Using lspWriter here spins up a goroutine and allocates the
  channel for a writer that SetupLogging immediately discards. Using
  os.Stderr avoids the wasted allocation entirely.

These two changes together cut logging-related heap allocation from
~228MB (cumulative alloc_space) to ~1.4MB during the unified smoke test.

* ci: upgrade macOS integration-test runner to macos-latest-large

The 7GB macos-latest runner was OOM-killing the integration test job.
Switching to macos-latest-large (14GB RAM) gives sufficient headroom
for the depgraph CLI subprocess and the Go test process to coexist.

* fix(auth): trigger logout on oauth2 invalid_grant wrapped in url.Error

When an OAuth2 refresh token is invalid or expired, the token endpoint
returns 400 invalid_grant. The SDK wraps this in a url.Error chain:

  Get /rest/self -> Post /oauth2/token -> "authentication failed"

shouldCauseLogout was returning false because the outer url.Error check
fired first, before any string matching. This caused the server to treat
the permanent credential failure as transient, retrying the token refresh
every ~600ms indefinitely. The config dialog timed out (10s) waiting for
the HTML response, leaving the user unable to log out or reset the token.

Fix: check for "authentication failed" before the transient-network guards.
"authentication failed" only appears when the OAuth server explicitly
rejects credentials — it cannot come from DNS/TCP/TLS failures.

Also extract isTransientNetworkError() to keep shouldCauseLogout below
the gocyclo limit.

* chore: update .gitignore

* fix(secrets): return nil instead of error when secrets feature flag is disabled

When the SnykSecretsEnabled feature flag is not set, the scanner was returning
an error ("feature flag not found") and logging at Error level. This caused a
spurious balloon notification in the IDE ("feature flag not found") whenever
secrets scanning is disabled — which is the normal state for users without
the feature flag enabled.

Change the behaviour to silently skip the scan (return empty issues, nil error)
and log at Debug level, consistent with how other early-exit conditions are
handled (e.g. product disabled, no token).

* chore: update hyphens in build.yml

* fix(logging,test): drop log messages when LSP write channel is full and fix smoke test flake

lsp_logger: make WriteLevel non-blocking by using a select/default on the
write channel. When the channel is full (e.g. under high-volume Trace logging
or a slow client), the message is written to stderr instead of blocking the
caller. This prevents the main LS goroutine from freezing the IDE extension.

server_smoke_test: distinguish transient RPC errors from correctness
violations in verifyQuickFixForIssue. Previously any network/unmarshal error
caused the entire 138-issue loop to restart, making it race against the
2-minute assert.Eventually timeout before reaching line 25 (errorhandler).
Now transient errors are skipped (soft no-op) while actual rule violations
(wrong title format, >1 upgrade action) still fail the check immediately.

* test: reduce maxIntegTestDuration from 45m to 15m

* fix: macos CI

* fix: don't log in debug mode

* chore: revert to small macos runner

* fix(test): disable binary search path walk in integ/smoke tests

On main, all integration/smoke test helpers called
WithBinarySearchPaths([]string{}) on the Config struct, preventing the
env-defaults goroutine from walking directories like C:\Program Files
when searching for mvn.exe or java.exe.

After the IDE-1786 Config-struct removal, this safeguard was lost.
prepareTestHelper (used by SmokeTestWithEngine and IntegTestWithEngine)
now pre-seeds an empty SettingBinarySearchPaths into the GAF
configuration before passing it to InitEngine, so the goroutine that
calls DetermineJavaHome/MavenDefaults never performs an exhaustive
directory walk.

On Windows CI this walk took ~10 minutes per test setup (hundreds of
thousands of files under C:\Program Files), causing the application/server
package to hit the 60-minute go test timeout. This restores the behavior
from main and brings Windows CI back in line.

* chore: adjust num cpus

* fix: add context to ScanProgress.Listen to prevent goroutine leaks

ScanProgress.Listen previously blocked indefinitely on its select
waiting for cancel or done signals. If a scan ended without calling
SetDone or CancelScan (e.g. due to an error early return), the listener
goroutine would leak and keep the WaitGroup in DelegatingConcurrentScanner
from completing, causing subsequent tests or scans to hang until timeout.

Adding ctx.Done() as a third select case ensures the goroutine always
exits when the scan's context is canceled, which happens at the latest
when the scan function returns via defer cancel().

* fix(lint): remove unnecessary int conversion in calcConcurrencyLimit

runtime.NumCPU() already returns int, the conversion was redundant.

* fix(test): initialize workflows on pre-configured engine in prepareTestHelper

prepareTestHelper passes a pre-configured engine to InitEngine to avoid
binary search path walks in CI. However, InitEngine only calls
InitWorkflows and engine.Init when engine==nil, so integration and smoke
tests were running without registered workflows. This caused:
- Test_connectivityCheckCommand_Execute_integration to fail because the
  connectivity check workflow was never registered
- Test_textDocumentInlineValues_InlineValues_IntegTest to fail because
  the CLI installer workflow was unavailable
- Test_SmokeInstanceTest to fail for the same reason

Also set EXECUTION_MODE_KEY and PersistInStorage on preConf to match
what InitEngine does for nil engines.

* fix(oss): defer SetDone() to mark scan progress done on all return paths

Previously, ScanProgress.SetDone() was only called on the happy path in
scanInternal, leaving isDone=false when legacyScan or ostestScan returned
an error. The next scan on the same folder would call CancelScan() on the
stale ScanProgress, hitting the 5-second channel timeout.

Convert the explicit SetDone() call to a deferred function, matching the
IAC scanner's existing pattern. SetDone() is safe to call repeatedly or
after cancellation, so deferring it guarantees cleanup on all exit paths.

Also adds a gomock-generated Executor mock and a regression test
(Test_ScanError_ScanProgressIsMarkedDone) that verifies IsDone()==true
after a scan error.

* fix(scans): buffer done channel to prevent SetDone() blocking after ctx.Done()

When Listen exits via ctx.Done(), the unbuffered done channel has no
active reader. Any subsequent call to SetDone() (e.g. from a deferred
cleanup in scanInternal or iac.Scan) would block for the full 5-second
timeout before returning, effectively hanging the scan goroutine on
every context-canceled completion.

Changing done to a buffered channel (capacity 1) ensures SetDone() can
always complete the send immediately: if Listen is still active it reads
the value normally; if Listen has already exited the value sits harmlessly
in the buffer until the ScanProgress is garbage-collected.

Adds TestScanProgress_SetDone_DoesNotBlockAfterContextCanceled to
reproduce the hang and confirm the fix, plus additional unit tests for
the normal SetDone, CancelScan, and idempotent SetDone paths.

* chore: update test timeout to 90m
* ci: run windows profiling in build workflow

Add a dedicated windows-test-profile job to the Build workflow so profiling runs in parallel with integration-tests.

The job splits JS and Go test phases, captures go test JSON output, generates slowest package/test and failure reports, uploads artifacts, and fails on non-zero phase exit codes.

Remove the standalone windows-test-profile workflow file.

* chore: add profiling workflow

* chore: use windows-latest

* fix: set search paths correctly in test setup

* chore: better test setup

* chore: use testhelper test setup where possible

* chore: remove profiling job
chore: update go to 1.26.2 [IDE-1935] (#1206)

* chore: update go to 1.26.2

* chore: update golangci-lint to 2.9.0

* chore: update contributing.md with new go version
…1189)

fix: pass trusted folders through didChangeConfiguration [IDE-1786]

The IDE-1786 refactoring moved TrustedFolders into InitializationOptions
as init-only metadata, but on main it was updatable at runtime via
writeSettings/updateTrustedFolders. This caused trusted folder changes
sent via workspace/didChangeConfiguration to be silently dropped.

Add TrustedFolders field to LspConfigurationParam (matching
InitializationOptions) and wire it through handlePushModel and
handlePullModel to applyTrustedFolders. Also add js-tests/fixtures/*.html
to .gitignore as generated test artifacts.

Co-authored-by: Abdelrahman Shawki Hassan <shawki.hassan@snyk.io>
#1196)

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

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.

* fix: require Changed flag in folder config setting readers [IDE-1816]

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).

* fix: use GAF Refresh for folder config keys, replace Changed gate with value comparison [IDE-1816]

Replace RefreshByPrefix with KeysByPrefix + per-key Refresh calls in
SetupStorage, following the same pattern as other storage refreshes.

Remove Changed flag requirement from IDE setting readers
(getSettingValue, getStringSliceFromSetting, getScanCommandConfigFromSetting,
applyFolderScopeUpdates). Instead, use reflect.DeepEqual to skip
no-op updates, preventing IDE defaults from overwriting persisted values
without requiring the IDE to set Changed: true.

Extract per-field apply helpers (applyBaseBranch, applyLocalBranches,
applyStringField, applyStringSliceField, applyScanCommandConfig) for
clarity and consistent value-comparison semantics.

* fix: reduce cyclomatic complexity of ReadFolderConfigSnapshot [IDE-1816]

Extract readScanCommandConfig and readUserOverrides helpers from
ReadFolderConfigSnapshot, and reuse existing getStoredStringSlice and
getStringSliceFromUserConfig for slice fields to bring complexity
from 19 down to within the gocyclo threshold.

* fix: sync proxy_insecure and cli_insecure bidirectionally [IDE-1816]

proxy_insecure (IDE/LDX-Sync contract) and cli_insecure (internal CLI
flag) were not kept in sync, causing the client to receive two different
values for the same concept.

- applyCliConfig now writes to both proxy_insecure and cli_insecure
  when the client sends proxy_insecure
- LDX-Sync boolSettingDefs setter propagates proxy_insecure to
  cli_insecure so CLI picks up remote values
- cli_insecure marked writeOnly so it is no longer sent to the client;
  only proxy_insecure is part of the IDE contract
- processFolderConfigs: only send update notification when configs
  actually changed

* fix: remove redundant cli_insecure setting, use proxy_insecure everywhere [IDE-1816]

cli_insecure was a redundant mirror of proxy_insecure that had no
RemoteMachineKey from LDX-Sync. The HTML settings page was reading
cli_insecure instead of proxy_insecure, so the resolver never saw
the locked remote value and returned the user override instead.

- Remove SettingCliInsecure constant and flag registration
- Update CLI and CLI auth provider to read SettingProxyInsecure
- Update HTML settings construction to read SettingProxyInsecure
- Remove sync/mirror logic from applyCliConfig and boolSettingDefs

* fix: rename config dialog form fields from camelCase to snake_case [IDE-1816]

Align HTML form field names and IDs with backend GAF config key naming
convention. Updates config.html template, settings-fallback.html, all
JS modules (validation, authentication, folders, form-handler, etc.),
Go smoke/unit tests, and JS test fixtures.

Also flattens issueViewOptions into individual fields and changes
scanningMode values from "auto"/"manual" to "true"/"false".

* fix(auth): always send IDE token to updateCredentials

* fix: token check

* fix: precedence tests

---------

Co-authored-by: Abdelrahman Shawki Hassan <shawki.hassan@snyk.io>
These are user-specific Claude Code configuration files (agents, commands,
settings). Each developer maintains their own locally. Tracking them caused
skip-worktree hacks and checkout conflicts across branches.
…#1210)

* fix: stop persisting delta base scan folder configs to disk [IDE-1905]

CopyFolderConfigValues() was calling PersistInStorage() for every key
copied to the destination folder. When the destination is a temp
directory (delta/net-new base branch scan clone), all those keys get
written to the on-disk config file and never cleaned up.

Remove PersistInStorage() calls from CopyFolderConfigValues() — copied
values remain in-memory for scan execution via conf.Set() but no longer
pollute the storage file with hundreds of temp folder entries.

* feat: migrate legacy folder config from JSON blob to individual GAF keys [IDE-1905]

Old format stored all folder configs as a single JSON string under the
INTERNAL_LS_CONFIG GAF key. New format uses individual prefix keys per
field per folder (user:folder:<path>:<setting>).

Without migration, users upgrading lose preferredOrg, orgSetByUser,
additionalParameters, baseBranch, and other folder settings — causing
scans to silently run against wrong org or with missing CLI args.

Migration runs once in SetupStorage() when INTERNAL_LS_CONFIG has
content, writes each folder's fields as individual GAF keys using
existing helpers, then clears the old key to prevent re-migration.

Migrated fields: baseBranch, additionalParameters, additionalEnv,
referenceFolderPath, preferredOrg, orgSetByUser, autoDeterminedOrg.

* fix: lint

* fix: use Unset for legacy config cleanup, downgrade log level, fix Windows test compat

Use conf.Unset instead of conf.Set("") to properly remove the legacy
config key. Downgrade migration error log from Error to Debug since
migration runs only once. Normalize fixture paths in tests using PathKey
so assertions pass on Windows where filepath.Clean converts to backslashes.
- README: initializationOptions and didChangeConfiguration use ConfigSetting maps and pflag keys; Secrets product; login args; Go version pointer; scan product values.
- configuration-dialog.md: actual JS modules, __ideExecuteCommand__ bridge, save JSON shape, checklists and mermaid.
- configuration.md: fix diagram cross-reference.
- tree-view/ui-rendering/manual_testing: Secrets product and links.
- CONTRIBUTING.md: link to configuration architecture doc.

Snyk MCP scans were not run (CLI auth required in this environment).
* fix: populate feature flags before auth notification [IDE-1901]

Race condition: after login, IDE receives $/snyk.hasAuthenticated and
triggers scan before SAST settings are cached → "Snyk Code not enabled".

Fix: postCredentialUpdateHook runs populateAllFolderConfigs between
credential storage and auth notification, ensuring feature flags and
SAST settings are available before the IDE reacts.

Review feedback addressed:
- Use unenriched folder config (no git enrichment for feature flag fetch)
- Loop only on trusted folders
- Remove unnecessary Clone()
- Set ConfigResolver on folder config so SetFeatureFlag persists to GAF
- Make sendFolderConfigs synchronous in login command
- Case-insensitive error string matching in shouldCauseLogout
- Extract isTransientNetworkError to reduce cyclomatic complexity

Includes regression test verifying feature flags populate before auth
notification is sent (fails when hook is removed).

* fix: harden postCredentialUpdateHook safety [IDE-1901]

- Synchronize SetPostCredentialUpdateHook with a.m mutex to prevent data race
- Skip hook when token is empty (logout path) to avoid wasteful API calls
- Wrap hook invocation in recover() to prevent LSP server crash on panic

* fix: populate feature flags for all workspace folders, not just trusted [IDE-1901]

Reverts populateAllFolderConfigs to iterate ws.Folders() instead of
ws.GetFolderTrust(). Fixes regression where newly trusted folders never
had feature flags populated, leading to "Snyk Code is not enabled" errors.

* fix: make sendFolderConfigs async in login to avoid UI blocking [IDE-1901]

postCredentialUpdateHook already populates feature flags synchronously
before auth notification, so sendFolderConfigs doesn't need to block.

* fix: move trusted folders from top-level field to settings map [IDE-1901]

Trusted folders were sent as a top-level field on LspConfigurationParam
and InitializationOptions, bypassing the settings map processing
pipeline. This caused a mismatch with IDEs that send it inside the
settings map (e.g. VSCode pull model), resulting in trusted folder
updates silently not reaching the LS after init.

Move handling into processConfigSettings via applyTrustedFoldersFromSettings,
using snake_case key (trusted_folders) consistent with other settings.
Remove top-level TrustedFolders field from both Go structs.
chore: bump gaf to 359a47b
…leans [IDE-1816] (#1179)

* feat: ldx-sunc provides product enablement and severity filter as booleans

* chore: merge upstream code into dev branch

* fix: smoke tests were not using the correct ldx-sync fields

* fix: smoke tests were not using the correct ldx-sync fields

* fix: align AnnotationRemoteKey values with LDX-Sync API spec [IDE-1902]

The AnnotationRemoteKey annotations in register_configurations.go used
incorrect keys that didn't match the actual LDX-Sync API field names.
This caused the GAF ConfigResolver to fail mapping remote config keys.

Changes:
- severities -> removed (severities now handled via severityAPIKeys)
- cwe -> cwe_ids
- cve -> cve_ids
- rule -> rule_ids
- automatic -> scan_automatic
- net_new -> scan_net_new
- open_issues -> issue_view_open_issues
- ignored_issues -> issue_view_ignored_issues
- Added AnnotationRemoteKey for product settings (product_code_enabled, etc.)

* refactor: split severity filters out into individual fields

* fix: update test cound of settings now that severity filters are individual

* fix: remove duplicated severity filter and issue view logic

* fix: locked settings were not getting set correctly

* refactor: revert changes to combined severityfilter object

* refactor: remove unused container code, remove duplicate test setup logic

* refactor: remove product container

* fix: process severity filter updates individually to preserve unchanged values

* fix: restore test

* fix: update HTML template to use new fields, add extra test coverage

---------

Co-authored-by: Nick Yasnohorodskyi <nikita.yasnohorodskyi@snyk.io>
#1219)

Revert "fix: handle case where values sent from ide are unchanged but non-nil (#1217)"

This reverts commit de974b3.
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.

7 participants