Skip to content

fix(core): retry busy write admission - #235

Merged
khaliqgant merged 6 commits into
mainfrom
fix/workspace-busy-admission-retry
Jul 14, 2026
Merged

fix(core): retry busy write admission#235
khaliqgant merged 6 commits into
mainfrom
fix/workspace-busy-admission-retry

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

  • reuse the Relayfile SDK's existing retry layer for direct write admission (no outer retry loop)
  • honor Retry-After up to 30 seconds only for pre-op PUT .../fs/file responses with HTTP 429, code=workspace_busy, and reason=write_admission_limit
  • preserve the prior two-second cap for all other retryable 429/5xx responses
  • bound pre-op admission by writebackTimeoutMs and surface a retryable RelayfileWritebackAdmissionTimeoutError without leaving an orphan retry timer

Attempt timeline

The SDK keeps its default maxRetries=3, so there are exactly four total attempts. A five-second server delay produces attempts at t+0s, t+5s, t+10s, and t+15s.

At the 30-second safety bound, the theoretical fourth attempt is t+90s. Daily-ship's 90-second client deadline is installed before the first request, so it governs that pathological boundary: it aborts the SDK wait at t+90s, returns the terminal admission-timeout error, and cancels the pending retry rather than allowing an orphaned attempt. No independent adapter retry loop is stacked on the SDK.

URL, request body, and idempotency payload remain byte-identical across attempts.

Scope audit

  • direct HTTP writes through adapter-core: changed
  • adapter-core consumers in relay-helpers (generic and Linear): inherit the bounded behavior
  • mount-backed writes: unchanged
  • GitHub's separate local write helper: unchanged
  • other 429 reasons and 5xx responses: retain the two-second cap
  • package versions: intentionally untouched for the feature PR; publish/bump follows after review and merge

Tests

  • red-first regression: existing SDK truncated a five-second admission delay to two seconds
  • exact four-attempt success with 5s/5s/5s delays and byte-identical URL/body
  • exact four-attempt exhausted error with retryable classification
  • wrong workspace-busy reason retains 2s cap
  • 503 retains 2s cap
  • client deadline aborts a 30s wait with one request and no later orphan attempt
  • digit-leading date headers are parsed as dates rather than partial relative seconds
  • bare retryable responses pin the SDK backoff schedule at 100/200/400ms
  • implicit 90s admission default survives a 5s delay and bounds repeated 30s delays without an orphan attempt

Validation

  • npm ci (real install)
  • npx turbo build typecheck test: 147/147 tasks green
  • adapter-core vfs-client tests: 28/28 green
  • adapter-core typecheck: green
  • git diff --check: clean
  • GitHub Build & Test: green

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@khaliqgant, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e0438bc6-3038-482f-838d-0d1b69813f14

📥 Commits

Reviewing files that changed from the base of the PR and between 22d47b1 and 554f2f3.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/core/src/vfs-client/index.ts
  • packages/core/tests/vfs-client/vfs-client.test.ts
📝 Walkthrough

Walkthrough

Direct Relayfile HTTP writes now apply workspace-busy-specific Retry-After handling, enforce writeback admission deadlines, expose a timeout error, and classify eligible admission failures as retryable. Tests cover retry delays, legacy caps, error details, and abort behavior.

Changes

Direct write admission

Layer / File(s) Summary
Workspace-busy retry handling
packages/core/src/vfs-client/index.ts, CHANGELOG.md
Direct Relayfile requests detect workspace-busy responses, honor bounded Retry-After values, preserve legacy retry caps, and document the behavior.
Admission deadline and error propagation
packages/core/src/vfs-client/index.ts
Direct writeback aborts at writebackTimeoutMs, validates queued operation IDs, clears timers, and marks workspace-busy admission errors retryable.
Retry and deadline validation
packages/core/tests/vfs-client/vfs-client.test.ts
Tests verify advertised delays, retry caps, wrapped API errors, and cancellation of follow-up attempts after admission timeout.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant writeJsonFile
  participant RelayFileSDK
  participant RelayfileAPI
  Caller->>writeJsonFile: request direct write
  writeJsonFile->>RelayFileSDK: submit write admission
  RelayFileSDK->>RelayfileAPI: send HTTP request
  RelayfileAPI-->>RelayFileSDK: workspace_busy 429 with Retry-After
  RelayFileSDK->>RelayfileAPI: retry within configured deadline
  RelayfileAPI-->>writeJsonFile: queued response or admission failure
Loading

Possibly related PRs

Poem

I’m a bunny hopping through the queue,
With Retry-After timed just right for you.
Busy work waits, but deadlines stay near,
Abort the extras—no orphan hops here!
Four small tries, then off I flee.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: fixing retry behavior for busy write admission.
Description check ✅ Passed The description is directly related to the changes and accurately describes the retry and timeout behavior updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/workspace-busy-admission-retry

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.

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

