jackwener/OpenCLI · error · CommandExecutionError

Substack post search failed: HTTP ${resp.status}

Error message

Substack post search failed: HTTP ${resp.status}

What it means

searchPosts calls the Substack post search API (substack.com/api/v1/post/search). If the response is not ok, it throws a CommandExecutionError with the HTTP status, since the search could not complete.

Source

Thrown at clis/substack/search.js:26

}
function trim(value) {
    return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
}
function publicationBaseUrl(publication) {
    if (publication?.custom_domain)
        return `https://${publication.custom_domain}`;
    if (publication?.subdomain)
        return `https://${publication.subdomain}.substack.com`;
    return '';
}
async function searchPosts(keyword, limit) {
    const url = new URL('https://substack.com/api/v1/post/search');
    url.searchParams.set('query', keyword);
    url.searchParams.set('page', '0');
    url.searchParams.set('includePlatformResults', 'true');
    const resp = await fetch(url, { headers: headers() });
    if (!resp.ok)
        throw new CommandExecutionError(`Substack post search failed: HTTP ${resp.status}`);
    const data = await resp.json();
    const results = Array.isArray(data?.results) ? data.results : [];
    return results.slice(0, limit).map((item, index) => ({
        rank: index + 1,
        title: trim(item?.title),
        author: trim(item?.publishedBylines?.[0]?.name),
        date: trim(item?.post_date).split('T')[0] || trim(item?.post_date),
        description: trim(item?.description || item?.subtitle || item?.truncated_body_text).slice(0, 150),
        url: trim(item?.canonical_url),
    }));
}
async function searchPublications(keyword, limit) {
    const url = new URL('https://substack.com/api/v1/profile/search');
    url.searchParams.set('query', keyword);
    url.searchParams.set('page', '0');
    const resp = await fetch(url, { headers: headers() });
    if (!resp.ok)
        throw new CommandExecutionError(`Substack publication search failed: HTTP ${resp.status}`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check the HTTP status in the message and retry with backoff for 429/5xx
  2. Use an authenticated browser session so headers() carries valid cookies
  3. Slow down request rate and add delays between searches
  4. Verify the endpoint still exists if you get repeated 404s after a Substack update

Example fix

// before
const posts = await searchPosts('rust', 10); // throws on 429
// after
let posts;
try { posts = await searchPosts('rust', 10); }
catch (e) { console.warn(e.message); await sleep(5000); posts = await searchPosts('rust', 10); }
Defensive patterns

Strategy: retry

Validate before calling

// ensure a browser session exists so headers() carries valid cookies
if (!session || !session.cookies) throw new Error('run within a browser session for substack search');

Type guard

null

Try / catch

try { posts = await searchPosts(q, 10); } catch (e) {
  const m = /HTTP (\d+)/.exec(e.message);
  if (m && ['429','502','503'].includes(m[1])) { await sleep(5000); posts = await searchPosts(q, 10); } else throw e;
}

Prevention

When it happens

Trigger: Any searchPosts call where substack.com returns a non-2xx status: rate limiting (429), auth/cookie rejection (401/403), server errors (5xx), or 404 when the endpoint changes.

Common situations: Heavy scraping triggering Substack rate limits, missing or expired browser session cookies expected by headers(), Substack API changes breaking the endpoint, datacenter IPs blocked by bot protection.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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