Skip to content

Conversation

@Shironex
Copy link
Collaborator

@Shironex Shironex commented Dec 25, 2025

  • Added concurrency limiting using p-limit to prevent ENFILE/EMFILE errors.
  • Introduced retry logic with exponential backoff for transient file descriptor errors.
  • Enhanced secure-fs with new functions for configuring throttling and monitoring active/pending operations.
  • Added unit tests for throttling and retry logic to ensure reliability.

Resolve

Summary by CodeRabbit

  • New Features

    • Added concurrency throttling for file system operations with configurable limits to prevent resource exhaustion.
    • Implemented automatic retry logic for file descriptor errors with exponential backoff.
    • Added APIs to monitor and configure throttling behavior: configureThrottling(), getThrottlingConfig(), getPendingOperations(), getActiveOperations().
    • Added path utility functions: joinPath() and resolvePath().
  • Chores

    • Added p-limit dependency for concurrency management.

✏️ Tip: You can customize this high-level summary in your review settings.

- Added concurrency limiting using p-limit to prevent ENFILE/EMFILE errors.
- Introduced retry logic with exponential backoff for transient file descriptor errors.
- Enhanced secure-fs with new functions for configuring throttling and monitoring active/pending operations.
- Added unit tests for throttling and retry logic to ensure reliability.
@Shironex Shironex self-assigned this Dec 25, 2025
@Shironex Shironex added the Do Not Merge Use this label if something should not be merged. label Dec 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR introduces file descriptor throttling and retry logic to prevent ENFILE/EMFILE errors during concurrent file system operations. It wraps all file-system operations with a configurable concurrency limiter and exponential backoff retry mechanism, then re-exports the throttling configuration APIs from the server layer.

Changes

Cohort / File(s) Summary
Core Throttling & Retry Infrastructure
libs/platform/src/secure-fs.ts
Introduces ThrottleConfig interface with configurable maxConcurrency, maxRetries, baseDelay, and maxDelay. Wraps all fs.promises operations (access, readFile, writeFile, mkdir, readdir, stat, etc.) through executeWithRetry mechanism using p-limit concurrency limiter. Adds helper utilities: isFileDescriptorError, calculateDelay, sleep. Exports configuration APIs: configureThrottling, getThrottlingConfig, getPendingOperations, getActiveOperations. Adds path utilities: joinPath, resolvePath.
Server-Side Re-exports
apps/server/src/lib/secure-fs.ts
Re-exports four new throttling configuration functions from platform's secure-fs module: configureThrottling, getThrottlingConfig, getPendingOperations, getActiveOperations.
Dependency Management
libs/platform/package.json
Adds runtime dependency p-limit (^6.2.0) for concurrency limiting.
Test Infrastructure Updates
libs/platform/tests/secure-fs.test.ts
Comprehensive test suite covering throttling configuration lifecycle, concurrency behavior, pending/active operation tracking, retry parameter validation, and file descriptor error handling.
Platform-Agnostic Path Handling
libs/platform/tests/node-finder.test.ts
Updates three test cases to use path.delimiter instead of hard-coded colons, enabling platform-agnostic path string comparisons.
Minor Size Handling Refactor
libs/git-utils/src/diff.ts
Converts stats.size to Number (fileSize variable) before size comparisons in generateSyntheticDiffForNewFile for consistency.

Sequence Diagram(s)

sequenceDiagram
    participant Client as API Client
    participant Handler as Route Handler
    participant SecureFS as Secure FS Wrapper
    participant Limiter as p-limit Limiter
    participant FSPromises as fs.promises
    participant RetryLogic as Retry & Backoff

    Client->>Handler: File system operation (e.g., readFile)
    Handler->>SecureFS: readFile(path)
    SecureFS->>SecureFS: executeWithRetry()
    
    alt Within concurrency limit
        SecureFS->>Limiter: Acquire slot
        Limiter-->>SecureFS: Slot available
        SecureFS->>FSPromises: Execute fs operation
        FSPromises-->>SecureFS: Success ✓
        SecureFS-->>Handler: Return result
    else File descriptor exhausted (ENFILE/EMFILE)
        SecureFS->>Limiter: Acquire slot
        Limiter-->>SecureFS: Slot available
        SecureFS->>FSPromises: Execute fs operation
        FSPromises-->>SecureFS: ENFILE/EMFILE error
        SecureFS->>RetryLogic: isFileDescriptorError?
        RetryLogic-->>SecureFS: true
        SecureFS->>RetryLogic: calculateDelay(attempt) with jitter
        RetryLogic-->>SecureFS: Exponential backoff delay
        SecureFS->>SecureFS: sleep(delay)
        
        rect rgba(255, 193, 7, 0.1)
            Note over SecureFS: Retry attempt
        end
        
        SecureFS->>FSPromises: Execute fs operation (retry)
        FSPromises-->>SecureFS: Success or max retries exceeded
        SecureFS-->>Handler: Return result or throw
    end
    
    Handler-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Enhancement, Testers-Requested