Copy link
Copy Markdown

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 updates the direct HTTP write admission layer to honor Retry-After headers up to 30 seconds for workspace_busy / write_admission_limit responses, while preserving the existing two-second cap for other retryable responses and aborting cleanly at the writeback deadline. It also introduces a new RelayfileWritebackAdmissionTimeoutError and adds extensive test coverage. The review feedback highlights three key improvement opportunities: refining retryAfterDelayMs to prevent date strings starting with digits from being incorrectly parsed as relative seconds, optimizing responseWithRetryAfter by passing response.body directly to avoid buffering the response in memory, and ensuring isDirectWriteAdmissionRequest handles relative URLs correctly by providing a dummy base URL to the URL constructor.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +251 to +256
function retryAfterDelayMs(value: string): number | undefined {
const seconds = Number.parseInt(value, 10);
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1_000;
const timestamp = Date.parse(value);
return Number.isNaN(timestamp) ? undefined : Math.max(0, timestamp - Date.now());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of retryAfterDelayMs uses Number.parseInt(value, 10) to check if the Retry-After header is a relative number of seconds. However, parseInt parses characters from left to right until it encounters a non-digit.

If the Retry-After header contains an ISO 8601 date string (e.g., "2026-06-19T12:01:04Z") or a date string that starts with a digit (e.g., "29 Oct 2026 07:28:00 GMT"), parseInt will successfully parse the leading digits (e.g., 2026 or 29) and treat them as a relative delay in seconds instead of parsing the string as a timestamp.

To prevent this, we should use a regular expression to ensure the entire string consists only of digits before parsing it as relative seconds.

function retryAfterDelayMs(value: string): number | undefined {
  const trimmed = value.trim();
  if (/^\d+$/.test(trimmed)) {
    return Number.parseInt(trimmed, 10) * 1_000;
  }
  const timestamp = Date.parse(trimmed);
  return Number.isNaN(timestamp) ? undefined : Math.max(0, timestamp - Date.now());
}

@khaliqgant khaliqgant Jul 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3c09d3d. Relative-seconds parsing now requires the entire trimmed header to be digits. Exact workspace-admission responses are normalized to a numeric delay before the SDK sees them, which also avoids the SDK misreading digit-leading date strings. Added a red/green regression using a digit-leading 1970 date (old behavior slept 1s per retry; fixed behavior retries immediately).

Comment thread packages/core/src/vfs-client/index.ts Outdated
Comment on lines +267 to +278
async function responseWithRetryAfter(
response: Response,
retryAfter: string
): Promise<Response> {
const headers = new Headers(response.headers);
headers.set("Retry-After", retryAfter);
return new Response(await response.arrayBuffer(), {
status: response.status,
statusText: response.statusText,
headers
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using await response.arrayBuffer() buffers the entire response body in memory. For large 429 or 5xx responses (such as custom HTML error pages from proxies or CDN gateways), this can lead to unnecessary memory overhead and latency.

We can optimize this by passing response.body (which is a ReadableStream) directly to the Response constructor. This avoids buffering the body in memory and allows the function to be synchronous.

Suggested change
async function responseWithRetryAfter(
response: Response,
retryAfter: string
): Promise<Response> {
const headers = new Headers(response.headers);
headers.set("Retry-After", retryAfter);
return new Response(await response.arrayBuffer(), {
status: response.status,
statusText: response.statusText,
headers
});
}
function responseWithRetryAfter(
response: Response,
retryAfter: string
): Response {
const headers = new Headers(response.headers);
headers.set("Retry-After", retryAfter);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
}

@khaliqgant khaliqgant Jul 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3c09d3d. The response reconstruction helper is now synchronous and forwards the response stream rather than buffering an arrayBuffer.

Comment on lines +280 to +288
function isDirectWriteAdmissionRequest(input: RequestInfo | URL, init?: RequestInit): boolean {
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
const url = input instanceof Request ? input.url : String(input);
try {
return method === "PUT" && new URL(url).pathname.endsWith("/fs/file");
} catch {
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If input is a relative URL string (e.g., "/v1/workspaces/rw_7ccfea89/fs/file"), new URL(url) will throw a TypeError: Invalid URL because it lacks a base URL. This will cause the catch block to execute and return false, failing to recognize valid direct write admission requests.

To make this robust against relative URLs, we should provide a dummy base URL to the URL constructor. If the input URL is absolute, the base URL is ignored; if it is relative, it resolves correctly without throwing.

Suggested change
function isDirectWriteAdmissionRequest(input: RequestInfo | URL, init?: RequestInit): boolean {
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
const url = input instanceof Request ? input.url : String(input);
try {
return method === "PUT" && new URL(url).pathname.endsWith("/fs/file");
} catch {
return false;
}
}
function isDirectWriteAdmissionRequest(input: RequestInfo | URL, init?: RequestInit): boolean {
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
const url = input instanceof Request ? input.url : String(input);
try {
return method === "PUT" && new URL(url, "http://dummy.com").pathname.endsWith("/fs/file");
} catch {
return false;
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3c09d3d. Relative request URLs now resolve against a non-routable parsing-only base; absolute URLs remain unchanged.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22d47b1d61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/vfs-client/index.ts Outdated
Comment on lines +538 to +542
const timeoutMs = client.writebackTimeoutMs ?? DEFAULT_WRITEBACK_TIMEOUT_MS;
const controller = timeoutMs > 0 ? new AbortController() : undefined;
const deadlineTimer = controller
? setTimeout(() => controller.abort(), timeoutMs)
: 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.

P2 Badge Keep default admission deadline above Retry-After

When direct callers leave writebackTimeoutMs unset, timeoutMs is the 3s receipt default, but this same signal is now passed into the SDK retry sleep. For a workspace_busy admission response with Retry-After: 5, the controller aborts at 3s and throws RelayfileWritebackAdmissionTimeoutError before the SDK can make the second attempt, so default clients still fail instead of honoring the advertised retry. Use a separate admission deadline/default that can cover the intended 30s admission cap, or do not apply the receipt timeout to admission backoff.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b4cad79. Implicit defaults are now split: receipt polling remains 3 seconds, while direct admission is bounded at 90 seconds so the SDK can honor three advertised delays up to 30 seconds. An explicit writebackTimeoutMs still governs both and zero remains unbounded/no receipt wait. Red-first regressions prove an omitted timeout survives a 5-second delay to attempt 2 and that repeated 30-second delays abort at 90 seconds with no orphan fourth attempt.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/core/tests/vfs-client/vfs-client.test.ts (1)

385-424: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Second assertion doesn't actually verify retry-timer cancellation.

After the rejection, the test waits only 40ms of real time (line 422) before re-checking attempts === 1, but the advertised Retry-After delay is 30s (retryAfterSeconds: 30, line 394). Since 40ms is nowhere near 30,000ms, this assertion would pass identically whether or not the SDK actually canceled its scheduled retry timer — it only proves the retry hasn't fired yet, not that it was canceled. This is exactly the guarantee the test's own comment claims to verify ("the SDK retry timer must be canceled"), so it currently gives false confidence for a resilience-critical behavior (avoiding orphan/duplicate write attempts after the caller gives up).

Consider reusing the withImmediateTimeouts pattern already in this file to capture/cancel the scheduled retry timer deterministically (e.g., track clearTimeout calls on the id returned for the retry's setTimeout), rather than relying on a short real-time wait against a 30s schedule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/tests/vfs-client/vfs-client.test.ts` around lines 385 - 424,
Strengthen the retry-cancellation test around writeJsonFile by reusing the
file’s withImmediateTimeouts pattern to control the scheduled retry timer
deterministically. Capture the timer returned for the advertised retry and
assert that the client deadline invokes clearTimeout for that timer, replacing
the ineffective 40ms real-time wait while preserving the single-attempt
assertion.
packages/core/src/vfs-client/index.ts (1)

538-584: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Admission wait and receipt wait are independently bounded by the same writebackTimeoutMs, so combined worst-case latency can reach ~2x the configured timeout.

The new AbortController deadline here (lines 539-542) bounds only the admission phase. waitForOperationReceipt (unchanged, lines 475-528) computes its own separate Date.now() + timeoutMs deadline using the same client.writebackTimeoutMs, and doesn't receive controller.signal. A caller setting writebackTimeoutMs: 5000 expecting a 5s overall bound could see up to ~10s total (5s admission wait + 5s receipt wait) in the worst case. Consider sharing a single deadline (e.g., pass the already-computed absolute deadline or controller.signal/remaining budget into waitForOperationReceipt) so the total bound matches caller expectations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/vfs-client/index.ts` around lines 538 - 584, Use one
overall writeback deadline across both admission and receipt waiting: update the
flow around the admission AbortController and waitForOperationReceipt so the
receipt wait receives the same absolute deadline or remaining timeout budget
instead of starting a fresh writebackTimeoutMs window. Preserve the existing
timeout/error behavior while ensuring combined admission plus receipt latency
cannot exceed the configured timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/vfs-client/index.ts`:
- Around line 241-317: Update directRetryFetch to also clamp retryable 429/5xx
responses when Retry-After is absent, preserving the workspace-busy admission
exception. Ensure responses without the header are rewritten with the SDK legacy
2-second retry limit so getOp polling and unrelated write failures retain the
intended cap.

---

Nitpick comments:
In `@packages/core/src/vfs-client/index.ts`:
- Around line 538-584: Use one overall writeback deadline across both admission
and receipt waiting: update the flow around the admission AbortController and
waitForOperationReceipt so the receipt wait receives the same absolute deadline
or remaining timeout budget instead of starting a fresh writebackTimeoutMs
window. Preserve the existing timeout/error behavior while ensuring combined
admission plus receipt latency cannot exceed the configured timeout.

In `@packages/core/tests/vfs-client/vfs-client.test.ts`:
- Around line 385-424: Strengthen the retry-cancellation test around
writeJsonFile by reusing the file’s withImmediateTimeouts pattern to control the
scheduled retry timer deterministically. Capture the timer returned for the
advertised retry and assert that the client deadline invokes clearTimeout for
that timer, replacing the ineffective 40ms real-time wait while preserving the
single-attempt assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b9fce26-761d-4689-8c01-17171c864134

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2a505 and 22d47b1.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/core/src/vfs-client/index.ts
  • packages/core/tests/vfs-client/vfs-client.test.ts

Comment thread packages/core/src/vfs-client/index.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/src/vfs-client/index.ts">

<violation number="1" location="packages/core/src/vfs-client/index.ts:232">
P2: Three 30s `Retry-After` delays cannot reach the promised fourth request: the 90s deadline is armed before attempt one and fires when the third retry becomes due. Allow time for the final attempt (or explicitly clamp/rework the final retry) so a valid max-delay sequence is not aborted after only three attempts.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

const DEFAULT_WRITEBACK_TIMEOUT_MS = 3_000;
const SDK_LEGACY_RETRY_MAX_DELAY_MS = 2_000;
const WORKSPACE_BUSY_RETRY_MAX_DELAY_MS = 30_000;
const DEFAULT_WRITEBACK_ADMISSION_TIMEOUT_MS = WORKSPACE_BUSY_RETRY_MAX_DELAY_MS * 3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Three 30s Retry-After delays cannot reach the promised fourth request: the 90s deadline is armed before attempt one and fires when the third retry becomes due. Allow time for the final attempt (or explicitly clamp/rework the final retry) so a valid max-delay sequence is not aborted after only three attempts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/vfs-client/index.ts, line 232:

<comment>Three 30s `Retry-After` delays cannot reach the promised fourth request: the 90s deadline is armed before attempt one and fires when the third retry becomes due. Allow time for the final attempt (or explicitly clamp/rework the final retry) so a valid max-delay sequence is not aborted after only three attempts.</comment>

<file context>
@@ -228,6 +229,7 @@ export interface WritebackResult {
 const DEFAULT_WRITEBACK_TIMEOUT_MS = 3_000;
 const SDK_LEGACY_RETRY_MAX_DELAY_MS = 2_000;
 const WORKSPACE_BUSY_RETRY_MAX_DELAY_MS = 30_000;
+const DEFAULT_WRITEBACK_ADMISSION_TIMEOUT_MS = WORKSPACE_BUSY_RETRY_MAX_DELAY_MS * 3;
 
 function isRecord(value: unknown): value is Record<string, unknown> {
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed against 495b329. This boundary is intentional: the 90s admission deadline is installed before attempt 1 and must win at t+90 when all three advertised delays equal the 30s safety maximum. The red regression pins attempts=3 plus cancellation of the pending fourth attempt, so no retry survives the caller deadline. Four total attempts remain proven for advertised delays that fit inside the deadline (t+0/5/10/15). Allowing the t+90 request would exceed the client bound and conflict with the daily-ship 90s deadline. The PR timeline documents this pathological boundary explicitly.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The parent comment was wrong here: the 90s admission deadline is intentional and documented, and it’s meant to win at t+90. The PR also proves the four-attempt path for delays that stay within the deadline, so the missing t+90 attempt is expected behavior.

@khaliqgant

Copy link
Copy Markdown
Member Author

Bot triage on final hardening: (1) CodeRabbit cancellation nitpick was valid and is fixed by deterministic retry-handle capture plus clearTimeout proof; the 40ms real-time wait is gone. (2) Independent admission and receipt bounds are intentional compatibility semantics, now explicit in the option docs and CHANGELOG; an explicit value bounds each phase, while omitted defaults are 90s admission / 3s receipt. (3) Bare retryables remain on the SDK 100/200/400ms schedule, pinned with jitter disabled, so the raised 30s ceiling cannot change their timing. (4) Cubic t+90 concern is the intentionally tested boundary: the client deadline wins after three maximal intervals and cancels the pending fourth attempt; four attempts are still proven when advertised delays fit inside the deadline. Current head 554f2f3 also states that boundary directly in the public option docs.

@khaliqgant
khaliqgant merged commit ec0f058 into main Jul 14, 2026
3 checks passed
@khaliqgant
khaliqgant deleted the fix/workspace-busy-admission-retry branch July 14, 2026 08:12
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