jackwener/OpenCLI · error · CommandExecutionError

Gmail ${operation} returned HTTP ${status || 'unknown'}

Error message

Gmail ${operation} returned HTTP ${status || 'unknown'}

What it means

For any captured Gmail response status that is not 200 (and not 401/403, which raise AuthRequiredError), parseJsonCapture throws CommandExecutionError. A missing/zero status (no capture at all) surfaces as 'HTTP unknown'. This signals the Gmail request itself failed at the HTTP level.

Source

Thrown at clis/gmail/utils.js:97

  return decodeEntities(String(value || '')
    .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
    .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
    .replace(/<br\s*\/?\s*>/gi, '\n')
    .replace(/<\/(p|div|li|tr|h[1-6])>/gi, '\n')
    .replace(/<[^>]+>/g, ' '))
    .replace(/[ \t]+/g, ' ')
    .replace(/ *\n */g, '\n')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function parseJsonCapture(entry, operation) {
  const status = Number(entry?.responseStatus || 0);
  if (status === 401 || status === 403) {
    throw new AuthRequiredError(GMAIL_HOST, `Gmail ${operation} returned HTTP ${status}`);
  }
  if (status !== 200) {
    throw new CommandExecutionError(`Gmail ${operation} returned HTTP ${status || 'unknown'}`);
  }
  if (entry?.responseBodyTruncated === true) {
    throw new CommandExecutionError(`Gmail ${operation} response exceeded the browser capture limit`);
  }
  const body = entry?.responsePreview;
  if (Array.isArray(body)) return body;
  if (typeof body !== 'string') {
    throw new CommandExecutionError(`Gmail ${operation} response body was unavailable`);
  }
  try {
    const parsed = JSON.parse(body.replace(/^\)\]\}'\s*/, ''));
    if (!Array.isArray(parsed)) throw new Error('not an array');
    return parsed;
  } catch {
    throw new CommandExecutionError(`Gmail ${operation} returned malformed JSON`);
  }
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry after a short backoff, especially if the status was 429 or 5xx.
  2. Reduce request frequency / lower the limit to avoid rate limiting.
  3. If status is 'unknown', check that the page stays open and the request isn't aborted by navigation; re-run the command.
  4. Inspect responsePreview/entry details in the capture log to identify the failing endpoint and status.

Example fix

// before
for (const q of queries) await search(q); // bursts -> HTTP 429
// after
for (const q of queries) {
  await search(q);
  await sleep(2000); // backoff between requests
}
Defensive patterns

Strategy: retry

Validate before calling

// No local pre-check possible; validate captures before parsing:
if (!entry || !Number(entry.responseStatus)) {
  throw new Error('capture has no HTTP status — request may have been aborted');
}

Try / catch

async function withRetry(fn, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await fn(); }
    catch (e) {
      const m = /HTTP (\d+|unknown)/.exec(e.message);
      const status = m && /^\d+$/.test(m[1]) ? Number(m[1]) : 0;
      const retryable = status === 429 || status >= 500 || status === 0;
      if (!retryable || i === attempts - 1) throw e;
      await sleep(2 ** i * 1000);
    }
  }
}

Prevention

When it happens

Trigger: Gmail returning 4xx/5xx (429 rate limit, 500 server error) for the captured operation, or the capture entry having no responseStatus so status coerces to 0 ('unknown') — e.g. request aborted before a response arrived.

Common situations: Hammering Gmail with many rapid queries and hitting 429; transient Google 5xx outages; navigation/page reload aborting the request so no status is captured; network interruptions mid-fetch.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/e670aedacb63380f. Report an issue: GitHub.