jackwener/OpenCLI · error · CommandExecutionError

${label} returned HTTP ${resp.status}

Error message

${label} returned HTTP ${resp.status}

What it means

s2Fetch in clis/semanticscholar/utils.js wraps every Semantic Scholar API call. After handling 404 and 429 specially, any other non-2xx status falls through to this generic `CommandExecutionError` with the raw HTTP status code in the message. It signals the Semantic Scholar graph/recommendations API rejected the request for a reason not otherwise classified (e.g. 400 bad field list, 403 forbidden key, 5xx server outage).

Source

Thrown at clis/semanticscholar/utils.js:122

        if (resp.status === 429 && attempt === 0 && !apiKey) {
            attempt += 1;
            await new Promise(resolve => setTimeout(resolve, 1500));
            continue;
        }
        break;
    }

    if (resp.status === 404) {
        throw new EmptyResultError(label, `Semantic Scholar returned 404 for ${url}.`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Semantic Scholar throttles anonymous traffic; set SEMANTIC_SCHOLAR_API_KEY (free at https://www.semanticscholar.org/product/api) or wait a minute and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let body;
    try {
        body = await resp.json();
    } catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    if (body && typeof body === 'object' && body.error) {
        throw new CommandExecutionError(`${label} returned an error: ${body.error}`);
    }
    return body;
}

/** Return the AI-generated one-line summary if present, else ''. */
export function tldrText(tldr) {
    if (tldr && typeof tldr === 'object' && typeof tldr.text === 'string') {
        return tldr.text.trim();
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log or inspect resp.status in the message to identify the specific failure code.
  2. For 400: check the paper ref/fields in the URL; validate refs with requirePaperRef semantics (paperId, DOI, arXiv, or prefixed id).
  3. For 403: verify SEMANTIC_SCHOLAR_API_KEY is valid and not expired; request a free key at https://www.semanticscholar.org/product/api.
  4. For 5xx: wait and retry; check Semantic Scholar status; add backoff/retry around the command.
  5. Confirm network/proxy access to api.semanticscholar.org.

Example fix

// before (no key, hitting 403 on authenticated-only endpoint)
await s2Fetch(`${S2_GRAPH_BASE}/paper/${ref}?fields=title`, 'paper');
// after (attach key and handle non-ok status explicitly)
process.env.SEMANTIC_SCHOLAR_API_KEY = 'your-key';
try {
  await s2Fetch(`${S2_GRAPH_BASE}/paper/${ref}?fields=title`, 'paper');
} catch (e) {
  if (/HTTP 403/.test(e.message)) console.error('Check SEMANTIC_SCHOLAR_API_KEY');
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate ref and key presence before calling
if (!/^(ARXIV|MAG|ACL|PMID|PMCID|URL|CorpusId|DBLP):|^10\.|^[0-9a-f]{40}$/i.test(ref) && !/^\d{4}\.\d{4,5}/.test(ref)) {
  throw new Error(`Unrecognised paper ref: ${ref}`);
}
const hasKey = Boolean(process.env.SEMANTIC_SCHOLAR_API_KEY);

Type guard

function isS2Ref(ref) {
  return typeof ref === 'string' && (/^[0-9a-f]{40}$/i.test(ref.trim()) || /^(ARXIV|MAG|ACL|PMID|PMCID|URL|CorpusId|DBLP):/i.test(ref.trim()) || /^10\./.test(ref.trim()) || /^\d{4}\.\d{4,5}(v\d+)?$/.test(ref.trim()));
}

Try / catch

try {
  const data = await s2Fetch(url, 'paper');
} catch (err) {
  const m = /HTTP (\d{3})/.exec(err.message);
  if (m && +m[1] >= 500) return retryWithBackoff(() => s2Fetch(url, 'paper'), 3);
  if (m && +m[1] === 403) console.error('Check SEMANTIC_SCHOLAR_API_KEY');
  throw err;
}

Prevention

When it happens

Trigger: Any s2Fetch call where api.semanticscholar.org responds with a status other than 404 or 429 and !resp.ok: malformed query params rejected with 400, invalid/expired SEMANTIC_SCHOLAR_API_KEY yielding 403, or upstream 500/502/503 outages.

Common situations: Requesting a field the API doesn't expose (400), a revoked API key (403), or transient Semantic Scholar server errors during partial outages; also hits when an unauthenticated client is IP-blocked (403) rather than merely throttled.

Related errors


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