jackwener/OpenCLI · error · CommandExecutionError

semanticscholar recommendations returned an unexpected…

Error message

semanticscholar recommendations returned an unexpected payload shape

What it means

The recommendations command (clis/semanticscholar/recommendations.js:39) calls the `/recommendations/v1/papers/forpaper` endpoint and expects a `recommendedPapers` array in the response. If that key is missing or not an array, this CommandExecutionError is thrown because the recommendation payload contract was violated.

Solutions

  1. Print the raw response body to see what replaced recommendedPapers.
  2. Retry with backoff — the recommendations endpoint throttles aggressively and often returns soft errors with 200.
  3. Confirm the input paper is valid via `semanticscholar paper <id>` first.
  4. Check the Semantic Scholar recommendations API docs for endpoint/shape changes (it is versioned separately from the graph API).
  5. Rule out proxies/caches rewriting the response.

Example fix

// before
const body = await s2Fetch(url, 'semanticscholar recommendations');
const recommended = body?.recommendedPapers ?? [];
// after
const body = await s2Fetch(url, 'semanticscholar recommendations');
if (body && typeof body === 'object' && body.error) {
  throw new Error(`S2 recommendations soft error: ${body.error} — backoff and retry`);
}
const recommended = Array.isArray(body?.recommendedPapers) ? body.recommendedPapers : [];
Defensive patterns

Strategy: type-guard

Validate before calling

function hasRecommendationsEnvelope(body) {
  return body !== null && typeof body === 'object' && Array.isArray(body.recommendedPapers);
}

Type guard

function isRecommendationsPayload(body) {
  return typeof body === 'object' && body !== null && Array.isArray(body.recommendedPapers)
    && body.recommendedPapers.every((p) => p && typeof p === 'object' && 'paperId' in p);
}

Try / catch

try {
  recs = await recommendations(id);
} catch (err) {
  if (/unexpected payload shape/.test(err.message)) {
    // recommendations endpoint throttles hard: exponential backoff
    await sleep(5000);
    recs = await recommendations(id);
  } else throw err;
}

Prevention

When it happens

Trigger: The recommendations endpoint returned 200 with JSON lacking recommendedPapers — e.g. a throttling/soft-error envelope, an error object for an unknown paperId, or an API schema change in the recommendations service.

Common situations: Semantic Scholar rate-limiting the recommendations endpoint (which is stricter than the graph API); recommending from a paper id the service cannot resolve; a proxy or cache substituting its own JSON body; recommendations API version changes.

Related errors


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

Appendix: source

Thrown at clis/semanticscholar/recommendations.js:39

    access: 'read',
    description: 'Semantic Scholar AI-curated related papers for a paperId, DOI, or arXiv id',
    domain: 'api.semanticscholar.org',
    strategy: Strategy.PUBLIC,
    browser: false,
    args: [
        { name: 'id', positional: true, required: true, help: 'paperId (40-char hex), DOI, arXiv id, or prefixed id' },
        { name: 'limit', type: 'int', default: 10, help: 'Max recommendations (1-500)' },
    ],
    columns: ['rank', 'paperId', 'doi', 'title', 'year', 'firstAuthor', 'citationCount', 'url'],
    func: async (args) => {
        const ref = requirePaperRef(args.id);
        const limit = requireBoundedInt(args.limit, 10, 500);
        const url = `${S2_REC_BASE}/papers/forpaper/${encodeURIComponent(ref)}?fields=${FIELDS}&limit=${limit}`;
        const body = await s2Fetch(url, 'semanticscholar recommendations');

        const recommended = Array.isArray(body?.recommendedPapers) ? body.recommendedPapers : null;
        if (recommended === null) {
            throw new CommandExecutionError('semanticscholar recommendations returned an unexpected payload shape');
        }
        if (!recommended.length) {
            throw new EmptyResultError('semanticscholar recommendations', `No Semantic Scholar recommendations for "${args.id}".`);
        }

        return recommended.slice(0, limit).map((p, i) => normalizePaperRow(p, 'recommendations', { rank: i + 1 }));
    },
});

View on GitHub (pinned to 49907e53dc)