Skip to content

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

Merged
bastiandoetsch merged 7 commits intomainfrom
refactor/IDE-1786_folder-config-refactoring
May 5, 2026
Merged

feat(config): centralized configuration [IDE-1786]#1162
bastiandoetsch merged 7 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

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

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

@bastiandoetsch

This comment was marked as outdated.

@bastiandoetsch

This comment was marked as outdated.

1 similar comment
@bastiandoetsch

This comment was marked as outdated.

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/review

@snyk-pr-review-bot

This comment has been minimized.

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/review

@snyk-pr-review-bot

This comment has been minimized.

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/review

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Breaking Change 🔴 [critical]

The refactor replaces the Settings struct with a map[string]*ConfigSetting for LSP configuration exchange (lines 693, 592). This is a breaking change for all existing IDE extensions. Existing extensions send simple key-value pairs (e.g., "endpoint": "url"), but the new protocol expects a nested object (e.g., "endpoint": {"value": "url", "changed": true}). Standard JSON unmarshaling will fail with a type mismatch error when attempting to parse a string into the ConfigSetting struct, effectively disabling configuration updates for older clients.

type DidChangeConfigurationParams struct {
	Settings      map[string]*ConfigSetting `json:"settings,omitempty"`
	FolderConfigs []LspFolderConfig         `json:"folderConfigs,omitempty"`
Race Condition 🟠 [major]

The temporarilyApplyNewOrgForValidation function (lines 939-973) mutates the global conf object by calling Set() on organization keys before reverting them in a defer. Since conf is a shared resource used by parallel scanning goroutines and other handlers, this temporary mutation is not thread-safe. A scan triggered for the same folder (or another folder sharing the same keys) during this validation window could read the temporary organization value, leading to incorrect configuration resolution or unauthorized API calls.

conf.Set(orgKey, &configresolver.LocalConfigField{Value: true, Changed: true})
conf.Set(prefKey, &configresolver.LocalConfigField{Value: newOrg, Changed: true})
Key Mismatch 🟠 [major]

The processConfigSettings logic (and helpers like applyToken at line 387) performs manual lookups in the settings map using internal flag name constants (e.g., types.SettingToken). However, the RegisterAllConfigurations definitions (see internal/types/register_configurations.go:292) indicate that internal names like SettingToken (likely "snyk_token") differ from the keys IDEs send via LSP (annotated as AnnotationIdeKey: {"token"}). Without a mapping layer or using the GAF configresolver to ingest the map, these lookups will fail to find settings sent by the IDE.

func applyToken(conf configuration.Configuration, settings map[string]*types.ConfigSetting) {
	if v, ok := settingStr(settings, types.SettingToken); ok && v != "" {
Potential Logic Error 🟡 [minor]

The applyMachineSetting logic for SettingApiEndpoint (lines 350-352) only applies a non-locked LDX-Sync value if the current configuration strictly matches config.DefaultSnykApiUrl. This prevents LDX-Sync from correcting semi-valid or legacy custom URLs (e.g., a URL missing a trailing slash or using an older subdomain) that the user might not have explicitly intended to pin as a custom override.

	return conf.GetString(configresolver.UserGlobalKey(types.SettingApiEndpoint)) == config.DefaultSnykApiUrl
}, func(v string) { config.UpdateApiEndpointsOnConfig(conf, v) }},
types.SettingCliPath:           {func() bool { return conf.GetString(configresolver.UserGlobalKey(types.SettingCliPath)) == "" }, func(v string) { conf.Set(configresolver.UserGlobalKey(types.SettingCliPath), v) }},

Comment thread .vscode/settings.json Outdated
Comment thread domain/ide/command/folder_handler.go Outdated
// Save the migrated folder config back to storage
if err := storedconfig.UpdateFolderConfig(gafConfig, folderConfig, &logger); err != nil {
logger.Err(err).Msg("unable to save folder config")
applyChanged := storedFolderConfig == 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.

if storedFolderConfig is nil it would panic in the line that is trying to clone

Comment thread domain/ide/command/folder_handler.go Outdated

// ReadFolderConfigSnapshot reads folder config values from configuration into a snapshot.
// An optional ConfigurationOptionsMetaData may be passed to populate UserOverrides for org-scoped flags.
func ReadFolderConfigSnapshot(conf configuration.Configuration, folderPath FilePath, fms ...workflow.ConfigurationOptionsMetaData) FolderConfigSnapshot {
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 should use configresolver.Resolve func

Comment thread internal/types/lsp.go Outdated
// config.displayName, config.description, and config.ideKey for framework integration.
func RegisterAllConfigurations(fs *pflag.FlagSet) {
// Machine-scope settings (14)
registerFlag(fs, SettingApiEndpoint, "", "API endpoint URL", map[string][]string{
Copy link
Copy Markdown
Collaborator

@ShawkyZ ShawkyZ Mar 11, 2026

Choose a reason for hiding this comment

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

TODO: check which flags are persisted and which aren't

Comment thread application/config/config.go Outdated
engineConfig.Set(configresolver.UserGlobalKey(types.SettingAuthenticationMethod), string(types.OAuthAuthentication))
engineConfig.Set(configresolver.UserGlobalKey(types.SettingToken), "")
engineConfig.Set(configresolver.UserGlobalKey(types.SettingCliPath), CliDefaultBinaryInstallPath())
if _, ok := engineConfig.Get(configresolver.UserGlobalKey(types.SettingBinarySearchPaths)).([]string); !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.

SettingBinarySearchPath doesn't have a flagset registration, is this intended ? when should a setting have a flagset registration and when it shouldn't?

Comment thread application/config/config.go
configresolver.AnnotationRemoteKey: {"publish_security_at_inception_rules"},
configresolver.AnnotationDisplayName: {"Publish Security At Inception Rules"},
configresolver.AnnotationDescription: {"Publish security rules at inception"},
configresolver.AnnotationIdeKey: {"publishSecurityAtInceptionRules"},
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.

replace this with SettingSecureAtInceptionExecutionFreq and delete SettingPublishSecurityAtInceptionRules

configresolver.AnnotationDescription: {"Publish security rules at inception"},
configresolver.AnnotationIdeKey: {"publishSecurityAtInceptionRules"},
})
registerFlag(fs, SettingTrustEnabled, "", "Enable trusted folders feature", map[string][]string{
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.

trust is internal config. let's leave it internal and not expose it now.

configresolver.AnnotationIdeKey: {"token"},
})
registerFlag(fs, SettingSendErrorReports, false, "Send error reports", map[string][]string{
configresolver.AnnotationScope: {"machine"},
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.

nitpick: reuse the scope constant from GAF

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.

also we don't have this setting

configresolver.AnnotationDescription: {"Enable sending error reports to Snyk"},
configresolver.AnnotationIdeKey: {"sendErrorReports"},
})
registerFlag(fs, SettingEnableSnykLearnCodeActions, false, "Enable Snyk Learn code actions", map[string][]string{
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 internal

configresolver.AnnotationDescription: {"Enable Snyk Learn code actions"},
configresolver.AnnotationIdeKey: {"enableSnykLearnCodeActions"},
})
registerFlag(fs, SettingEnableSnykOssQuickFixActions, false, "Enable Snyk OSS quick fix code actions", map[string][]string{
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.

internal

})
registerFlag(fs, SettingEnableSnykLearnCodeActions, false, "Enable Snyk Learn code actions", map[string][]string{
configresolver.AnnotationScope: {"machine"},
configresolver.AnnotationWriteOnly: {"true"},
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.

what is a WriteOnly annotation?

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/describe

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/review

@snyk-pr-review-bot
Copy link
Copy Markdown

Preparing review...

@bastiandoetsch bastiandoetsch force-pushed the refactor/IDE-1786_folder-config-refactoring branch from 9cf7693 to fc16c58 Compare March 23, 2026 08:59
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Description updated to latest commit (9cf7693)

@bastiandoetsch
Copy link
Copy Markdown
Collaborator Author

/describe

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Description updated to latest commit (77a7d2b)

Copy link
Copy Markdown
Contributor

@rrama rrama left a comment

Choose a reason for hiding this comment

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

Submitting my comments so far. I am still a long way off, but I want to at least feedback some of the bugs I spotted.

Concern:

  1. To me it was not immediately clear that there is special folder config paths for OrgSetByUser, PreferredOrg, and AutoDeterminedOrg that do not have wiring, but all other folder configs need it. I am wondering if we should switch to calling them something else to make it clearer. They could be FolderOrgSelectors or something (not so keen on calling them "native"), then if those functions are renamed to follow suit it would be clear if you are getting wiring for full or not. Then could do similar for the Git enrichments (not so keen on calling them "metadata").
  2. You have to know a lot about a key to get its value, it would be easier if all the information about a setting and how to get it's value was all in one place and you just use that everywhere. Currently it is spread between files like configuration.go, config.go, config_readers.go, ldx_sync_config.go and register_configurations.go, so there is a bit of jumping back and forth to figure out that some user global keys are overridden by LDX-Sync, etc., but feels weird I have to know it is a user global key in some of the places instead of that being stored with the key definition.

Testing improvements:

  1. Maybe an additional test to test a bit further / more multi-step than just Test_RefreshConfigFromLdxSync_PreservesNonLockedOverrides: A test which starts off with user set at folder risk score at 200, then a remote admin locks it to 300 and user fetches, then the remote admin unlocks and unsets the setting plus the user fetches, then verify that we get back the default of 0 and not the stale 200.

Personal things to still check:

  1. Do we need migration code?
  2. Look into the bug AI thought it found:

    Tracing ApplyLspUpdate (IDE → LS folder config writes)
    FolderConfig.ApplyLspUpdate (folder_config.go:255) → applyFolderScopeUpdates (:344):

    applyBasicFolderFields (:395): Handles SettingBaseBranch, SettingReferenceBranch, SettingLocalBranches, SettingAdditionalParameters, SettingAdditionalEnvironment, SettingReferenceFolder, SettingScanCommandConfig. All written to UserFolderKey as *LocalConfigField{Value, Changed:true} except SettingLocalBranches which goes to FolderMetadataKey.
    applyPreferredOrg (:488): Sets both SettingPreferredOrg and SettingOrgSetByUser together via UserFolderKey. Marks both as handled.
    applyOrgSetByUser (:520): Handles standalone SettingOrgSetByUser changes. Note: applyPreferredOrg already marks SettingOrgSetByUser as handled at line 490, but applyOrgSetByUser re-adds it at line 521 — this is fine since the handled map is only used by the generic PATCH loop.
    Generic PATCH loop (:373-391): For any remaining folder-scoped settings not in handled, applies Changed:true values to UserFolderKey, or Unsets on Value:nil.
    Issue: getSettingValue doesn't check Changed flag
    getSettingValue (folder_config.go:264-276) does not check cs.Changed. This means applyBasicFolderFields and applyPreferredOrg will process settings even when Changed: false. The generic PATCH loop at line 374 correctly checks !cs.Changed, but the explicit handlers don't. This is inconsistent with the documented PATCH semantics. In practice, it's mitigated by equality checks (e.g., baseBranch != cur), but a setting sent with Changed:false and a different value than current would still be applied.

Comment thread README.md
- Folder Config Notification
- method: `$/snyk.folderConfigs`
- params: `types.FolderConfigsParam`
- Configuration Notification (protocol v25+)
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: Don't state the protocol version, if someone wants historic analysis, they can get it from the Git history.

* limitations under the License.
*/

package types
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.

Why is this whole file in the types package?

// goroutine. SetEngineDefaults only sets defaults when the key is not yet present,
// so a pre-seeded configuration ensures the goroutine reads the test-specific paths
// rather than the system defaults (which in CI can include many unrelated Java installs).
conf := configuration.NewWithOpts(configuration.WithAutomaticEnv())
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.

Minor nitpick: Comment should also explain we use this technique to be more realistic, as in prod we have WithAutomaticEnv().

func initEngineForClientSettingsTest(t *testing.T) workflow.Engine {
t.Helper()
e, _ := InitEngine(nil)
e.GetConfiguration().Set(configresolver.UserGlobalKey(types.SettingBinarySearchPaths), []string{})
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.

Not meant to be keyed.

Comment thread application/config/config_test.go Outdated
func initEngineForConfigTest(t *testing.T) (workflow.Engine, *TokenServiceImpl) {
t.Helper()
engine, ts := InitEngine(nil)
engine.GetConfiguration().Set(configresolver.UserGlobalKey(types.SettingBinarySearchPaths), []string{})
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.

Same, not meant to be keyed.

modified := c.enableDeltaFindings != enabled
c.enableDeltaFindings = enabled
return modified
return getCustomEndpointUrlFromSnykApi(types.GetGlobalString(conf, types.SettingApiEndpoint), "deeproxy")
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: Again may be worth calling out in a comment that LDX-Sync will directly override the user global key for API endpoint.

// AuthenticationMethodMatchesCredentials returns true if the token matches the configured authentication method.
func AuthenticationMethodMatchesCredentials(token string, method types.AuthenticationMethod, logger *zerolog.Logger) bool {
if method == types.FakeAuthentication {
return true // We allow any value for the token in unit tests which use FakeAuthentication.
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: Leave the comment in.

if org != "" {
// Store the resolved org so that defaultFuncOrganization's UUID fast-path
// returns it directly next time, avoiding /rest/self.
conf.Set(configuration.ORGANIZATION, org)
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.

Not needed. After a resolved defaultFunc GAF will cache it.
Although standalone and tests do not have the cache enabled... See https://snyksec.atlassian.net/browse/IDE-1727
So a future enhancement to remove this then.

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.

It is needed, when the non-UUID org is given, as this is not cached. Was a fix to reduce API calls.

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.

I disagree, the GetWithError function in GAF's pkg/configuration/configuration.go will cache it after the default value function runs if you have caching enabled. I am going to refactor this out in my PR.

c.deviceId = deviceId
}
if engine == nil {
conf := configuration.NewWithOpts(configuration.WithAutomaticEnv())
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 enhancement reminder: We should enable the cache for standalone / tests: See https://snyksec.atlassian.net/browse/IDE-1727

}
if v, ok := settingBool(settings, types.SettingIssueViewIgnoredIssues); ok {
ivo.IgnoredIssues = v
}
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.

If only one is set then the other will be defaulted back to `false. TODO - Check severity filters too.

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.

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.

}

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

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.


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


return len(inlineValues) == 1 && inlineValues[0].Range.Start.Line == 17 && inlineValues[0].Range.End.Line == 17
}, time.Minute, 1*time.Second, "expected inline values to be served, but they were not")
}, time.Minute, time.Millisecond, "expected inline values to be served, but they were not")
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.

Why? This is an incredibly frequent poll which will just spam logs and slow things down surely. Looking at when you changed this (b6e3f0a) it seems like you did it randomly as part of a merge commit. I think this may be causing the panic I am seeing in CI: https://github.com/snyk/snyk-ls/actions/runs/24986913711/job/73162609575?pr=1162#logs

Comment thread application/server/notification.go Outdated
time.Sleep(300 * time.Millisecond)
l.Debug().Msg("waiting for lsp initialization to be finished...")
for !conf.GetBool(types.SettingIsLspInitialized) {
time.Sleep(time.Millisecond)
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.

I understand 300ms was too long, but why 1ms? Seems like a lot of CPU spinning. Why not 25ms?

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.

because with thousands of tests it adds up. local cpu spinning is ok (imo)

return globalOrg
fcConf := fc.Conf()
if fcConf == nil {
fcConf = conf
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.

I believe this path will always be hit, as the resolver and config is always nil from folderconfig.GetFolderConfigWithOptions

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 whole function's existence is silly and should be removed in favour of using FolderOrganizationFromConfig only.

for _, folder := range ws.Folders() {
folderPath := folder.Path()
snapshot := types.ReadFolderConfigSnapshot(conf, folderPath)
effectiveOrg := snapshot.AutoDeterminedOrg
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.

Should not be auto-org, should be the preferred org if org set by user.

nick-y-snyk and others added 4 commits April 30, 2026 13:15
#1240)

* fix: refresh LDX-Sync before populating SAST cache on login [IDE-1981]

When LDX-Sync resolves a different org after login than before, the
post-credential hook cached feature flags + SAST settings keyed by the
pre-login org. The next workspace scan reads the wrong cache entry and
reports "Snyk Code is not enabled for this organization".

- Reorder loginCommand post-credential hook: RefreshConfigFromLdxSync
  runs before populateFolderFeatureFlagsAndSastSettings so populate
  sees the post-login org.
- Rename populateAllFolderConfigs -> populateFolderFeatureFlagsAndSastSettings.
- Add info logs around startup populate flow + SAST cache hit/miss/failure
  for diagnosing similar issues.

* chore: remove intrusive debug logging from login flow [IDE-1981]

Strip verbose breadcrumb logs added during investigation of the SAST
cache race. Restore prior log messages and levels; keep the fix logic
(LDX-Sync refresh before populate) intact.

* docs: restore context.Background rationale comment [IDE-1981]

Per PR review — keep the explanation that context.Background() prevents
the LDX-Sync refresh from being canceled when the IDE cancels the
snyk.login LSP request.

* test: clear stale token before snyk.login in StartsAuthentication [IDE-1981]

After moving the login-time RefreshConfigFromLdxSync call into the
post-credential hook, this test failed because:
- During `initialized`, scanner Init runs an auth check that stores the
  fake provider's fixed token in config.
- snyk.login's Authenticate then returns the same token; updateCredentials
  early-returns on the no-op (oldToken == newToken), so the post-credential
  hook (where Refresh now lives) never fires.

Clearing the token after `initialized` simulates the realistic
"unauthenticated user clicks Login" precondition the test is meant to
exercise. No production change.
// which issues /rest/self synchronously. IsSet bypasses default-value functions,
// keeping this read network-free for hot paths like StateSnapshot. Auto-determination
// stays the responsibility of GetGlobalOrganization.
func (r *ConfigResolver) GlobalOrg() string {
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.

Having both this and types.GetGlobalOrganization() is confusing. Either we should stick to one OR rename this one to be explicitly a GlobalOrgNoNetwork or something.

nick-y-snyk and others added 3 commits May 1, 2026 16:44
Two related fixes for log-level handling that conspired to silently
downgrade test output to INFO regardless of what the user requested.

1. application/config/config.go (SetupLogging)
   The error branch for SNYK_LOG_LEVEL parsing was guarded by
   `if levelErr == nil`, so it printed "Can't set log level from flag.
   Setting to default (=info)" on parse SUCCESS. Cosmetic — level itself
   was applied — but the misleading stderr line hid the real issue and
   invalid env values were silently ignored. Flip to `if levelErr \!= nil`
   and warn only on actual parse failure.

   Bug introduced in fc92273 (HEAD-317, 2023-06-01).

2. application/server/server_smoke_test.go (ensureInitialized)
   The helper unconditionally forced INFO via:
       config.SetLogLevel(zerolog.LevelInfoValue)
       t.Setenv("SNYK_LOG_LEVEL", config.GetLogLevel())
   which clobbered any SNYK_LOG_LEVEL the developer had set, then
   replaced the engine logger with an INFO-level instance. Result: pre-
   init DBG lines visible, post-init silent at INFO.

   Only force INFO when env var not preset, so `SNYK_LOG_LEVEL=debug
   go test ...` actually surfaces debug logs end-to-end.
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