Files
jackwener__opencli/docs/adapters/browser/linkedin.md
jakevin 69ee36f997 fix(linkedin): surface detail_error on --details (no silent catch / no silent empty) (#1363)
* fix(linkedin): surface detail_error on --details (no silent catch / no silent empty)

The previous --details enrichment path had two indistinguishable failure modes
that both produced `description: '', apply_url: ''`:

1. `if (!job.url)` early return — row had no jobId, so we couldn't navigate.
2. `} catch {}` — page.goto / page.evaluate threw (network, timeout, parse error).

Callers couldn't tell "upstream had no description" from "we failed to fetch",
and the catch swallowed every error without logging. For an enrichment that
costs one page navigation per row, silent failure is especially harmful — users
just see an empty cell with no way to debug.

Fix: replace empty strings with `null` for missing/failed rows, add a new
`detail_error` column (string|null) carrying a short typed reason:

  - 'no url'                — row had no jobId
  - 'fetch failed: <msg>'   — page.goto / page.evaluate threw
  - 'missing description'   — page loaded but body was empty
  - null                    — success

Every failure is also logged to stderr with the offending URL so debugging is
possible. Per-row failures still don't abort the batch (the original intent),
but they're now visible.

Tests: 13 new contract assertions in clis/linkedin/search.test.js covering
parseCsvArg, mapFilterValues (ArgumentError on unknown values), decodeLinkedinRedirect,
and 5 enrichJobDetails paths (no-url / goto-throw / empty-description / success /
multi-row-mixed). Added `export const __test__` for testability.

Audits clean: typed-error-lint 196/196, silent-column-drop 103/103.

* fix(linkedin): fail fast on auth walls
2026-05-06 22:57:09 +08:00

2.1 KiB

LinkedIn

Mode: 🔐 Browser · Domain: linkedin.com

Commands

Command Description
opencli linkedin search Search LinkedIn jobs (Voyager API), with optional --details enrichment
opencli linkedin timeline Read posts from your LinkedIn home feed

Usage Examples

# Quick start
opencli linkedin search --limit 5

# Search with filters
opencli linkedin search "site reliability engineer" --location "San Francisco Bay Area" --remote remote

# Enrich with full description and apply URL (slower; 1 page navigation per row)
opencli linkedin search "data scientist" --limit 3 --details

# Read your home timeline
opencli linkedin timeline --limit 5

# JSON output
opencli linkedin search -f json
opencli linkedin timeline -f json

Output

Always returns: rank · title · company · location · listed · salary · url

When --details is set, each row additionally has:

Column Type Notes
description string | null Full "About the job" body. null if upstream had nothing or fetch failed (see detail_error).
apply_url string | null First apply-labelled link on the page. null if upstream had nothing or fetch failed.
detail_error string | null null on success. Otherwise short reason: 'no url' (row had no jobId), 'fetch failed: <message>' (navigation/parse threw), or 'missing description' (page loaded but body was empty).

Previously the adapter returned description: '', apply_url: '' for both the missing-url path and the silent-catch path — callers couldn't tell upstream gaps apart from fetch failures. The current shape preserves backward compatibility on success and surfaces failures with null + a typed reason on detail_error. Per-row failures still don't abort the batch.

--limit must be between 1 and 100, and --start must be a non-negative integer. LinkedIn login/auth walls abort with AuthRequiredError instead of being folded into detail_error.

Prerequisites