jackwener/OpenCLI · error · CommandExecutionError

medium tag returned HTTP ${resp.status}

Error message

medium tag returned HTTP ${resp.status}

What it means

After the request succeeds and is not a 404, any other non-OK HTTP status (403 rate-limit/bot-block, 5xx server errors, redirects mishandled) triggers CommandExecutionError 'medium tag returned HTTP <status>'. It reports the raw status so the caller can diagnose server-side or anti-bot rejections.

Source

Thrown at clis/medium/tag.js:113

        try {
            resp = await fetch(url, {
                headers: {
                    'user-agent': 'opencli-medium-adapter (+https://github.com/jackwener/opencli)',
                    accept: 'application/rss+xml, application/xml',
                },
            });
        }
        catch (err) {
            throw new CommandExecutionError(
                `medium tag request failed: ${err?.message ?? err}`,
                'Check that medium.com is reachable from this network.',
            );
        }
        if (resp.status === 404) {
            throw new EmptyResultError('medium tag', `Medium tag "${tag}" does not exist.`);
        }
        if (!resp.ok) {
            throw new CommandExecutionError(`medium tag returned HTTP ${resp.status}`);
        }
        const xml = await resp.text();
        const items = [];
        const re = /<item[^>]*>([\s\S]*?)<\/item>/g;
        let m;
        while ((m = re.exec(xml)) !== null) {
            items.push(m[1]);
        }
        if (!items.length) {
            throw new EmptyResultError('medium tag', `Medium tag "${tag}" RSS feed has no items.`);
        }
        return items.slice(0, limit).map((block, i) => ({
            rank: i + 1,
            title: decodeHtml(extractTag(block, 'title')).trim(),
            author: decodeHtml(extractTag(block, 'dc:creator')).trim(),
            description: stripHtml(extractTag(block, 'description')),
            categories: extractCategories(block).join(', '),
            published: isoDateFromRfc822(decodeHtml(extractTag(block, 'pubDate')).trim()),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the status in the message: 403/429 usually means bot mitigation or rate limiting — back off and retry with delays.
  2. Retry 5xx statuses after a short wait; they are typically transient.
  3. Try from a different network/IP (residential) if 403 persists.
  4. Check Medium's status page during apparent outages.

Example fix

// before (tight loop triggers 429)
for (const t of tags) await mediumTag(t);
// after
for (const t of tags) {
  await mediumTag(t);
  await new Promise(r => setTimeout(r, 2000));
}
Defensive patterns

Strategy: retry

Validate before calling

// optional preflight of the endpoint's health
const res = await fetch('https://medium.com/feed/tag/programming');
if (!res.ok && res.status !== 404) throw new Error(`Medium unhealthy: HTTP ${res.status}`);

Try / catch

try {
  await run(['medium', 'tag', tag]);
} catch (e) {
  const m = /returned HTTP (\d+)/.exec(e.message);
  if (m && (m[1].startsWith('5') || m[1] === '429')) {
    await backoffRetry(() => run(['medium', 'tag', tag]), 3);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `medium tag <tag>` when Medium responds 403 (Cloudflare/bot mitigation), 429 (rate limited after rapid repeated calls), or 500/502/503 (Medium server issues).

Common situations: Hammering the feed endpoint in a loop from CI; requests from datacenter IPs flagged by bot protection; Medium incidents producing 5xx responses.

Related errors


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