Poem

🐰 Hop, skip, and throttle with care,
No more files lost in the air!
With p-limit's grace and retries so bold,
Concurrency tales shall safely unfold. 🚀

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing throttling and retry logic in the secure-fs module to address file descriptor errors.
Linked Issues check ✅ Passed The PR successfully implements concurrency limiting with p-limit, retry logic with exponential backoff for file descriptor errors, and exposes monitoring APIs—directly addressing issue #269's objective of preventing ENFILE/EMFILE errors during concurrent task execution.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing file descriptor errors: throttling/retry logic in secure-fs, dependency addition for p-limit, test coverage, and path utilities. The platform-agnostic path delimiter update in node-finder tests is a minor supporting change aligned with overall improvements.
Docstring Coverage ✅ Passed Docstring coverage is 95.24% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/task-execution

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Shironex, 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 significantly enhances the "secure-fs" module by integrating robust throttling and retry mechanisms. The primary goal is to prevent system instability caused by file descriptor exhaustion (ENFILE/EMFILE errors) and to improve the overall reliability of file system interactions. By limiting concurrent operations and automatically retrying transient failures with an exponential backoff strategy, the module becomes more resilient under heavy load, ensuring smoother and more consistent performance.

Highlights

  • Concurrency Limiting: Implemented using "p-limit" to prevent ENFILE/EMFILE errors during high-volume file system operations.
  • Retry Logic: Added exponential backoff and jitter for transient file descriptor errors, improving resilience.
  • Throttling Configuration & Monitoring: Exposed new functions (configureThrottling, getThrottlingConfig, getPendingOperations, getActiveOperations) for dynamic control and visibility into secure-fs operations.
  • Comprehensive Unit Tests: New tests ensure the reliability and correct behavior of the throttling and retry mechanisms.

🧠 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
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 introduces valuable throttling and retry logic to the secure-fs module, which will significantly improve its robustness against file descriptor exhaustion errors. The implementation is clean and the addition of configuration and monitoring functions is a great touch.

My review includes a few suggestions to enhance this new functionality:

  • A high severity suggestion to handle a potential issue in configureThrottling where pending operations could be lost.
  • A medium severity suggestion to improve logging within the retry logic.
  • Two medium severity suggestions to strengthen the unit tests for the new concurrency and retry features.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
libs/platform/src/secure-fs.test.ts (2)

64-90: Test does not verify actual concurrency limiting behavior.

The test name suggests it verifies concurrency limiting, but the local variables (activeConcurrency, maxObservedConcurrency, delays) and the loop are dead code that don't interact with secureFs operations. The test only asserts the config value was set.

Consider either renaming the test to reflect what it actually tests, or implementing a proper concurrency test by mocking fs operations and observing getActiveOperations().


93-106: Tests don't verify actual error handling behavior.

These tests are named for ENFILE/EMFILE error identification but only check that maxRetries config exists. To properly test retry logic, mock an fs method to throw file descriptor errors and verify the wrapper retries before succeeding or failing.

libs/platform/src/secure-fs.ts (2)

122-124: Consider using a configurable logger instead of console.warn.

Using console.warn directly in a library pollutes the consuming application's console and is not configurable. Consider accepting a logger via configureThrottling or using the project's existing createLogger utility (as seen in libs/git-utils/src/diff.ts).


47-53: Recreating limiter while operations are in-flight can cause issues.

When configureThrottling is called with a new maxConcurrency while operations are pending on the old limiter, those pending promises may never settle, leading to memory leaks and unexpected behavior.

🔎 Proposed fix: Guard against in-flight operations
 export function configureThrottling(newConfig: Partial<ThrottleConfig>): void {
+  const newConcurrency = newConfig.maxConcurrency;
+
+  if (newConcurrency !== undefined && newConcurrency !== config.maxConcurrency) {
+    if (fsLimit.activeCount > 0 || fsLimit.pendingCount > 0) {
+      throw new Error(
+        `[SecureFS] Cannot change maxConcurrency while operations are in flight. Active: ${fsLimit.activeCount}, Pending: ${fsLimit.pendingCount}`
+      );
+    }
+    fsLimit = pLimit(newConcurrency);
+  }
+
   config = { ...config, ...newConfig };
-  // Recreate the limiter if concurrency changed
-  if (newConfig.maxConcurrency !== undefined) {
-    fsLimit = pLimit(config.maxConcurrency);
-  }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f952820 and 6d3314f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • apps/server/src/lib/secure-fs.ts
  • libs/git-utils/src/diff.ts
  • libs/platform/package.json
  • libs/platform/src/secure-fs.test.ts
  • libs/platform/src/secure-fs.ts
🧰 Additional context used
🧬 Code graph analysis (2)
libs/platform/src/secure-fs.test.ts (1)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-28)
libs/platform/src/secure-fs.ts (2)
.github/scripts/upload-to-r2.js (2)
  • attempt (65-65)
  • delay (179-179)
