Skip to content

Refactor retry and timeout types with improved documentation#3

Merged
adelrodriguez merged 1 commit intomainfrom
02-22-refactor_retry_and_timeout_types_with_improved_documentation
Feb 22, 2026
Merged

Refactor retry and timeout types with improved documentation#3
adelrodriguez merged 1 commit intomainfrom
02-22-refactor_retry_and_timeout_types_with_improved_documentation

Conversation

@adelrodriguez
Copy link
Copy Markdown
Owner

@adelrodriguez adelrodriguez commented Feb 22, 2026

This pull request refactors the type system to improve API ergonomics and type safety:

Type System Improvements:

  • Split RetryPolicy into RetryOptions (user input) and RetryPolicy (internal representation)
  • Split TimeoutOptions into user input type and TimeoutPolicy internal type
  • Created discriminated union types for retry policies with LinearBackoffRetryPolicy and ExponentialBackoffRetryPolicy
  • Added maxDelayMs?: never constraint to linear backoff to prevent invalid configurations

API Changes:

  • Builder methods now accept the more flexible input types (RetryOptions, TimeoutOptions)
  • Removed fallback logic from normalizeRetryPolicy() - now expects fully formed policies
  • Updated normalizeTimeoutOptions() to handle the new type structure

Documentation:

  • Added comprehensive JSDoc comments to all type interfaces explaining parameters and usage
  • Documented shorthand vs full configuration options
  • Added future follow-up note about scope: "attempt" timeout semantics

The changes maintain backward compatibility while providing better type checking and clearer API boundaries between user-facing and internal types.

Summary by CodeRabbit

  • Refactor

    • Timeout configuration now requires explicit options rather than raw numbers.
    • Retry numeric shorthand now defaults to a constant backoff; object-form retry must explicitly specify backoff.
  • Documentation

    • Updated docs to reflect the new retry forms, precedence rules, and timeout semantics.
  • Tests

    • Added comprehensive unit tests covering retry-policy normalization and related behaviors.

Greptile Summary

This PR refactors the type system to improve API ergonomics and type safety by splitting user-facing input types from internal policy representations.

Key Changes:

  • Introduced discriminated union types for retry policies (ConstantBackoffRetryPolicy, LinearBackoffRetryPolicy, ExponentialBackoffRetryPolicy) with proper backoff discriminant fields
  • Split user input types (RetryOptions, TimeoutOptions) from internal policy types (RetryPolicy, TimeoutPolicy) to provide clearer API boundaries
  • Added maxDelayMs?: never constraints to constant and linear backoff types to prevent invalid configurations
  • Changed number shorthand behavior from "linear" to "constant" backoff (intentional breaking change documented in design.md)
  • Refactored normalizeRetryPolicy() to use exhaustive switch statement with assertUnreachable() helper for type safety
  • Added comprehensive JSDoc documentation explaining all type parameters and usage patterns
  • Updated tests to reflect new type requirements (explicit backoff field now required for object form)

Quality:

  • All tests pass (13/13)
  • Design documentation updated to match implementation
  • Type safety improved through discriminated unions and exhaustiveness checking
  • No logical errors or security issues identified

Confidence Score: 5/5

  • Safe to merge - well-designed type system refactor with comprehensive tests and documentation
  • All tests pass, types are properly structured with discriminated unions, exhaustiveness checking prevents runtime errors, documentation is thorough, and the changes align with the design specification
  • No files require special attention

Important Files Changed

Filename Overview
src/lib/types.ts Refactored retry and timeout types into discriminated unions with comprehensive JSDoc documentation
src/lib/retry.ts Refactored normalizeRetryPolicy to handle discriminated union types with exhaustive switch statement
src/lib/builder.ts Updated builder methods to accept flexible input types (RetryOptions, TimeoutOptions)
src/lib/tests/retry.test.ts Added comprehensive unit tests for retry policy normalization covering all backoff strategies

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class RetryOptions {
        <<union>>
        number | RetryPolicy
    }
    
    class RetryPolicy {
        <<union>>
    }
    
    class BaseRetryPolicy {
        +number limit
        +number? delayMs
        +boolean? jitter
        +Function? shouldRetry
    }
    
    class ConstantBackoffRetryPolicy {
        +string backoff = "constant"
        +never? maxDelayMs
    }
    
    class LinearBackoffRetryPolicy {
        +string backoff = "linear"
        +never? maxDelayMs
    }
    
    class ExponentialBackoffRetryPolicy {
        +string backoff = "exponential"
        +number? maxDelayMs
    }
    
    class TimeoutOptions {
        <<union>>
        number | TimeoutPolicy
    }
    
    class TimeoutPolicy {
        +number ms
        +string scope = "total"
    }
    
    RetryPolicy <|-- ConstantBackoffRetryPolicy
    RetryPolicy <|-- LinearBackoffRetryPolicy
    RetryPolicy <|-- ExponentialBackoffRetryPolicy
    BaseRetryPolicy <|-- ConstantBackoffRetryPolicy
    BaseRetryPolicy <|-- LinearBackoffRetryPolicy
    BaseRetryPolicy <|-- ExponentialBackoffRetryPolicy
    
    RetryOptions --> RetryPolicy
    TimeoutOptions --> TimeoutPolicy
