-
-
Notifications
You must be signed in to change notification settings - Fork 329
fix: consume and clean up copied Chrome profile in authenticated browser fetch (#161) #247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
josephkehan-prog
wants to merge
7
commits into
KnockOutEZ:main
Choose a base branch
from
josephkehan-prog:resubmit/161-chrome-profile-consume-cleanup
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1d32338
fix: consume and clean up copied Chrome profile in authenticated brow…
josephkehan-prog 804fd9c
merge: update from main (v0.2.1)
josephkehan-prog 3696ca4
fix(cache): parse zone-less UTC timestamps as UTC in expiry checks (#…
josephkehan-prog bb4ed3a
test(fetch): add page.evaluate mock to persistent-profile tests
josephkehan-prog 90ef577
Merge branch 'main' into fix/161-chrome-profile-consume-cleanup
josephkehan-prog 88ba74b
Merge remote-tracking branch 'origin/main' into fix/161-chrome-profil…
josephkehan-prog 0865723
perf(fetch): copy and remove the temp Chrome profile without blocking
josephkehan-prog File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { cp, mkdtemp, rm } from 'node:fs/promises'; | ||
| import { basename, join } from 'node:path'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { createLogger } from '../logger.js'; | ||
|
|
||
| const logger = createLogger('fetch'); | ||
|
|
||
| /** | ||
| * Prefix for temp Chrome-profile copies. Doubles as the deletion guard in | ||
| * `removeTempProfile` so cleanup can never touch a user-configured directory. | ||
| */ | ||
| export const TEMP_PROFILE_PREFIX = 'wigolo-chrome-'; | ||
|
|
||
| /** | ||
| * Copy the user's Chrome profile into a fresh temp directory so the browser | ||
| * tier can open it without touching (or locking) the live profile. The copy is | ||
| * SINGLE-USE and caller-owned: whoever triggers the copy MUST remove it with | ||
| * `removeTempProfile` once the fetch settles (success, failure, or abort) — | ||
| * a surviving copy is a full-profile privacy leak in tmp. | ||
| */ | ||
| export async function copyProfileToTemp(profilePath: string): Promise<string> { | ||
| const tempDir = await mkdtemp(join(tmpdir(), TEMP_PROFILE_PREFIX)); | ||
| await cp(profilePath, tempDir, { recursive: true }); | ||
| logger.debug('copied Chrome profile to temp directory', { from: profilePath, to: tempDir }); | ||
| return tempDir; | ||
| } | ||
|
|
||
| /** | ||
| * Remove a temp Chrome-profile copy created by `copyProfileToTemp`. Guarded to | ||
| * wigolo-owned temp copies only (the `wigolo-chrome-` prefix) and best-effort: | ||
| * a cleanup failure is logged, never thrown, so it cannot mask the fetch's own | ||
| * outcome. No-op when no copy was made (`userDataDir` undefined). | ||
| */ | ||
| export async function removeTempProfile(userDataDir: string | undefined): Promise<void> { | ||
| if (!userDataDir) return; | ||
| if (!basename(userDataDir).startsWith(TEMP_PROFILE_PREFIX)) { | ||
| logger.warn('refusing to remove a directory that is not a wigolo temp profile copy', { userDataDir }); | ||
| return; | ||
| } | ||
| try { | ||
| await rm(userDataDir, { recursive: true, force: true }); | ||
| logger.debug('removed temp Chrome profile copy', { userDataDir }); | ||
| } catch (err) { | ||
| logger.warn('failed to remove temp Chrome profile copy', { | ||
| userDataDir, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
A
cpfailure leaks the empty temp directory.mkdtempcreatestempDirbeforecpruns. Ifcpthrows (e.g. bad/misconfiguredWIGOLO_CHROME_PROFILE_PATH), the function rejects before returningtempDirto the caller, so nothing ever callsremoveTempProfileon it — the empty directory is orphaned in/tmpon every failed copy attempt.🧹 Proposed fix: clean up tempDir on copy failure
export async function copyProfileToTemp(profilePath: string): Promise<string> { const tempDir = await mkdtemp(join(tmpdir(), TEMP_PROFILE_PREFIX)); - await cp(profilePath, tempDir, { recursive: true }); + try { + await cp(profilePath, tempDir, { recursive: true }); + } catch (err) { + await rm(tempDir, { recursive: true, force: true }).catch(() => {}); + throw err; + } logger.debug('copied Chrome profile to temp directory', { from: profilePath, to: tempDir }); return tempDir; }📝 Committable suggestion
🤖 Prompt for AI Agents
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: KnockOutEZ/wigolo
Length of output: 4930
🏁 Script executed:
Repository: KnockOutEZ/wigolo
Length of output: 9620
🌐 Web query:
Chrome ProcessSingleton SingletonLock File exists profile copy launchPersistentContext Chromium💡 Result:
The "SingletonLock" error in Chromium-based browsers occurs because the browser uses a set of lock files (specifically
SingletonLock,SingletonSocket, andSingletonCookie) within the user data directory to ensure that only one browser process manages a single profile at a time [1][2][3]. When the browser starts, it creates these files—often as symlinks pointing to a target containing the hostname and process ID—to claim ownership [4][5][2]. If these files exist when a new process attempts to launch, the browser assumes the profile is already in use and aborts to prevent data corruption [6][7][3]. This error frequently manifests in automated environments like Playwright (e.g.,launchPersistentContext) or CI/CD pipelines under several common scenarios: 1. Stale Lock Files: If a browser process crashes or is terminated abruptly, it may fail to clean up the lock files, leaving them in a "stale" state that blocks future launches [1][8][9]. 2. Race Conditions: When multiple automated tests or processes attempt to launch using the same user data directory simultaneously, they race to create the lock [6][10]. Only one succeeds, causing others to fail [6]. 3. Environment Mismatches: Changes to system configuration, such as a hostname change or moving a profile directory between machines, can cause the stored lock file (which contains the old hostname/PID) to remain and block the browser from starting [11][12]. To address this: - Graceful Shutdown: Ensure that all browser contexts and processes are closed cleanly using standard API methods (e.g.,browserContext.close()orbrowser.close()) to allow the browser to remove its own lock files [8]. - Stale File Cleanup: If a browser is known to be closed but the error persists, you can manually remove the stale lock files (SingletonLock,SingletonCookie, andSingletonSocket) from the profile directory [13][11][3]. Note that this should only be done when you are certain no other browser instance is using that profile [2][3]. - Serialization: In automated testing, ensure that launches using the same profile directory are serialized (e.g., using a lock or shared promise) to prevent concurrent access [6][10]. - Profile Isolation: For parallel testing, assign a unique user data directory to each parallel process to avoid resource contention entirely [6]. If you are usinglaunchPersistentContextand encounter persistent issues, investigate whether the browser is truly crashing during initialization, as misleading "already in use" errors can sometimes mask other failures like sandbox configuration issues [14].Citations:
Strip Chrome process singleton locks before launching the persistent context.
copyProfileToTemp()copies the Chrome profile byte-for-byte, includingSingletonLock/SingletonCookie/SingletonSocket, and thenlaunchPersistentContext()may open that temp directory. Those lock files can make Chromium refuse to launch withSingletonLock: File exists, the same behavior warned about for the live profile. Remove the copied singleton files before returning the temp profile path.🔒️ Proposed fix: remove copied `Singleton*` files from the temp profile
export async function copyProfileToTemp(profilePath: string): Promise<string> { const tempDir = await mkdtemp(join(tmpdir(), TEMP_PROFILE_PREFIX)); await cp(profilePath, tempDir, { recursive: true }); + await Promise.all( + ['SingletonLock', 'SingletonCookie', 'SingletonSocket'].map((name) => + rm(join(tempDir, name), { force: true }).catch(() => {}), + ), + ); logger.debug('copied Chrome profile to temp directory', { from: profilePath, to: tempDir }); return tempDir; }📝 Committable suggestion
🤖 Prompt for AI Agents