heygen-com/hyperframes · error

${failureStage}${attemptDetail}: ${describeFetchFailure(last

Error message

${failureStage}${attemptDetail}: ${describeFetchFailure(lastError)}

What it means

fetchForPublish is the shared retry wrapper for all publish-network calls. It retries on generic fetch exceptions (network/DNS/SSL) up to `attempts` times (default 1; staged upload paths use PUBLISH_TRANSPORT_ATTEMPTS=2), but breaks immediately on a request timeout/abort. When every attempt throws (the fetch never returned a Response at all), it throws `${failureStage}${attemptDetail}: ${describeFetchFailure(lastError)}` with the original error on the `.cause`. This is transport-level — distinct from a server HTTP error response (those are [257]/[258]/[259]).

Source

Thrown at packages/cli/src/utils/publishProject.ts:243

  createInit: () => RequestInit,
  failureStage: string,
  attempts = 1,
): Promise<Response> {
  if (attempts < 1) throw new RangeError("Publish fetch attempts must be at least 1");
  let lastError: unknown;
  let attemptsMade = 0;
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    attemptsMade = attempt;
    try {
      return await fetch(input, createInit());
    } catch (error) {
      lastError = error;
      if (isRequestTimeout(error) || attempt === attempts) break;
      await waitBeforePublishRetry();
    }
  }
  const attemptDetail = attemptsMade > 1 ? ` after ${attemptsMade} attempts` : "";
  throw new Error(`${failureStage}${attemptDetail}: ${describeFetchFailure(lastError)}`, {
    cause: lastError instanceof Error ? lastError : undefined,
  });
}

export function uploadTimeoutMs(byteLength: number): number {
  return Math.max(
    PUBLISH_UPLOAD_MIN_TIMEOUT_MS,
    Math.ceil((byteLength / PUBLISH_UPLOAD_BYTES_PER_SECOND) * 1000),
  );
}

function shouldIgnoreSegment(segment: string): boolean {
  return segment.startsWith(".") || IGNORED_DIRS.has(segment) || IGNORED_FILES.has(segment);
}

function createProjectIgnore(rootDir: string): Ignore {
  const matcher = ignore().add(DEFAULT_PROJECT_IGNORE);
  const ignorePath = join(rootDir, HYPERFRAMES_IGNORE_FILE);

View on GitHub (pinned to c2996c8626)

Solutions

  1. If a proxy is set, retry with NODE_USE_ENV_PROXY=1 (Node 22.21+) — the error appends this hint when relevant.
  2. Verify network connectivity to the API host: curl -I <api-base-url>.
  3. For transient issues, simply retry the publish command (the wrapper already retries twice for staged uploads).
  4. Check DNS resolution and TLS trust store on the host.
  5. For slow networks, the upload timeout is auto-scaled by archive size (uploadTimeoutMs); ensure the archive is not unexpectedly huge.

Example fix

# before: proxy set but ignored by Node fetch
export HTTPS_PROXY=http://corp-proxy:3128
hyperframes publish .
# after
export NODE_USE_ENV_PROXY=1
hyperframes publish .
Defensive patterns

Strategy: retry

Validate before calling

import { isRequestTimeout } from './publishProject'; // or replicate

async function pingApi(apiBaseUrl: string): Promise<void> {
  try {
    const r = await fetch(`${apiBaseUrl}/v1/hyperframes/projects`, { signal: AbortSignal.timeout(5000) });
    if (!r.ok && r.status >= 500) throw new Error('API unhealthy');
  } catch (e) {
    throw new Error(`Cannot reach publish API at ${apiBaseUrl}: ${(e as Error).message}`);
  }
}

Try / catch

try {
  return await publishProjectArchive(projectDir, opts);
} catch (err) {
  if (err instanceof Error && /after \d+ attempts:/.test(err.message)) {
    // transport-level: retry with backoff, or surface proxy hint
    if (process.env.HTTPS_PROXY && process.env.NODE_USE_ENV_PROXY !== '1') {
      console.error('Set NODE_USE_ENV_PROXY=1 and retry (Node fetch ignores proxy vars otherwise).');
    }
    await new Promise(r => setTimeout(r, 1000));
    return publishProjectArchive(projectDir, opts);
  }
  throw err;
}

Prevention

When it happens

Trigger: DNS resolution fails for the API host; the TCP connection is refused (API down); TLS handshake fails; the network is unreachable; a timeout fires (AbortSignal.timeout or DOMException TimeoutError) on the first attempt (immediate break, no retry); a proxy is configured via HTTPS_PROXY but Node fetch ignores it without NODE_USE_ENV_PROXY=1 (the error appends a proxySupportHint).

Common situations: Corporate proxy environment where HTTPS_PROXY is set but Node 22's fetch does not honor it without NODE_USE_ENV_PROXY=1; offline or flaky network; the publish API host is temporarily unreachable; a self-signed cert or corporate MITM breaking TLS; slow upload exceeding uploadTimeoutMs on every retry.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/e1ebd0f8cb3640fc. Report an issue: GitHub.