Skip to content

fix: races during extension activation#704

Merged
bastiandoetsch merged 8 commits intomainfrom
fix/race-condition-in-snyk-studio-enablement
Jan 14, 2026
Merged

fix: races during extension activation#704
bastiandoetsch merged 8 commits intomainfrom
fix/race-condition-in-snyk-studio-enablement

Conversation

@bastiandoetsch
Copy link
Copy Markdown
Contributor

@bastiandoetsch bastiandoetsch commented Jan 9, 2026

Description

Problem:
When multiple VS Code windows are opened and activated simultaneously, a race condition occurs in sendPluginInstalledEvent(). Multiple windows check global state concurrently, all see it as unset, and all proceed to show the "Secure at Inception" modal dialog, resulting in duplicate modals.

Solution:
Replaced the suboptimal random delay approach with a proper file-based locking mechanism using atomic file operations.

Changes:

  • Added FileLockService using Node.js fs.open() with O_CREAT | O_EXCL flags for atomic lock acquisition
  • Added globalStoragePath getter to ExtensionContext
  • Replaced checkGlobalStateSafely() (random delay) with withLock() pattern
  • Removed window.state.focused check (file lock handles synchronization)
  • Added 8 unit tests for FileLockService

How it works:

  1. First window acquires file lock → checks state (false) → sets state (true) → releases lock → shows modal
  2. Second window waits for lock → acquires lock → checks state (true, set by first window) → releases lock → exits without showing modal

Why file locking over random delay:

  • Deterministic behavior vs. probabilistic
  • Proper mutex semantics for cross-process synchronization
  • No external dependencies (uses Node.js built-in fs module)

Checklist

Screenshots / GIFs

N/A - No UI changes, internal synchronization fix.

when multiple windows were opened and activated at the same time, global state was retrieved simultaneously, causing unintended duplicate execution of business logic.
@bastiandoetsch bastiandoetsch requested review from a team as code owners January 9, 2026 06:16
@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Jan 9, 2026

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

Status Scanner 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.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @bastiandoetsch, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses and resolves critical race conditions that could arise during the Snyk extension's activation, particularly when multiple VS Code windows were initialized simultaneously. The changes prevent unintended duplicate executions of core business logic, such as analytics event logging and modal displays, by introducing a controlled and safe mechanism for accessing and updating global state. This significantly enhances the extension's stability and ensures a consistent user experience across different VS Code window scenarios.

Highlights

  • Race Condition Fix: Implemented a robust fix for race conditions that occurred during extension activation, specifically when multiple VS Code windows were opened and activated concurrently.
  • Safe Global State Access: Introduced a new utility function, checkGlobalStateSafely, to manage access to global state values. This function prevents duplicate execution of logic by checking the window's focus state and incorporating a random delay to mitigate race conditions.
  • Refactoring for Modularity: Refactored the sendPluginInstalledEvent method to utilize the new checkGlobalStateSafely function and extracted the 'Secure at Inception' modal configuration into its own dedicated configureSecureAtInception method, improving code organization and readability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request aims to fix a race condition during extension activation by checking for the focused window and adding a delay to state checks. While this is a step in the right direction, the use of a random delay is not a robust solution for preventing race conditions and can still lead to the issues it's trying to solve. I've identified a couple of areas for improvement to make the logic more resilient against race conditions, particularly around state updates for analytics events. My feedback includes specific suggestions to improve the atomicity of these operations.

Comment thread src/snyk/extension.ts Outdated
Comment thread src/snyk/extension.ts Outdated
@bastiandoetsch bastiandoetsch force-pushed the fix/race-condition-in-snyk-studio-enablement branch from e487815 to 2a9a90e Compare January 9, 2026 08:31
@bastiandoetsch bastiandoetsch enabled auto-merge (squash) January 9, 2026 08:32
Comment thread src/snyk/extension.ts Outdated
Comment thread src/snyk/extension.ts Outdated
Replace the suboptimal random delay approach with a proper file-based
locking mechanism to prevent race conditions when multiple VS Code
windows activate simultaneously.

Changes:
- Add FileLockService using atomic file operations (O_CREAT | O_EXCL)
- Add globalStoragePath getter to ExtensionContext
- Replace checkGlobalStateSafely() random delay with withLock()
- Remove window.state.focused check (file lock handles synchronization)
- Add 8 unit tests for FileLockService

The file lock ensures only one window can execute the plugin-installed
event logic at a time, preventing duplicate modal dialogs.
The no-await-in-loop warnings are intentional:
- FileLockService retry loop requires sequential await for lock acquisition
- Test teardown cleanup requires sequential file deletion
When lock file has empty or invalid JSON content (e.g., still being
written), fall back to checking file modification time (mtime) instead
of treating it as stale.

This prevents a race condition where:
1. Process A creates lock file and starts writing JSON
2. Process B reads empty/partial content, treats as stale
3. Process B deletes lock and creates its own
4. Both processes now think they have the lock

Now if JSON is invalid but mtime is recent, the lock is considered
valid. Only if mtime is older than threshold is it considered stale.
Instead of immediately falling back to mtime check when JSON is invalid,
retry reading the file content multiple times (5 attempts, 50ms apart).
This gives the writing process time to complete.

Only consider the lock stale if:
1. Valid JSON with old timestamp, OR
2. Invalid/empty content AND mtime is older than threshold

This better handles the race condition where we read while another
process is still writing the lock file content.
Move the globalState update outside of the analytics callback to ensure
it happens synchronously while holding the lock, preventing race
conditions where the callback runs after lock release.
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.

The solution is not the best file lock I have ever seen and has subtle bugs, but they are very unlikely to come up with our usage, those have all been protected against. So I will approve.

@bastiandoetsch bastiandoetsch merged commit aa82bbc into main Jan 14, 2026
12 checks passed
@bastiandoetsch bastiandoetsch deleted the fix/race-condition-in-snyk-studio-enablement branch January 14, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants