Skip to content

linkedin: domain skills for send-connection-request + connection-search-scrape#348

Open
frumza wants to merge 1 commit into
browser-use:mainfrom
frumza:feat/linkedin-domain-skills
Open

linkedin: domain skills for send-connection-request + connection-search-scrape#348
frumza wants to merge 1 commit into
browser-use:mainfrom
frumza:feat/linkedin-domain-skills

Conversation

@frumza
Copy link
Copy Markdown

@frumza frumza commented May 13, 2026

What this adds

Two new LinkedIn domain skills in domain-skills/linkedin/:

  1. send-connection-request.md — invite flow (with + without note), free-tier paywall detection, rate-limit notes, coordinate-click fallback when the JS scope can't reach. Covers the non-obvious "Connect is rendered as an <a>, not a <button>" gotcha that breaks the obvious button[aria-label^="Connect"] selector.
  2. connection-search-scrape.md — keyword-filtered People-search via the connectionOf filter. Mines a target user's network surface accessible from the asker's 1st+2nd-degree graph. Anchor-innerText extraction pattern (robust to LinkedIn's rotating CSS-module class names), pagination, anti-bot guidance, keyword strategy, and a self-contained runnable script.

Why this is worth its own pair of domain skills

The non-obvious bits I think are durable enough to be worth capturing:

For send-connection-request.md:

  • The Connect button is an <a>, not a <button>. The obvious button[aria-label^="Connect"] returns the wrong (hidden, 0×0 geometry) element when querying button only.
  • The first invite modal is in the main DOM but the second "Add a note" dialog is inside an iframe at linkedin.com/preload/ that CDP doesn't expose through the normal iframe target map.
  • The Premium paywall renders inside that iframe — undetectable from the top-level JS scope; the recommended detection is "after clicking Add-a-note, check if a top-level textarea exists."
  • Plain "Send without a note" is rate-limited much less aggressively (~100/week soft cap) than custom notes (~5/month).

For connection-search-scrape.md:

  • LinkedIn's 2026 People-search DOM uses obfuscated CSS-module class names (_83309bd4 _6e63fa0b d343d86c) that rotate per deploy. Selector-hunting against them is a maintenance trap.
  • The robust pattern is to query a[href*="/in/"] and read innerText off the anchor — the anchor's text carries the entire structured card payload (name + degree + headline + location + mutuals + summary highlights).
  • The connectionOf URL parameter takes a percent-encoded JSON array of MEMBER_URNs. Bare brackets silently 400 or return empty.
  • Empirical keyword hit-rate table from a real ~380-result sweep so users picking keywords have prior data on what yields high precision (named companies, 0-3 hits) vs high recall (geographic countries, 50-80 hits) vs high noise (broad industry, 50-100).
  • Anti-bot specifics for this surface: real Chrome session sails through at ~2s/page; same flow in headless cloud mode soft-blocks after a handful of queries.

Tested against

Both skills were developed and validated against real flows:

  • send-connection-request.md — multiple invite sends across free + paywall states.
  • connection-search-scrape.md — full keyword sweep against a target with a regional emerging-markets business network (~380 unique results, 39 keywords, ~10 min wall-time, no rate-limiting or block).

Scope

Two new markdown-only files in domain-skills/linkedin/. No changes to helpers.py, daemon.py, or any other harness internals. Consistent with the existing domain-skill shape (selector gotchas → framework quirks → end-to-end flow → traps).


Summary by cubic

Adds two LinkedIn domain skills to automate connection invites and scrape a target’s connections via keyword-filtered People search. Improves reliability with robust selectors, paywall detection, anti-bot guidance, and a runnable script.

  • New Features
    • domain-skills/linkedin/send-connection-request.md: Handles Connect as an <a> (not <button>), full invite modal flow, detects add-note iframe paywall (free-tier cap), includes coordinate-click fallback and rate-limit guidance.
    • domain-skills/linkedin/connection-search-scrape.md: Keyword-filtered People search using connectionOf with percent-encoded JSON, parses /in/ anchors’ innerText (resilient to rotating classes), server-side pagination rules, real-Chrome anti-bot tips, and a self-contained Python script with keyword strategy.

Written for commit 031bf47. Summary will update on new commits.

…ch-scrape

Two new LinkedIn domain skills:

- send-connection-request.md: invite flow with paywall detection,
  rate-limit notes, coordinate-click fallback. Covers the 'Connect
  is an <a> not a <button>' gotcha + the iframe-paywall detection
  pattern.

- connection-search-scrape.md: keyword-filtered People-search via
  connectionOf filter. Anchor-innerText extraction pattern (robust
  to LinkedIn's rotating CSS-module class names), pagination,
  anti-bot guidance, empirical keyword hit-rate table, self-contained
  runnable script.
@browser-harness-review
Copy link
Copy Markdown

⛔ Skill review blocked

An automated security review found 3 finding(s) across 1 file(s).

  • domain-skills/linkedin/connection-search-scrape.mddangerous_browser_action, excessive_scope

Skill authorship is restricted to maintainers. Please do not attempt to self-fix — a maintainer will review and follow up.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="domain-skills/linkedin/connection-search-scrape.md">

<violation number="1" location="domain-skills/linkedin/connection-search-scrape.md:173">
P1: `parse_card` mishandles cards where `• <Degree>` is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).</violation>
</file>

<file name="domain-skills/linkedin/send-connection-request.md">

<violation number="1" location="domain-skills/linkedin/send-connection-request.md:61">
P2: Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

def parse_card(text, href):
lines = [l.strip() for l in text.split("\n") if l.strip()]
if not lines: return None
parts = lines[0].split("•")
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: parse_card mishandles cards where • <Degree> is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/linkedin/connection-search-scrape.md, line 173:

<comment>`parse_card` mishandles cards where `• <Degree>` is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).</comment>