apps/server/src/services/auto-mode-service.ts (1)
  • sleep (2528-2551)
🔇 Additional comments (7)
libs/platform/src/secure-fs.test.ts (2)

9-17: Good test setup with config reset.

The beforeEach hook properly resets the throttling configuration to known defaults before each test, ensuring test isolation.


19-50: LGTM: Configuration tests are well-structured.

These tests properly verify:

  • Partial config updates work correctly
  • Existing values are preserved when updating partial config
  • Config structure contains all expected properties
  • Default values match expected defaults
apps/server/src/lib/secure-fs.ts (1)

23-27: LGTM: Clean API surface extension.

The new throttling configuration and monitoring APIs are properly re-exported, maintaining backward compatibility while exposing the new functionality to server code.

libs/platform/src/secure-fs.ts (3)

110-135: Retry logic implementation is sound.

The executeWithRetry function correctly:

  • Wraps operations with the p-limit throttle
  • Implements exponential backoff with jitter for ENFILE/EMFILE errors
  • Respects maxRetries configuration (loop runs maxRetries + 1 times total)
  • Re-throws non-retryable errors immediately

94-98: Backoff calculation starts at baseDelay on first retry.

Note that on the first retry (attempt=0), the delay is baseDelay * 2^0 + jitter = baseDelay + jitter. This is reasonable behavior, but worth documenting if the intent was for the first retry to wait exactly baseDelay.


140-143: All fs wrappers correctly integrate throttling and retry logic.

The wrappers properly:

  • Validate paths before execution
  • Route all operations through executeWithRetry
  • Include meaningful operation names for debugging

Also applies to: 148-159, 164-174, 179-185, 198-209, 214-217, 222-228, 233-236, 241-248, 253-263, 268-275, 281-284

libs/platform/package.json (1)

21-22: p-limit version and ESM compatibility verified.

The addition of p-limit at version ^6.2.0 is appropriate for implementing concurrency limiting. Verification confirms p-limit v6.2.0 is a valid release (December 19, 2024) published as ESM-only, which aligns correctly with the "type": "module" configuration in package.json.

- Enhanced the configureThrottling function to prevent changes to maxConcurrency while operations are in flight.
- Added comprehensive unit tests for secure-fs throttling and retry logic, ensuring correct behavior and configuration.
- Removed outdated secure-fs test file and replaced it with a new, updated version to improve test coverage.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
libs/platform/src/secure-fs.ts (1)

129-131: Console.warn issue previously flagged.

Direct console.warn usage in a library remains present. As noted in the previous review, this pollutes the application console and isn't configurable. Consider adding a logger option to configureThrottling to allow consumers to handle these warnings.

Based on past review comments, this issue was already raised but remains unaddressed.

🧹 Nitpick comments (2)
libs/platform/tests/secure-fs.test.ts (2)

74-82: Consider clarifying test name or improving coverage.

The test name "should throw when changing maxConcurrency while operations are in flight" is misleading—it only verifies the success case (no operations in flight). The actual throwing behavior is never tested because simulating in-flight operations would require async operations and coordination.

Consider either:

  1. Renaming to "should not throw when changing maxConcurrency with no operations in flight"
  2. Adding a more robust integration test that actually triggers the error condition
💡 Potential integration test approach
it('should throw when changing maxConcurrency while operations are in flight', async () => {
  // Create a slow operation that keeps the limiter busy
  const slowOp = secureFs.readFile('/some/large/file');
  
  // Wait briefly to ensure operation is active
  await new Promise(resolve => setTimeout(resolve, 10));
  
  // Should throw because operation is in flight
  expect(() => secureFs.configureThrottling({ maxConcurrency: 50 }))
    .toThrow(/Cannot change maxConcurrency while operations are in flight/);
  
  await slowOp; // cleanup
});

Note: This requires a real file operation, making it more of an integration test.


86-136: LGTM! Retry configuration properly tested.

The tests verify retry configuration APIs work correctly. The inline comments (lines 112-114) appropriately acknowledge that actual retry behavior requires integration testing with real file descriptor errors, which is challenging to mock in ESM.

Optional: Consider adding integration tests that trigger actual ENFILE/EMFILE errors in a controlled environment, perhaps by:

  • Using resource limits (ulimit -n on Unix) in a separate test suite
  • Creating a large number of concurrent file operations to exhaust file descriptors
  • Or documenting manual testing procedures for file descriptor exhaustion scenarios

These would validate the complete retry flow end-to-end.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d3314f and ad983c6.

