jackwener/OpenCLI · error · CommandExecutionError

returned HTTP

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).

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.

Appendix: 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)