Loading

Last reviewed commit: 154a2e1

@adelrodriguez adelrodriguez marked this pull request as ready for review February 22, 2026 22:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 22, 2026

Walkthrough

Refactors retry and timeout types and normalization: introduces discriminated retry backoff interfaces and RetryOptions (number | policy), changes numeric shorthand to constant backoff, adds TimeoutPolicy, tightens normalization logic, updates builder API, adds tests, and an assertUnreachable utility; includes one planning note.

Changes

Cohort / File(s) Summary
Planning
.context/plan.md
Adds a Phase 4 follow-up note about evaluating "attempt" timeout semantics and interaction with retry/backoff (editorial-only).
Types
src/lib/types.ts
Introduces BaseRetryPolicy and discriminated backoff interfaces (LinearBackoffRetryPolicy, ExponentialBackoffRetryPolicy, ConstantBackoffRetryPolicy), adds RetryPolicy union and RetryOptions (number
Retry logic
src/lib/retry.ts
Narrows inputs to RetryOptions, changes numeric shorthand to produce backoff: "constant", replaces merge/defaulting with explicit switch on backoff, propagates fields per-case, adds assertUnreachable usage; updates normalization/adapter signatures.
Builder API
src/lib/builder.ts
Updates public API types to use RetryOptions for retry() and imports/uses TimeoutPolicy; normalizeTimeoutOptions now returns TimeoutPolicy and signatures tightened.
Utilities
src/lib/utils.ts
Adds export function assertUnreachable(value: never): never which throws for exhaustive-check failures.
Tests
src/lib/__tests__/retry.test.ts, src/lib/__tests__/runner.test.ts
Adds retry.test.ts with unit tests for normalizeRetryPolicy covering numeric shorthand, backoff forms, optional fields and round-trip; updates runner.test.ts to include backoff: "constant" in an existing test input.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • feat: add execution runner #1: Updates retry/type handling and normalization (signatures and backoff behavior) that directly overlap with the changes in this PR.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring work: retry and timeout types are restructured with enhanced documentation, matching the primary changes across types.ts, retry.ts, and builder.ts.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 02-22-refactor_retry_and_timeout_types_with_improved_documentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Owner Author

adelrodriguez commented Feb 22, 2026

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +3 to 13
export function normalizeRetryPolicy(policy: RetryOptions): RetryPolicy {
if (typeof policy === "number") {
return {
backoff: "linear",
backoff: "linear" as const,
delayMs: 0,
limit: policy,
}
}

return {
backoff: policy.backoff ?? "linear",
delayMs: policy.delayMs ?? 0,
...policy,
}
return policy
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Check that retry logic (Phase 3) handles optional delayMs field - currently no default is applied when object form is used

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/retry.ts
Line: 3-13

Comment:
Check that retry logic (Phase 3) handles optional `delayMs` field - currently no default is applied when object form is used

How can I resolve this? If you propose a fix, please make it concise.

@adelrodriguez adelrodriguez changed the base branch from 02-22-feat_add_execution_runner to graphite-base/3 February 22, 2026 23:06
@adelrodriguez adelrodriguez force-pushed the 02-22-refactor_retry_and_timeout_types_with_improved_documentation branch from 88617c1 to 72fb8cd Compare February 22, 2026 23:06
@adelrodriguez adelrodriguez changed the base branch from graphite-base/3 to main February 22, 2026 23:06
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

src/lib/retry.ts Outdated
delayMs: policy.delayMs ?? 0,
...policy,
}
return policy
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Returning the policy object directly breaks the normalization contract - the old implementation filled in defaults (delayMs: 0, backoff: "linear"). Now { limit: 2 } without backoff causes type errors since RetryPolicy requires the discriminant field.

Suggested change
return policy
return {
delayMs: 0,
jitter: undefined,
shouldRetry: undefined,
...policy,
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/retry.ts
Line: 12

Comment:
Returning the policy object directly breaks the normalization contract - the old implementation filled in defaults (`delayMs: 0`, `backoff: "linear"`). Now `{ limit: 2 }` without `backoff` causes type errors since `RetryPolicy` requires the discriminant field.

```suggestion
  return {
    delayMs: 0,
    jitter: undefined,
    shouldRetry: undefined,
    ...policy,
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 5 to +6
return {
backoff: "linear",
backoff: "constant",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Changed from "linear" to "constant" backoff. The design doc (.context/design.md:327) specifies number shorthand should normalize to { limit: 3, delayMs: 0, backoff: "linear" }, not "constant".

Suggested change
return {
backoff: "linear",
backoff: "constant",
return {
backoff: "linear",
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/retry.ts
Line: 5-6

Comment:
Changed from `"linear"` to `"constant"` backoff. The design doc (`.context/design.md:327`) specifies number shorthand should normalize to `{ limit: 3, delayMs: 0, backoff: "linear" }`, not `"constant"`.

```suggestion
    return {
      backoff: "linear",
```

How can I resolve this? If you propose a fix, please make it concise.

})

it("defaults omitted backoff to constant", () => {
expect(normalizeRetryPolicy({ limit: 2 })).toEqual({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

{ limit: 2 } without backoff field fails TypeScript compilation - RetryPolicy is now a discriminated union requiring the backoff discriminant. Either fix the test input or adjust the type system to make backoff optional with a default.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/__tests__/retry.test.ts
Line: 16

Comment:
`{ limit: 2 }` without `backoff` field fails TypeScript compilation - `RetryPolicy` is now a discriminated union requiring the `backoff` discriminant. Either fix the test input or adjust the type system to make `backoff` optional with a default.

How can I resolve this? If you propose a fix, please make it concise.


describe("normalizeRetryPolicy", () => {
it("normalizes number shorthand to constant backoff", () => {
expect(normalizeRetryPolicy(3)).toEqual({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test expects "constant" but design doc specifies number shorthand normalizes to "linear" backoff (see .context/design.md:327)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/__tests__/retry.test.ts
Line: 8

Comment:
Test expects `"constant"` but design doc specifies number shorthand normalizes to `"linear"` backoff (see `.context/design.md:327`)

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +15 to +23
it("defaults omitted backoff to constant", () => {
expect(normalizeRetryPolicy({ limit: 2 })).toEqual({
backoff: "constant",
delayMs: 0,
jitter: undefined,
limit: 2,
shouldRetry: undefined,
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test expects normalized output to include delayMs: 0, jitter: undefined, and shouldRetry: undefined, but normalizeRetryPolicy now returns the input object unchanged (just { limit: 2 }). The assertion will fail.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/__tests__/retry.test.ts
Line: 15-23

Comment:
Test expects normalized output to include `delayMs: 0`, `jitter: undefined`, and `shouldRetry: undefined`, but `normalizeRetryPolicy` now returns the input object unchanged (just `{ limit: 2 }`). The assertion will fail.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/__tests__/retry.test.ts`:
- Around line 15-23: The test about defaulting omitted backoff should be removed
or replaced because it fails type-checking and the function normalizeRetryPolicy
no longer fills defaults; update the test file to either delete this spec or
change it to assert pass-through behavior by providing a valid RetryOptions
object (include an explicit backoff literal) and expecting normalizeRetryPolicy
to return the same object (reference normalizeRetryPolicy, RetryOptions, and
RetryPolicy to find the relevant types and function).
- Around line 25-50: Update the two tests for normalizeRetryPolicy to reflect
that the function no longer injects defaults: change the test names to remove
"with default delay" and assert that providing { backoff: "linear", limit: 2 }
returns the same object (no delayMs added), and that providing { backoff:
"exponential", limit: 4, maxDelayMs: 1000 } returns the same object (no delayMs
added but maxDelayMs remains); remove the noisy jitter: undefined and
shouldRetry: undefined expectations so the assertions match the function's
behavior of returning inputs as-is for normalizeRetryPolicy.

In `@src/lib/types.ts`:
- Around line 71-80: The JSDoc on ConstantBackoffRetryPolicy incorrectly says
"Omit for constant backoff" implying the backoff field is optional; update the
comment for the exported interface ConstantBackoffRetryPolicy (and its backoff
property) to reflect that backoff: "constant" is a required discriminant and not
optional — replace the misleading wording with a clear statement like "Required
discriminant: use a fixed delay between attempts" so the docs match the current
type.

Comment on lines +25 to +50
it("normalizes linear policy with default delay", () => {
expect(normalizeRetryPolicy({ backoff: "linear", limit: 2 })).toEqual({
backoff: "linear",
delayMs: 0,
jitter: undefined,
limit: 2,
shouldRetry: undefined,
})
})

it("normalizes exponential policy and preserves maxDelayMs", () => {
expect(
normalizeRetryPolicy({
backoff: "exponential",
limit: 4,
maxDelayMs: 1000,
})
).toEqual({
backoff: "exponential",
delayMs: 0,
jitter: undefined,
limit: 4,
maxDelayMs: 1000,
shouldRetry: undefined,
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Both tests assert synthesized defaults that normalizeRetryPolicy no longer populates — fix expectations and test names

Both the linear (lines 25-33) and exponential (lines 35-50) tests expect delayMs: 0 to be set when delayMs is omitted from the input. This was the old normalization behavior, which the PR intentionally removed. normalizeRetryPolicy now returns object inputs as-is, so neither delayMs: 0 nor any other default is injected.

The jitter: undefined / shouldRetry: undefined fields in the expected objects are benign — toEqual ignores object keys with undefined properties — but they add noise and should be removed for clarity.

The test names ("normalizes … with default delay") are also stale descriptions of the removed behavior and should be updated.

🔧 Proposed fixes for both tests
- it("normalizes linear policy with default delay", () => {
-   expect(normalizeRetryPolicy({ backoff: "linear", limit: 2 })).toEqual({
-     backoff: "linear",
-     delayMs: 0,
-     jitter: undefined,
-     limit: 2,
-     shouldRetry: undefined,
-   })
- })
+ it("passes through a linear backoff policy unchanged", () => {
+   expect(normalizeRetryPolicy({ backoff: "linear", limit: 2 })).toEqual({
+     backoff: "linear",
+     limit: 2,
+   })
+ })

- it("normalizes exponential policy and preserves maxDelayMs", () => {
-   expect(
-     normalizeRetryPolicy({
-       backoff: "exponential",
-       limit: 4,
-       maxDelayMs: 1000,
-     })
-   ).toEqual({
-     backoff: "exponential",
-     delayMs: 0,
-     jitter: undefined,
-     limit: 4,
-     maxDelayMs: 1000,
-     shouldRetry: undefined,
-   })
- })
+ it("passes through an exponential backoff policy and preserves maxDelayMs", () => {
+   expect(
+     normalizeRetryPolicy({
+       backoff: "exponential",
+       limit: 4,
+       maxDelayMs: 1000,
+     })
+   ).toEqual({
+     backoff: "exponential",
+     limit: 4,
+     maxDelayMs: 1000,
+   })
+ })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("normalizes linear policy with default delay", () => {
expect(normalizeRetryPolicy({ backoff: "linear", limit: 2 })).toEqual({
backoff: "linear",
delayMs: 0,
jitter: undefined,
limit: 2,
shouldRetry: undefined,
})
})
it("normalizes exponential policy and preserves maxDelayMs", () => {
expect(
normalizeRetryPolicy({
backoff: "exponential",
limit: 4,
maxDelayMs: 1000,
})
).toEqual({
backoff: "exponential",
delayMs: 0,
jitter: undefined,
limit: 4,
maxDelayMs: 1000,
shouldRetry: undefined,
})
})
it("passes through a linear backoff policy unchanged", () => {
expect(normalizeRetryPolicy({ backoff: "linear", limit: 2 })).toEqual({
backoff: "linear",
limit: 2,
})
})
it("passes through an exponential backoff policy and preserves maxDelayMs", () => {
expect(
normalizeRetryPolicy({
backoff: "exponential",
limit: 4,
maxDelayMs: 1000,
})
).toEqual({
backoff: "exponential",
limit: 4,
maxDelayMs: 1000,
})
})
🧰 Tools
🪛 GitHub Check: unit

[failure] 42-42: error: expect(received).toEqual(expected)
{
"backoff": "exponential",

  • "delayMs": 0,

  • "jitter": undefined,
    "limit": 4,
    "maxDelayMs": 1000,

  • "shouldRetry": undefined,
    }

  • Expected - 3

  • Received + 0

    at <anonymous> (/home/runner/work/hardtry/hardtry/src/lib/__tests__/retry.test.ts:42:7)
    

[failure] 26-26: error: expect(received).toEqual(expected)
{
"backoff": "linear",

  • "delayMs": 0,

  • "jitter": undefined,
    "limit": 2,

  • "shouldRetry": undefined,
    }

  • Expected - 3

  • Received + 0

    at <anonymous> (/home/runner/work/hardtry/hardtry/src/lib/__tests__/retry.test.ts:26:67)
    
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/__tests__/retry.test.ts` around lines 25 - 50, Update the two tests
for normalizeRetryPolicy to reflect that the function no longer injects
defaults: change the test names to remove "with default delay" and assert that
providing { backoff: "linear", limit: 2 } returns the same object (no delayMs
added), and that providing { backoff: "exponential", limit: 4, maxDelayMs: 1000
} returns the same object (no delayMs added but maxDelayMs remains); remove the
noisy jitter: undefined and shouldRetry: undefined expectations so the
assertions match the function's behavior of returning inputs as-is for
normalizeRetryPolicy.

@adelrodriguez adelrodriguez force-pushed the 02-22-refactor_retry_and_timeout_types_with_improved_documentation branch from 72fb8cd to 5f999ea Compare February 22, 2026 23:16
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.context/design.md (1)

153-189: ⚠️ Potential issue | 🟡 Minor

Design doc code snippets are stale after the type refactoring.

The BuilderConfig and TryBuilder code examples in the design doc don't reflect the actual types:

  1. Line 156: timeout?: TimeoutOptions → should be timeout?: TimeoutPolicy (matches src/lib/types.ts line 127)
  2. Line 177: retry(policy: number | RetryPolicy) → should be retry(policy: RetryOptions) (matches src/lib/builder.ts line 30)
  3. Line 184: timeout(options: number | TimeoutOptions) → should be timeout(options: TimeoutOptions) (matches src/lib/builder.ts line 37)
📝 Proposed fix
 interface BuilderConfig {
   retry?: RetryPolicy
-  timeout?: TimeoutOptions
+  timeout?: TimeoutPolicy
   signal?: AbortSignal
   wraps?: WrapFn[]
 }
-  retry(policy: number | RetryPolicy): TryBuilder {
+  retry(policy: RetryOptions): TryBuilder {
-  timeout(options: number | TimeoutOptions): TryBuilder {
+  timeout(options: TimeoutOptions): TryBuilder {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.context/design.md around lines 153 - 189, Update the design doc code
snippets to match the refactored types: change BuilderConfig's timeout property
from TimeoutOptions to TimeoutPolicy, update TryBuilder.retry signature from
retry(policy: number | RetryPolicy) to retry(policy: RetryOptions), and update
TryBuilder.timeout signature from timeout(options: number | TimeoutOptions) to
timeout(options: TimeoutOptions); keep the rest of the examples identical so
they match the actual types defined in src/lib/types.ts and src/lib/builder.ts
and reference the same symbol names (BuilderConfig, TryBuilder, retry, timeout).
🧹 Nitpick comments (2)
src/lib/types.ts (1)

49-58: Consider whether linear backoff should also support maxDelayMs.

Linear delay grows as delayMs * attempt, which can become arbitrarily large for high attempt counts. Currently maxDelayMs?: never prevents setting a cap, while ExponentialBackoffRetryPolicy allows it. If a user configures a high limit with linear backoff, there's no way to bound the delay.

This may be an intentional design choice, but worth confirming — if so, a brief JSDoc note explaining why would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/types.ts` around lines 49 - 58, LinearBackoffRetryPolicy currently
forbids maxDelayMs (maxDelayMs?: never) which prevents capping the linear growth
of delay (delayMs * attempt); either allow an optional maxDelayMs on
LinearBackoffRetryPolicy or add a JSDoc note explaining the deliberate
exclusion. To fix, update the LinearBackoffRetryPolicy definition to make
maxDelayMs?: number (matching ExponentialBackoffRetryPolicy behavior) so callers
can bound delay for high limit values, or add a clear JSDoc comment on
LinearBackoffRetryPolicy explaining why a cap is not permitted and documenting
the expected growth (delayMs * attempt) and risks for large limit values;
reference LinearBackoffRetryPolicy, BaseRetryPolicy,
ExponentialBackoffRetryPolicy, delayMs, attempt, and maxDelayMs in the change so
reviewers can locate and validate the update.
src/lib/retry.ts (1)

13-41: Consider consolidating the constant and linear branches.

Both branches produce identical output shapes (same fields, same defaults) — only the backoff discriminant differs. This is fine for explicitness, but if the pattern grows with more backoff strategies, a shared helper could reduce repetition.

♻️ Optional: shared helper
+function normalizeBasePolicy(
+  policy: RetryPolicy,
+): Omit<RetryPolicy, "backoff" | "maxDelayMs"> {
+  return {
+    delayMs: policy.delayMs ?? 0,
+    jitter: policy.jitter,
+    limit: policy.limit,
+    shouldRetry: policy.shouldRetry,
+  }
+}
+
 // Then in the switch:
 // case "constant":
-//   return { backoff: "constant", delayMs: policy.delayMs ?? 0, ... }
+//   return { ...normalizeBasePolicy(policy), backoff: "constant" }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/retry.ts` around lines 13 - 41, The switch over policy.backoff
duplicates the "constant" and "linear" branches; consolidate them by merging the
two cases into one that returns the shared shape and sets backoff to
policy.backoff (or otherwise derives the discriminant) so you avoid repeating
delayMs, jitter, limit, and shouldRetry; update the switch around policy.backoff
in src/lib/retry.ts (the "constant" and "linear" cases) to use a single branch
or a small helper that builds the common object and keep the "exponential" case
separate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.context/design.md:
- Around line 153-189: Update the design doc code snippets to match the
refactored types: change BuilderConfig's timeout property from TimeoutOptions to
TimeoutPolicy, update TryBuilder.retry signature from retry(policy: number |
RetryPolicy) to retry(policy: RetryOptions), and update TryBuilder.timeout
signature from timeout(options: number | TimeoutOptions) to timeout(options:
TimeoutOptions); keep the rest of the examples identical so they match the
actual types defined in src/lib/types.ts and src/lib/builder.ts and reference
the same symbol names (BuilderConfig, TryBuilder, retry, timeout).

---

Duplicate comments:
In `@src/lib/types.ts`:
- Around line 71-80: The JSDoc for the ConstantBackoffRetryPolicy.backoff
property is wrong — it says "Omit for constant backoff" even though backoff:
"constant" is required; update the comment on the backoff property in the
ConstantBackoffRetryPolicy interface to accurately describe that this field is a
required discriminant indicating a fixed delay between attempts (e.g., "Use a
fixed delay between attempts; set to 'constant'"), and leave the maxDelayMs
comment as "Not supported for constant backoff."

---

Nitpick comments:
In `@src/lib/retry.ts`:
- Around line 13-41: The switch over policy.backoff duplicates the "constant"
and "linear" branches; consolidate them by merging the two cases into one that
returns the shared shape and sets backoff to policy.backoff (or otherwise
derives the discriminant) so you avoid repeating delayMs, jitter, limit, and
shouldRetry; update the switch around policy.backoff in src/lib/retry.ts (the
"constant" and "linear" cases) to use a single branch or a small helper that
builds the common object and keep the "exponential" case separate.

In `@src/lib/types.ts`:
- Around line 49-58: LinearBackoffRetryPolicy currently forbids maxDelayMs
(maxDelayMs?: never) which prevents capping the linear growth of delay (delayMs
* attempt); either allow an optional maxDelayMs on LinearBackoffRetryPolicy or
add a JSDoc note explaining the deliberate exclusion. To fix, update the
LinearBackoffRetryPolicy definition to make maxDelayMs?: number (matching
ExponentialBackoffRetryPolicy behavior) so callers can bound delay for high
limit values, or add a clear JSDoc comment on LinearBackoffRetryPolicy
explaining why a cap is not permitted and documenting the expected growth
(delayMs * attempt) and risks for large limit values; reference
LinearBackoffRetryPolicy, BaseRetryPolicy, ExponentialBackoffRetryPolicy,
delayMs, attempt, and maxDelayMs in the change so reviewers can locate and
validate the update.

@adelrodriguez adelrodriguez force-pushed the 02-22-refactor_retry_and_timeout_types_with_improved_documentation branch from 5f999ea to 9cf5642 Compare February 22, 2026 23:24
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.

1 participant