📒 Files selected for processing (4)
  • libs/git-utils/src/diff.ts
  • libs/platform/src/secure-fs.ts
  • libs/platform/tests/node-finder.test.ts
  • libs/platform/tests/secure-fs.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/git-utils/src/diff.ts
🧰 Additional context used
🧬 Code graph analysis (2)
libs/platform/src/secure-fs.ts (2)
.github/scripts/upload-to-r2.js (2)
  • attempt (65-65)
  • delay (179-179)
apps/server/src/services/auto-mode-service.ts (1)
  • sleep (2528-2551)
libs/platform/tests/secure-fs.test.ts (1)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (9)
libs/platform/tests/node-finder.test.ts (1)

133-133: LGTM! Excellent cross-platform improvement.

Replacing hard-coded colons with path.delimiter makes these tests platform-agnostic, correctly handling Windows (;) and Unix (:) path separators.

Also applies to: 147-147, 156-156

libs/platform/src/secure-fs.ts (6)

19-41: LGTM! Well-structured throttling configuration.

The ThrottleConfig interface and defaults are well-designed. The default values (maxConcurrency: 100, maxRetries: 3, baseDelay: 100ms, maxDelay: 5s) strike a good balance between preventing file descriptor exhaustion and maintaining reasonable performance under load.


47-60: Previous concern addressed.

The implementation now safely prevents changing maxConcurrency while operations are in flight by checking activeCount and pendingCount before recreating the limiter. This addresses the concern raised in the previous review about potential memory leaks and lost promises.


65-81: LGTM! Clean API surface.

The getter functions properly expose throttling state. getThrottlingConfig() correctly returns a shallow copy to prevent external mutation of internal configuration.


86-112: LGTM! Solid retry helpers.

The helper functions are well-implemented:

  • isFileDescriptorError correctly identifies ENFILE/EMFILE errors
  • calculateDelay uses exponential backoff with jitter to prevent thundering herd
  • sleep is a clean Promise-based delay

147-291: LGTM! Comprehensive fs operation wrapping.

All file system operations are correctly wrapped with executeWithRetry, ensuring consistent throttling and retry behavior. Path validation occurs before throttling (fail-fast for security violations), and operation names include file paths for effective debugging.


297-307: LGTM! Clear path construction utilities.

The joinPath and resolvePath wrappers are well-documented. The explicit note that they don't validate paths makes the security boundary clear: these are for construction, validation happens when the path is used in an fs operation.

libs/platform/tests/secure-fs.test.ts (2)

8-50: LGTM! Solid configuration test coverage.

The tests thoroughly verify the configuration API behavior, including:

  • Updating individual configuration values
  • Preserving unspecified fields during partial updates
  • Verifying default values

52-62: LGTM! Basic monitoring coverage.

The tests verify that monitoring APIs exist and return expected values when idle.

Comment on lines +117 to +142
async function executeWithRetry<T>(operation: () => Promise<T>, operationName: string): Promise<T> {
return fsLimit(async () => {
let lastError: unknown;

for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;

if (isFileDescriptorError(error) && attempt < config.maxRetries) {
const delay = calculateDelay(attempt);
console.warn(
`[SecureFS] ${operationName}: File descriptor error (attempt ${attempt + 1}/${config.maxRetries + 1}), retrying in ${delay}ms`
);
await sleep(delay);
continue;
}

throw error;
}
}

throw lastError;
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Unreachable code at line 140.

The throw lastError statement on line 140 is unreachable. The loop will always either return successfully (line 123) or throw an error (line 136) before completing all iterations. Every error path within the loop leads to either a continue or throw, so the loop cannot exit normally.

While this doesn't affect behavior, it's a code smell that could confuse maintainers.

🔎 Proposed fix
     for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
       try {
         return await operation();
       } catch (error) {
-        lastError = error;
-
         if (isFileDescriptorError(error) && attempt < config.maxRetries) {
           const delay = calculateDelay(attempt);
           console.warn(
             `[SecureFS] ${operationName}: File descriptor error (attempt ${attempt + 1}/${config.maxRetries + 1}), retrying in ${delay}ms`
           );
           await sleep(delay);
           continue;
         }

         throw error;
       }
     }
-
-    throw lastError;
   });
 }
🤖 Prompt for AI Agents
In libs/platform/src/secure-fs.ts around lines 117 to 142, the final "throw
lastError" is unreachable because every loop iteration either returns,
continues, or throws; remove the unreachable throw statement (or alternatively
replace it with a single explicit throw new Error(`${operationName}: exhausted
retries`) if you want an explicit post-loop failure message) so the function no
longer contains dead code and the control flow is clear.

@webdevcody webdevcody merged commit b60e8f0 into main Dec 26, 2025
8 checks passed
@webdevcody webdevcody deleted the fix/task-execution branch December 26, 2025 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix executing multiply task at once

3 participants