jackwener/OpenCLI · error · EmptyResultError

Story not found

Error message

Story not found

What it means

This EmptyResultError is thrown by fetchStory (invoked by the `lobsters read` command) when GET https://lobste.rs/s/<shortId>.json returns HTTP 404, meaning no story exists with that short id on Lobste.rs. It distinguishes 'unknown/deleted story id' from transport failures, which raise CommandExecutionError instead.

Source

Thrown at clis/lobsters/read.js:23

 *   https://lobste.rs/s/<short_id>.json
 * already returns the story plus a flat `comments[]` array where each entry
 * carries `parent_comment` (short_id of parent or null) and `depth` — so we
 * just need one HTTP call, then build a children map and DFS.
 *
 * Output rows:
 *   - first row is the story itself (`type=POST`)
 *   - each subsequent row is a comment, indented by depth (`L0`, `L1`, …)
 *   - `[+N more replies]` summary rows whenever depth/limit cuts in
 */
import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';

const LOBSTERS_STORY_BASE = 'https://lobste.rs/s';

async function fetchStory(shortId) {
    const res = await fetch(`${LOBSTERS_STORY_BASE}/${shortId}.json`);
    if (res.status === 404) {
        throw new EmptyResultError(`lobsters/${shortId}`, 'Story not found');
    }
    if (!res.ok) {
        throw new CommandExecutionError(`Lobsters API HTTP ${res.status} for story ${shortId}`, 'Check the short id');
    }
    return res.json();
}

function requirePositiveInt(value, label) {
    if (!Number.isInteger(value) || value <= 0) {
        throw new ArgumentError(`${label} must be a positive integer`);
    }
    return value;
}

function requireMinInt(value, min, label) {
    if (!Number.isInteger(value) || value < min) {
        throw new ArgumentError(`${label} must be an integer >= ${min}`);
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-copy the short id from the story URL https://lobste.rs/s/<shortId> and verify it opens in a browser.
  2. Confirm the id comes from Lobste.rs, not another aggregator (HN/Reddit ids will not resolve).
  3. List stories again via `lobsters` front-page/domain commands to get a currently valid short id.
  4. In scripts, catch EmptyResultError and prompt the user to re-enter the id instead of crashing.

Example fix

// before
await cli.lobsters.read('12345678'); // HN-style numeric id -> Story not found

// after
const id = 'abcdefgh'; // copy from the lobste.rs story URL /s/<id>
await cli.lobsters.read(id);
Defensive patterns

Strategy: validation

Validate before calling

function isValidLobstersShortId(id) {
  return typeof id === 'string' && /^[a-z0-9]{5,10}$/i.test(id);
}
if (!isValidLobstersShortId(shortId)) {
  console.error(`"${shortId}" does not look like a Lobste.rs short id (copy it from https://lobste.rs/s/<id>).`);
  process.exit(1);
}

Type guard

null

Try / catch

try {
  const story = await cli.lobsters.read(shortId);
} catch (err) {
  if (err.name === 'EmptyResultError') {
    console.error(`No story with id "${shortId}" on Lobste.rs — check the id or the story may have been removed.`);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Running `lobsters read <shortId>` where the id does not match any story: a typo in the short id, a story deleted or removed by moderators, an id copied from another site (Hacker News ids are numeric and will 404), or a truncated id.

Common situations: Hand-typing a short id from memory, using a HN numeric id like '12345678', referencing a story that was since deleted on lobste.rs, or off-by-one copy/paste of the id from a URL (extra characters or missing tail).

Related errors


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