Skip to content

feat(cli): add Effect.timeout to preset imports to prevent hanging #34

@aridyckovsky

Description

@aridyckovsky

Summary

Add timeout protection to dynamic preset imports in loadPreset to prevent the CLI from hanging indefinitely when a preset package has issues during module initialization.

Problem

Current implementation in packages/cli/src/loaders/presets.ts:

const module = yield* Effect.tryPromise({
  try: () => import(name),
  catch: error => new PresetLoadError({
    preset: name,
    message: `Failed to import: ${String(error)}`
  })
})

Edge case: If a preset package:

  • Has an infinite loop during initialization
  • Makes a hanging network request
  • Deadlocks on a resource

...the CLI will hang indefinitely with no feedback to the user.

Proposed Solution

Wrap the import with Effect.timeout:

const loadPreset = (name: string): Effect.Effect<Preset, PresetLoadError> =>
  Effect.gen(function* () {
    // Dynamically import preset module with timeout
    const module = yield* Effect.tryPromise({
      try: () => import(name),
      catch: error =>
        new PresetLoadError({
          preset: name,
          message: `Failed to import: ${String(error)}`
        })
    }).pipe(
      Effect.timeout("5 seconds"), // Reasonable timeout for module loading
      Effect.catchTag("TimeoutException", () =>
        Effect.fail(
          new PresetLoadError({
            preset: name,
            message: "Import timed out after 5 seconds (possible infinite loop or deadlock)"
          })
        )
      )
    )

    // ... rest of validation
  })

Configuration

Consider making timeout configurable:

// In config schema
presets: {
  names: ReadonlyArray<string>
  timeout?: number // milliseconds, default 5000
}

Benefits

  • Prevents CLI from hanging indefinitely
  • Provides clear error message to user
  • Allows graceful fallback (warning + continue)
  • Protects against malicious or buggy preset packages

Acceptance Criteria

  • Effect.timeout added to preset imports
  • Timeout exception caught and converted to PresetLoadError
  • Default timeout of 5 seconds
  • Error message explains timeout occurred
  • Test added for timeout scenario (mock slow import)
  • Documentation updated

Testing

it.effect("should timeout on slow preset import", () =>
  Effect.gen(function* () {
    // Mock preset that takes 10 seconds to load
    const slowPreset = {
      rules: [],
      defaults: {}
    }
    
    // Should fail with timeout error
    const result = yield* Effect.flip(loadPreset("slow-preset"))
    
    expect(result).toBeInstanceOf(PresetLoadError)
    expect(result.message).toContain("timed out")
  })
)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    effect-tsEffect-TS patterns and usagepkg:cliIssues related to @effect-migrate/cli packagepriority:lowLow prioritytype:featureNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions