Skip to content

fix(person): try connect deeplink fallback for follow_only profiles#467

Open
devag7 wants to merge 4 commits into
stickerdaniel:mainfrom
devag7:fix/454-connect-visible-but-unavailable
Open

fix(person): try connect deeplink fallback for follow_only profiles#467
devag7 wants to merge 4 commits into
stickerdaniel:mainfrom
devag7:fix/454-connect-visible-but-unavailable

Conversation

@devag7
Copy link
Copy Markdown

@devag7 devag7 commented May 26, 2026

Fixes #454. Removes strict has_invite_anchor requirement and probes deeplink to detect Connect buttons hidden inside standard button elements.

Copilot AI review requested due to automatic review settings May 26, 2026 20:52
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR adjusts LinkedIn connection detection to handle profiles where Connect may be hidden behind a deeplink fallback. It changes:

  • Broadens action-root discovery to include standard buttons as candidates.
  • Allows the custom-invite deeplink fallback when no invite anchor is found but an unavailable profile has a labeled action button.
  • Adds a regression test for probing the invite deeplink without an exposed invite anchor.

Confidence Score: 3/5

This should be fixed before merging.

  • The updated action-root finder can select an unrelated button area in main.

  • The connection state can then be computed from the wrong subtree.

  • The fallback can navigate to the invite deeplink without a real invite anchor from the profile action row.

  • linkedin_mcp_server/scraping/extractor.py

Important Files Changed

Filename Overview
linkedin_mcp_server/scraping/extractor.py Changes action-root discovery and the no-anchor fallback gate for connection invites.
tests/test_scraping.py Adds coverage for the deeplink fallback path without an invite anchor.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
linkedin_mcp_server/scraping/extractor.py:134-136
**Keep action-root scoped**

`findActionRoot` now starts from every button in `main`, so the first unrelated button cluster with two interactive descendants can become the action root before the real profile action row is reached. In that case `_ACTION_SIGNALS_JS` reads compose, pending, and labeled-button signals from the wrong subtree, and `_OPEN_MORE_BUTTON_JS` can click the wrong expanded menu. A profile with a valid hidden Connect action can fail to open the real More menu, while a wrong root with an `aria-label` button and no compose anchor produces `state == "unavailable"` plus `has_labeled_action_button == true`, which reaches the fallback deeplink path without an invite anchor.

Reviews (4): Last reviewed commit: "fix(person): decouple action root detect..." | Re-trigger Greptile

Comment thread linkedin_mcp_server/scraping/extractor.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the LinkedIn connection flow to attempt the custom-invite deeplink even when the vanityName invite anchor is not present, and adds a regression test to verify the fallback behavior.

Changes:

  • Add a deeplink “probe” fallback when signals.has_invite_anchor is false.
  • Return connect_unavailable only if the deeplink does not open an invite dialog.
  • Add an async test covering the fallback navigation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
linkedin_mcp_server/scraping/extractor.py Adds deeplink-probe fallback to connect_with_person when the invite anchor is missing.
tests/test_scraping.py Adds test asserting the deeplink is navigated even when has_invite_anchor is false.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_scraping.py Outdated
Comment on lines +1005 to +1031
async def mock_dialog(*args, **kwargs):
return True

with (
patch.object(
extractor,
"scrape_person",
self._mock_scrape("Profile", follow_up_text="Profile"),
),
patch.object(
extractor,
"_read_action_signals",
new_callable=AsyncMock,
side_effect=[self._signals(labeled_action=True), self._signals()],
),
patch.object(
extractor, "_open_more_menu", new_callable=AsyncMock, return_value=True
),
patch.object(
extractor, "_navigate_to_page", new_callable=AsyncMock
) as mock_nav,
patch.object(
extractor,
"_dialog_is_open",
new_callable=AsyncMock,
side_effect=mock_dialog,
),
Comment on lines 1743 to 1753
await self._navigate_to_page(invite_url)
if not await self._dialog_is_open(timeout=5000):
return _connection_result(
url,
"connect_unavailable",
"LinkedIn did not expose a usable Connect action for this profile.",
profile=page_text,
)
else:
await self._navigate_to_page(invite_url)

Comment on lines +1738 to +1740
# Write-gate: Try to find the vanityName invite anchor. If missing,
# (e.g. because Connect is rendered as a <button> instead of an <a>,
# see Issue #454), fallback to probing the deeplink URL directly.
Comment thread linkedin_mcp_server/scraping/extractor.py Outdated
Comment thread linkedin_mcp_server/scraping/extractor.py
Comment on lines +134 to +136
const candidates = main.querySelectorAll('button, a[href*="/messaging/compose/"]');
for (const c of candidates) {
let el = c.parentElement;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Keep action-root scoped

findActionRoot now starts from every button in main, so the first unrelated button cluster with two interactive descendants can become the action root before the real profile action row is reached. In that case _ACTION_SIGNALS_JS reads compose, pending, and labeled-button signals from the wrong subtree, and _OPEN_MORE_BUTTON_JS can click the wrong expanded menu. A profile with a valid hidden Connect action can fail to open the real More menu, while a wrong root with an aria-label button and no compose anchor produces state == "unavailable" plus has_labeled_action_button == true, which reaches the fallback deeplink path without an invite anchor.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/scraping/extractor.py
Line: 134-136

Comment:
**Keep action-root scoped**

`findActionRoot` now starts from every button in `main`, so the first unrelated button cluster with two interactive descendants can become the action root before the real profile action row is reached. In that case `_ACTION_SIGNALS_JS` reads compose, pending, and labeled-button signals from the wrong subtree, and `_OPEN_MORE_BUTTON_JS` can click the wrong expanded menu. A profile with a valid hidden Connect action can fail to open the real More menu, while a wrong root with an `aria-label` button and no compose anchor produces `state == "unavailable"` plus `has_labeled_action_button == true`, which reaches the fallback deeplink path without an invite anchor.

**Context Used:** AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=7e01dc20-d2a2-4426-a426-ce1121bc577c))

How can I resolve this? If you propose a fix, please make it concise.

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

Labels

None yet

Projects

None yet

2 participants