jackwener/OpenCLI · error · CommandExecutionError
Pin ${id} has no downloadable image (it may be a video or st
Error message
Pin ${id} has no downloadable image (it may be a video or story pin) What it means
The pin resolved but pickPinImage(pin.images) returned no usable image URL, so the command raises CommandExecutionError. Some Pinterest pins are videos or story (idea) pins whose images map contains no static downloadable rendition.
Source
Thrown at clis/pinterest/download.js:40
func: async (page, kwargs) => {
const id = parsePinId(kwargs.pin);
const output = String(kwargs.output ?? './pinterest-downloads');
const sourceUrl = `/pin/${id}/`;
await page.goto(`${PINTEREST_BASE}${sourceUrl}`);
const { data: pin } = await pinterestResourceFetch(
page,
'PinResource',
{ id, field_set_key: 'detailed' },
sourceUrl,
);
if (!pin || !pin.id) {
throw new EmptyResultError('pinterest download', `pin "${id}" not found`);
}
const imageUrl = pickPinImage(pin.images);
if (!imageUrl) {
throw new CommandExecutionError(`Pin ${id} has no downloadable image (it may be a video or story pin)`);
}
fs.mkdirSync(output, { recursive: true });
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg';
const destPath = path.join(output, `${id}${ext}`);
let result;
try {
result = await httpDownload(imageUrl, destPath, { timeout: 60000 });
} catch (err) {
throw new CommandExecutionError(`Failed to download pin ${id}: ${getErrorMessage(err)}`);
}
if (!result.success) {
throw new CommandExecutionError(`Failed to download pin ${id}: ${result.error || 'unknown error'}`);
}
return [{
pinId: id,View on GitHub (pinned to 49907e53dc)
Solutions
- Only call download on image pins; detect video/story pins beforehand and skip them.
- If you need video content, use a pin video resource/URL instead of the image path.
- Catch CommandExecutionError and record the pin as skipped in your pipeline.
- Check pin.images in the API response — if it lacks renditions, the pin is not image-based.
Example fix
// before await cli.download(videoPinId); // after if (pin.type === 'video' || pin.is_story) skip(pin); else await cli.download(pin.id);
Defensive patterns
Strategy: type-guard
Validate before calling
const pin = await fetchPin(id);
if (pin.type === 'video' || pin.is_story || !pin.images || Object.keys(pin.images).length === 0) {
skip(id, 'non-image pin');
} Type guard
function isDownloadableImagePin(pin) {
return Boolean(pin && pin.id && pin.images && Object.keys(pin.images).length > 0);
} Try / catch
try {
await cli.download(pinId);
} catch (e) {
if (/no downloadable image/.test(e.message)) {
skipped.push({ id: pinId, reason: 'video-or-story-pin' });
} else throw e;
} Prevention
- Check pin type before downloading (video/story pins have no image)
- Inspect pin.images in the API payload for available renditions
- Handle idea pins as a separate content type
- Maintain a skip list rather than failing whole batch jobs
When it happens
Trigger: Calling `pinterest download <id>` on a video pin, story/idea pin, or a pin whose detailed field_set images payload lacks the expected renditions.
Common situations: Batch-download scripts iterating a user's pins and hitting video-heavy boards; newer Pinterest formats (idea pins) that older scrapers assume are image pins.
Related errors
- Could not resolve board "${username}/${slug}" (does it exist
- board "${username}/${slug}" has no sections
- Unknown privacy "${privacy}". Valid: ${PRIVACY.join(', ')}
- nothing to update
- pin "${id}" not found
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/60367bc051f09aaa.
Report an issue: GitHub.