Skip to content

fix(opera): support modern Chromium profiles layout and zero-kill cookie extraction#556

Open
alztrk wants to merge 8 commits into
stickerdaniel:mainfrom
alztrk:fix/555-opera-layout-and-zero-kill
Open

fix(opera): support modern Chromium profiles layout and zero-kill cookie extraction#556
alztrk wants to merge 8 commits into
stickerdaniel:mainfrom
alztrk:fix/555-opera-layout-and-zero-kill

Conversation

@alztrk

@alztrk alztrk commented Jun 28, 2026

Copy link
Copy Markdown

Closes #XXX

--import-from-browser opera was broken on two fronts: the profile layout discovery was wrong for modern Opera, and the locked Cookies database could not be read without killing browser processes.

Opera profile layout

Old Opera and Opera GX store the default profile directly in the user data folder (a "flat" layout). Opera One (2023+) uses the standard Chromium layout with a Default subdirectory. The original code only looked for the flat layout, so it found zero profiles on any modern Opera installation.

The fix tries the profiles layout first, then falls back to flat. This covers all three cases: modern Opera One (profiles), legacy Opera and Opera GX (flat), and migrated users who have both — the active cookies in Default are preferred over any stale copies lingering at the root level.

Locked Cookies database

Chromium locks its Cookies SQLite database with a mandatory byte-range lock while the browser is running, so it cannot be opened or copied by normal means. The existing code used Windows Restart Manager to kill the browser's network process, release the lock, copy the file, then let the process restart. This has two problems: it kills browser processes (the zero-kill requirement), and the network process restarts so quickly that the lock is often reacquired before the copy completes.

The primary path now uses DuplicateHandle instead. It enumerates all system handles via NtQuerySystemInformation, locates the handle locking the target file, duplicates it from the lock-holding process (with only PROCESS_DUP_HANDLE access), verifies it is a disk file via GetFileType, resolves the path with GetFinalPathNameByHandleW, and reads the content. A thread-based timeout prevents hangs on pipe or socket handles. The Restart Manager fallback remains but its race condition is fixed: the copy now happens immediately after RmShutdown with tight retries, before the network process can restart.

Other fixes

  • Removed the hardcoded FILE_TYPE_INDEX = 42, which was specific to one Windows build, in favor of a GetFileType call that works across all versions.
  • Set explicit restype/argtypes on all Win32 API calls to prevent 64-bit handle truncation in ctypes.

Verification

uv run ruff check .
uv run ruff format .
uv run ty check
uv run pytest --cov

Tested end-to-end on Windows 11 with Opera One open: 34 LinkedIn cookies extracted, session validated against /feed/. No processes killed.

Synthetic prompt

On Windows, --import-from-browser opera fails because:

  1. Modern Opera (Opera One) uses a Default/ subdirectory (Chromium profiles layout), not flat
  2. Opera locks its Cookies SQLite file while running, and the existing Restart Manager approach kills processes and has a race condition

Fix discovery.py to try profiles first, then flat. Add a DuplicateHandle-based function in extract.py that reads the locked file by duplicating the handle from the lock-holding process. Fix the Restart Manager fallback race. Replace the hardcoded FILE_TYPE_INDEX with GetFileType. Add thread timeouts for GetFinalPathNameByHandleW.

Generated with Claude Code

…kie extraction

Closes stickerdaniel#555

Modern Opera (Opera One, 2023+) uses the standard Chromium profiles
layout with a Default/ subdirectory, not the flat layout used by old
Opera and Opera GX. The import code only searched the flat layout and
silently found zero profiles.

Also fixes reading the locked Cookies database without killing browser
processes, using DuplicateHandle instead of Windows Restart Manager.
@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Opera cookie import on modern Chromium-based profile layouts. The main changes are:

  • Opera discovery now tries profile directories before flat-layout roots.
  • Locked cookie files can be read through duplicated Windows handles.
  • Restart Manager fallback now copies files during the unlock window.
  • Win32 ctypes signatures and file-type checks were made explicit.

Confidence Score: 4/5

This is close, but the cookie snapshot path should be fixed before merging.

  • The locked-file flow can still copy Cookies before its WAL or SHM sidecars.
  • A Chromium write between those copies can make the temporary SQLite set inconsistent.
  • Cookie import can then miss the active session cookie or fail while opening the copied DB.

linkedin_mcp_server/browser_import/extract.py

Important Files Changed

Filename Overview
linkedin_mcp_server/browser_import/discovery.py Updates Opera profile discovery to support both Chromium-style and flat profile layouts.
linkedin_mcp_server/browser_import/extract.py Adds Windows locked-file extraction paths and improves Restart Manager copying, but the DB and sidecars can still be copied from different moments.
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/browser_import/extract.py:833-841
**Snapshot still splits** When `Cookies` copies successfully in this phase but `Cookies-wal` or `Cookies-shm` is locked, the main DB is captured before the sidecar is copied through DuplicateHandle or Restart Manager. If Chromium commits or checkpoints between those phases, the temp directory contains SQLite files from different moments. The later read opens the copy with `mode=ro`, so it can replay a WAL that does not match the copied DB and return stale cookies, miss the current `li_at`, or fail with a corrupt snapshot. The DB and sidecars need to be captured as one consistency unit instead of batching only the files that remain locked.

Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
alztrk added 2 commits June 28, 2026 13:45
ctypes.windll, WinDLL, WINFUNCTYPE and os.O_BINARY are Windows-only. Without ty: ignore, the Linux CI reports them as errors.
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Greptile identified a race: after RmEndSession the browser restarts its network process and reacquires the lock. The copy must happen during the unlock window, before the session ends.
Comment thread linkedin_mcp_server/browser_import/extract.py
Comment thread linkedin_mcp_server/browser_import/extract.py Outdated
Also passes copy_to for sidecar Restart Manager fallback.
Comment thread linkedin_mcp_server/browser_import/extract.py
Previously each locked file (DB, WAL, SHM) was handled individually through separate Restart Manager sessions or DuplicateHandle calls, risking a mixed-snapshot where DB and sidecars come from different database moments. Now all files that still need unlocking after DuplicateHandle are registered with one RmStartSession, released atomically via one RmShutdown, and copied within a single unlock window.
Comment thread linkedin_mcp_server/browser_import/extract.py
Comment thread linkedin_mcp_server/browser_import/extract.py
Comment on lines +833 to +841
need_duphandle: list[tuple[Path, Path]] = []
for src, dst in copies:
try:
shutil.copy2(src, dst)
os.chmod(dst, 0o600)
except PermissionError:
if os.name == "nt":
need_duphandle.append((src, dst))
else:

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 Snapshot still splits When Cookies copies successfully in this phase but Cookies-wal or Cookies-shm is locked, the main DB is captured before the sidecar is copied through DuplicateHandle or Restart Manager. If Chromium commits or checkpoints between those phases, the temp directory contains SQLite files from different moments. The later read opens the copy with mode=ro, so it can replay a WAL that does not match the copied DB and return stale cookies, miss the current li_at, or fail with a corrupt snapshot. The DB and sidecars need to be captured as one consistency unit instead of batching only the files that remain locked.

Prompt To Fix With AI
This is a comment left during a code review.
Path: linkedin_mcp_server/browser_import/extract.py
Line: 833-841

Comment:
**Snapshot still splits** When `Cookies` copies successfully in this phase but `Cookies-wal` or `Cookies-shm` is locked, the main DB is captured before the sidecar is copied through DuplicateHandle or Restart Manager. If Chromium commits or checkpoints between those phases, the temp directory contains SQLite files from different moments. The later read opens the copy with `mode=ro`, so it can replay a WAL that does not match the copied DB and return stale cookies, miss the current `li_at`, or fail with a corrupt snapshot. The DB and sidecars need to be captured as one consistency unit instead of batching only the files that remain locked.

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

alexeynesteruk pushed a commit to alexeynesteruk/linkedin-mcp-server that referenced this pull request Jul 13, 2026
Port get_saved_jobs (stickerdaniel#523), job output_mode/file export (stickerdaniel#519),
Opera modern profile layout and Windows zero-kill cookie copy (stickerdaniel#556),
and comment reading plus analytics tools (stickerdaniel#571) onto the Tier B branch.
alexeynesteruk pushed a commit to alexeynesteruk/linkedin-mcp-server that referenced this pull request Jul 13, 2026
Port get_saved_jobs (stickerdaniel#523), job output_mode/file export (stickerdaniel#519),
Opera modern profile layout and Windows zero-kill cookie copy (stickerdaniel#556),
and comment reading plus analytics tools (stickerdaniel#571) onto the Tier B branch.
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