<file context>
@@ -0,0 +1,259 @@
+def parse_card(text, href):
+    lines = [l.strip() for l in text.split("\n") if l.strip()]
+    if not lines: return None
+    parts = lines[0].split("•")
+    name = parts[0].strip()
+    degree = parts[1].strip() if len(parts) > 1 else ""
</file context>
Fix with Cubic

break

# 4. Click Send without a note
js("""document.querySelector('button[aria-label="Send without a note"]').click()""")
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/linkedin/send-connection-request.md, line 61:

<comment>Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found</comment>

<file context>
@@ -0,0 +1,150 @@
+        break
+
+# 4. Click Send without a note
+js("""document.querySelector('button[aria-label="Send without a note"]').click()""")
+time.sleep(2.5)
+
</file context>
Suggested change
js("""document.querySelector('button[aria-label="Send without a note"]').click()""")
js("""(() => { const btn = document.querySelector('button[aria-label=\"Send without a note\"]'); if (!btn) return 'modal-not-found'; btn.click(); return 'clicked'; })()""")
Fix with Cubic

@frumza
Copy link
Copy Markdown
Author

frumza commented May 13, 2026

Thanks both bots.

Acknowledging the browser-harness-review[bot] policy that skill authorship is restricted to maintainers — happy to leave this PR for maintainer triage and not self-modify per the bot's request.

On the cubic-dev-ai[bot] findings: both are valid.

  • P1 (parse_card field misalignment when • <Degree> is rendered on a separate line) is a real edge case — LinkedIn sometimes wraps long names so the degree-bullet lands on its own line, which our parser mis-attributes to the headline field.
  • P2 (missing null-guard after modal polling) is a real crash path when the "Send without a note" button never renders (auth/rate-limit edge cases).

I have working fixes ready for both if a maintainer wants them applied here. Alternatively I'm happy to pull this PR and resubmit a leaner version if scope is the concern, or close it entirely if external skill PRs aren't being accepted at all. Whichever route works for the project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant