Skip to content

fix(cli): surface sdk-gen-api build timeouts before the client poll deadline - #18362

Open
zamn wants to merge 2 commits into
mainfrom
fix/sdk-gen-api-poll-timeout
Open

zamn wants to merge 2 commits into
mainfrom
fix/sdk-gen-api-poll-timeout

Conversation

@zamn

@zamn zamn commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: n/a

Part of hardening "stuck builds look like a hang" on the sdk-gen-api remote generation path. A companion sdk-gen-api PR makes server-side build timeouts return a target error { code: "FERN_BUILD_TIMEOUT", message, retryable? }. This PR makes the CLI wait long enough to receive that error and show its code.

Changes Made

  • POLL_TIMEOUT_MS goes from 15 to 18 minutes. The old value matched sdk-gen-api's 15-minute build timeout, so the CLI gave up at the same moment the server would have reported the real failure.
  • When the deadline passes, the CLI polls the build status exactly once more. If every target is finished (same check as in the loop), it completes through the normal path. If any target is still running, it fails with Timed out waiting for sdk-gen-api build <id> after 18 minutes and code CliError.Code.InternalError instead of NetworkError.
  • Target errors now keep error.code. Before this change, sanitizeFernBuildStatus dropped it. A failed target now shows <code>: <message>, then falls back to just the message, then to sdk-gen-api generation failed.
  • Real submit and poll request failures still use NetworkError.
  • Added the unreleased changelog entry packages/cli/cli/changes/unreleased/sdk-gen-api-poll-timeout.yml (fix).

Instructions on how to review this PR

  1. Deadline and final poll (executeFernSdkGenApiBuild in packages/cli/generation/remote-generation/remote-workspace-runner/src/fernSdkGenApi.ts). Review this closely. The deadline check moved from after the GET to the top of each iteration (isFinalPoll). This guarantees that the last GET starts at or after the deadline. That GET still goes through the missing-target check, log flushing, and the terminal check. We fail only if it is not terminal. I chose this over a separate post-loop GET helper because it does not duplicate the request, sanitizing, and log-handling code, and it does not send two GETs back to back. Things to check:
    • No extra GET runs while the deadline has not passed. Each isFinalPoll iteration always returns, so there is never more than one GET after the deadline.
    • Request failures on that final GET still go through the existing NetworkError catch.
  2. Error code choice (same block). CliError.Code has no timeout-specific code. InternalError is the closest fit. With the 3-minute margin, hitting this path means sdk-gen-api did not enforce or report its own 15-minute timeout, which breaks the service contract. The same file already uses InternalError for other contract violations ("response did not contain target", "build ended with status X while target remained Y"). It is also reported to Sentry, which we want for a real stuck build. ContainerError is used for failures the server reports for generation or publication, and it is not reported to Sentry, so a hang that nobody explains would go unnoticed. If CLI-side Sentry noise for a backend fault is a concern, please say so.
  3. Error formatting (formatFernSdkGenApiTargetError and sanitizeFernBuildStatus). code is kept only when it is a string, and it is redacted the same way as message. An empty code falls back to the message alone. The FernBuildStatus type gains error.code?: string.
  4. Tests (src/__test__/fernSdkGenApi.test.ts, the two new describe blocks at the end of the file). Tests are a secondary part of this PR.
    • sdk-gen-api build status polling uses fake setTimeout/Date timers and moves time forward 18 minutes:
      • The poll after the deadline returns a finished target, and the build completes normally.
      • The poll after the deadline returns a running target, and the build fails with the timeout message and CliError.Code.InternalError.
      • Both tests assert that exactly one GET was sent at or after the deadline.
    • sdk-gen-api target failure message covers three cases: code with message, message only, and no error (default text).

Testing

  • Unit tests added/updated
  • pnpm turbo run compile --filter @fern-api/remote-workspace-runner passes (50/50 tasks)
  • pnpm turbo run test --filter @fern-api/remote-workspace-runner passes (39 files, 655 tests)
  • biome check --error-on-warnings on the changed files: clean. prettier --check on the changelog file: clean.
  • Manual testing against a live sdk-gen-api with a build that hangs

@zamn zamn self-assigned this Oct 16, 2026
@zamn
zamn marked this pull request as ready for review October 16, 2026 21:08

@nitpickybot nitpickybot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI Review Summary

Small, well-scoped change: bump the poll timeout to 18 minutes, move the deadline check to the top of the loop so the final GET actually runs after the deadline, keep the target error code through sanitization, and switch the timeout failure to InternalError. The logic reads correctly and the tests cover both terminal and non-terminal final polls. No blocking issues found.

  • 🔵 1 suggestion(s)

Review Scope

  • Mode: Full pull request
  • Range: Full pull request through 3979979063b6a9789a8edb54227930ff796449f3
  • Current range coverage: Complete
  • Cumulative pull request coverage: Complete

This review is complete for the current scope. Addressing all critical and warning findings above satisfies the review; suggestions are optional. Request another review only if subsequent changes introduce new behavior or materially expand the pull request's scope.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@zamn

zamn commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review

@nitpickybot nitpickybot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Incremental change swaps ?? for || in the target-error message fallback and adds a test for the empty-message case, exactly addressing the prior review's suggestion. No regressions spotted — the code check already guards against empty strings. Nothing further to flag.

Review Scope

  • Mode: Incremental
  • Range: 3979979063b6a9789a8edb54227930ff796449f3...eea6cbd6f0110bc6beb0641769e93f3c568bd60f
  • Current range coverage: Complete
  • Cumulative pull request coverage: Complete

This review is complete for the current scope. Addressing all critical and warning findings above satisfies the review; suggestions are optional. Request another review only if subsequent changes introduce new behavior or materially expand the pull request's scope.

This branch has not been deployed

No deployments
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