jackwener/OpenCLI · error · CommandExecutionError
Booking.com hotel row is missing stable name/url identity
Error message
Booking.com hotel row is missing stable name/url identity
What it means
This CommandExecutionError is thrown during post-processing of scraped Booking.com hotel rows when a row lacks the stable identity the CLI guarantees: a non-empty name, a two-letter lowercase country code, a non-empty slug, and a url that exactly equals the canonical https://www.booking.com/hotel/<country>/<slug>.html form. It exists so downstream consumers can rely on every row having a deterministic, reconstructible hotel URL rather than a transient DOM-derived value. It indicates the page extractor produced a row whose DOM shape the extractor did not fully capture.
Source
Thrown at clis/booking/search.js:322
totalText
? `No hotels rendered (${totalText}). Try a broader destination, different dates, or check the URL in a browser.`
: 'No hotels rendered. Try a broader destination, different dates, or check the URL in a browser.',
);
}
return items.slice(0, limit).map((it, i) => {
if (!it || typeof it !== 'object') {
throw new CommandExecutionError('Booking.com extractor returned malformed hotel row');
}
const name = String(it.name || '').trim();
const country = String(it.country || '').trim();
const slug = String(it.slug || '').trim();
const urlValue = String(it.url || '').trim();
const expectedUrl = country && slug
? `https://www.booking.com/hotel/${country}/${slug}.html`
: '';
if (!name || !/^[a-z]{2}$/.test(country) || !slug || urlValue !== expectedUrl) {
throw new CommandExecutionError('Booking.com hotel row is missing stable name/url identity');
}
return {
rank: offset + i + 1,
name,
country,
slug,
star_rating: it.star_rating,
review_score: it.review_score,
review_count: it.review_count,
price_amount: it.price_amount,
price_currency: it.price_amount == null ? '' : (currency || it.price_currency || ''),
distance: it.distance,
recommended_room: it.recommended_room,
url: urlValue,
};
});
},
});View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run the search with a different locale (--lang en) or destination to see if only certain layouts fail
- Update the Booking.com extractor to pick up the new hotel-card DOM selectors so name/slug/url are captured
- Check Booking.com HTML for the changed card structure and add the missing selector to the in-page extraction script
- If a specific property type never matches the /hotel/<country>/<slug>.html pattern, extend expectedUrl construction or relax the strict equality while keeping the invariant documented
Example fix
// before (extractor misses url on new card layout)
url: card.querySelector('a.hotel-card-link')?.href || ''
// after
target_url: card.querySelector('a[data-testid="property-card-desktop-link"], a.hotel-card-link')?.getAttribute('href') || '' Defensive patterns
Strategy: validation
Validate before calling
// Validate extracted rows before calling/consuming the search result
function hasStableHotelIdentity(it) {
if (!it || typeof it !== 'object') return false;
const name = String(it.name || '').trim();
const country = String(it.country || '').trim();
const slug = String(it.slug || '').trim();
const url = String(it.url || '').trim();
const expected = country && slug ? `https://www.booking.com/hotel/${country}/${slug}.html` : '';
return !!name && /^[a-z]{2}$/.test(country) && !!slug && url === expected;
}
if (!raw.items.every(hasStableHotelIdentity)) {
console.warn('some Booking.com rows lack stable identity; extractor may be stale');
} Type guard
function isWellFormedHotelRow(it) {
return (
typeof it === 'object' && it !== null &&
typeof it.name === 'string' && it.name.trim() !== '' &&
typeof it.slug === 'string' && it.slug.trim() !== '' &&
typeof it.country === 'string' && /^[a-z]{2}$/.test(it.country) &&
typeof it.url === 'string' &&
it.url === `https://www.booking.com/hotel/${it.country}/${it.slug}.html`
);
} Try / catch
try {
const results = await runBookingSearch(opts);
} catch (err) {
if (err.message.includes('missing stable name/url identity')) {
// fall back to raw rows or flag extractor drift
console.warn('Booking extractor layout drift; skipping identity check or re-extract');
} else throw err;
} Prevention
- Pin/monitor Booking.com DOM changes; add an integration test asserting extracted rows pass the identity invariant
- Prefer the canonical data-testid selectors Booking.com uses for property-card links
- Normalize locale/country codes to lowercase two-letter form at extraction time
- Handle special property types (apartments, hostels) whose URLs may deviate from /hotel/<country>/<slug>.html
When it happens
Trigger: Running the booking search command when the page extractor returns an item where: it.name is empty, it.country is missing or not a 2-letter lowercase code, it.slug is empty, or it.url does not exactly match the constructed canonical hotel URL. Booking.com markup changes, alternative hotel card layouts, locale variants emitting uppercase/region country codes, or hotels whose canonical URL deviates from the /hotel/<country>/<slug>.html pattern all trigger it.
Common situations: A Booking.com A/B test or redesign changes hotel card DOM attributes so the extractor misses name/slug/url fields; scraping a non-standard property type (e.g. hostels/apartments with different URL structure); country code returned as 'GB' or 'en-gb' instead of 'gb'; new marketplace domains (e.g. .co.uk or b-hotel URLs) failing the equality check.
Related errors
- IMDb redirected to a different title: ${currentId}
- Taobao my_itaobao rendered but no user_id extractable — stal
- Tieba may have blocked the hot page, or the DOM structure ma
- Upwork result at rank ${rank} did not include a valid cipher
- weixin search returned an unreadable browser payload
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/d60c7fabea71e882.
Report an issue: GitHub.