Skip to content

fix(boxel-cli): Extract fetch error.cause on publish/unpublish failures#4925

Merged
backspace merged 3 commits into
mainfrom
boxel-cli-surface-fetch-error-cause
May 21, 2026
Merged

fix(boxel-cli): Extract fetch error.cause on publish/unpublish failures#4925
backspace merged 3 commits into
mainfrom
boxel-cli-surface-fetch-error-cause

Conversation

@backspace
Copy link
Copy Markdown
Contributor

@backspace backspace commented May 21, 2026

While working on #4897, there were unhelpful fetch failed errors like here when publishing wasn’t working. This extracts more detail from error.cause to help clarify failures.

Node's fetch always reports `TypeError: fetch failed` as `error.message`;
the actual transport reason (ECONNRESET, TLS handshake error, undici
socket error, ENOTFOUND, GOAWAY, etc.) is stashed on `error.cause` and
was being silently dropped by the publish/unpublish error paths. That
left the action-demo workflow showing a bare "Error: fetch failed" with
no way to distinguish a real network issue from, say, a self-signed
cert problem against the published-realm subdomain.

Wrap the three swallowed sites:

- `publish.ts` `.action()` catch: log `err.cause` separately if present.
- `publish.ts` `waitForPublishedRealmReady`: capture cause into the
  `lastError` string so the readiness-timeout error reports the same
  thing the polling loop kept hitting.
- `unpublish.ts` `unpublishRealm`: embed cause into the `result.error`
  string the CLI surfaces.

This is the diagnostic the action-demo on #4897 needs to figure out
why publish hangs at the initial POST despite the server-side mount
completing successfully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@backspace backspace changed the title boxel-cli: surface fetch error.cause on publish/unpublish failures fix(boxel-cli): Extract fetch error.cause on publish/unpublish failures May 21, 2026
@backspace backspace requested a review from a team May 21, 2026 19:56
@habdelra habdelra requested a review from Copilot May 21, 2026 19:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics in @cardstack/boxel-cli when realm publish/unpublish operations fail due to Node fetch() reporting only TypeError: fetch failed, by extracting and surfacing the underlying transport error stored on error.cause.

Changes:

  • Include error.cause details in readiness polling timeout errors during realm publish.
  • Log error.cause in the realm publish CLI error path to provide actionable transport failure details.
  • Inline error.cause details into the returned error string for realm unpublish failures.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/boxel-cli/src/commands/realm/publish.ts Adds a helper to summarize fetch errors (including error.cause) for readiness polling and logs causes on CLI failures.
packages/boxel-cli/src/commands/realm/unpublish.ts Enhances unpublish failure reporting by appending error.cause details to the returned error message.
Comments suppressed due to low confidence (1)

packages/boxel-cli/src/commands/realm/publish.ts:209

  • This change introduces new user-facing error formatting (including error.cause) for readiness polling timeouts, but there’s no automated coverage to prevent regressions in the output. Consider adding a unit test for describeFetchError() (and/or a small test that simulates a fetch error with an Error cause) so the thrown timeout message reliably includes the underlying transport detail.
      }
      lastError = `HTTP ${response.status}`;
    } catch (error) {
      lastError = describeFetchError(error);
    }
    let remaining = timeoutMs - (Date.now() - startedAt);
    if (remaining <= 0) break;
    await new Promise((r) =>
      setTimeout(r, Math.min(READINESS_POLL_INTERVAL_MS, remaining)),
    );
  }

  throw new Error(
    `Timed out after ${timeoutMs}ms waiting for ${publishedRealmURL} to pass readiness check${
      lastError ? `: ${lastError}` : ''
    }`,
  );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/boxel-cli/src/commands/realm/publish.ts Outdated
Comment thread packages/boxel-cli/src/commands/realm/unpublish.ts Outdated
Comment thread packages/boxel-cli/src/commands/realm/unpublish.ts Outdated
Comment thread packages/boxel-cli/src/commands/realm/publish.ts Outdated
backspace added 2 commits May 21, 2026 16:32
Per code review on #4925: `if (... && error.cause)` uses a truthy
test, so causes of `''`, `0`, `false`, `NaN` would be silently
dropped back to the shallow "fetch failed" output we're trying to
avoid. `!= null` matches both `null` and `undefined` — the two
absence markers — and lets every explicit cause value through.

Applied at all three sites in this PR:
- `publish.ts` `describeFetchError()`
- `publish.ts` `.action()` catch
- `unpublish.ts` `unpublishRealm()` catch
Per code review on #4925: the cause-flattening logic in `publish.ts`'s
local `describeFetchError` and `unpublish.ts`'s inline catch block was
byte-identical. Extract to `src/lib/describe-fetch-error.ts` so future
tweaks (walking the full cause chain, surfacing `cause.code`, etc.)
stay consistent across commands.

The pretty-printing path in `publish.ts`'s `.action()` catch (which
passes `err.cause` as a separate console.error arg so Node renders
the full nested Error including stack frames) intentionally stays
inline — it's a different shape from the "flatten to a string"
use the helper covers.

Tests: `tests/lib/describe-fetch-error.test.ts` covers all six
shapes — no cause, Error cause, non-Error cause, the four falsy
primitives (`''`, `0`, `false`, `NaN`) that motivated the `!= null`
check, and the null/undefined absence cases that should drop the
suffix entirely.
@backspace backspace merged commit e01a615 into main May 21, 2026
24 checks passed
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.

3 participants