feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc and Firefox#7260

merged23
browser-profile-import
browser-import-more-sources
openedAug 16, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 4d ago

Stacked on #7255 (browser-profile-import).

Expands cookie import to a source registry with Chromium and Firefox readers.

SourcemacOSWindowsLinux in this layer
Chrome, Edge, Brave, Vivaldi, OperaSupportedUnsupportedDetected; key support in #7261
Arc, HeliumSupportedUnsupportedUnsupported
FirefoxSupportedSupportedSupported

Firefox imports its default container; other containers are excluded so their sessions are not mixed. Discovery respects profiles.ini, validates relative paths, supports explicitly configured absolute profile directories, and ignores fallback entries without a cookie database. Counts use the same default-container filter as the reader.

Both engines share a consistent read-only SQLite snapshot path. Windows Chromium remains intentionally unsupported, preserving the Windows-tested contribution #7323.

Validation: focused source-discovery, path-validation, Firefox cookie/count, and SQLite snapshot fixtures. Cross-platform path tests run on macOS; they are not a substitute for another live Windows/Linux test.

Original implementation: Claude Code. Review fixes: GPT-5.6 Sol agents, coordinated through Codex.

Note

Add browser cookie import for Chrome, Edge, Brave, Vivaldi, Opera, Arc, and Firefox

  • Expands BROWSER_IMPORT_SOURCE_IDS to include seven Chromium-based browsers and Firefox, with per-browser platform paths, keychain coordinates, and engine type (chromium vs firefox)
  • Adds Firefox cookie reading in readFirefoxCookies that snapshots cookies.sqlite read-only, decodes schema-version-aware moz_cookies rows (expiry unit changes at schema 16, rawSameSite migration for schemas ≤14), and filters to the default container
  • Adds Firefox profile discovery via parseFirefoxProfiles (profiles.ini parsing with path validation) plus cross-platform fallback directory scanning, including Linux Snap root deduplication
  • Adds Firefox running-state detection with platform-specific lock probes: firefoxSymlinkLockIsHeld + posixLockIsHeld (fcntl via child interpreter) on POSIX, windowsLockIsHeld (sharing violation on parent.lock) on Windows
  • Extracts shared ImportedCookie, CookieReadResult, cookieScope, bareHost, and snapshotCookieDatabase into CookieDatabase.ts for reuse by both engines
  • Risk: ChromiumCookies.sameSiteFromColumn now maps column value 1 and unknown values to unspecified instead of lax; Chromium import is blocked on non-macOS platforms via unsupportedPlatform in BrowserImport.ts

Macroscope summarized 4916613.


Note

Medium Risk Touches cookie import, keychain access, and path validation across many browsers; Chromium SameSite mapping changed and Firefox lock probing can mis-detect running state if probes fail.

Overview Expands desktop browser cookie import from a single Helium source to a registry of Chrome, Edge, Brave, Vivaldi, Opera, Arc, Helium, and Firefox, wired through contracts and user docs for Settings → Integrations.

Discovery and safety move to a shared BrowserImportPathContext (platform, home, Windows %APPDATA%). Chromium sources get per-fork paths and keychain IDs; Windows Chromium stays unsupported (App-Bound Encryption). Non-macOS Chromium is rejected at list/import time until other key stores exist. Firefox adds profiles.ini parsing with path hardening, directory fallback scanning, Linux Snap alongside native installs, symlink-following for cookie DB detection, and per-profile running checks (symlink lock, fcntl on .parentlock, Windows parent.lock).

Reading cookies is split by engine: shared CookieDatabase (WAL-safe snapshot, ImportedCookie / cookieScope) feeds Chromium (macOS keychain, refactored SameSite → unspecified where appropriate) and new Firefox (readFirefoxCookies — schema-aware expiry, rawSameSite, default-container-only). BrowserImport branches readers, skips false keychain audit logs for Firefox, and maps tagged read errors. HostProcessAddresses supports Firefox lock PID matching on local IPs.

Reviewed by Cursor Bugbot for commit eea4224c0a10d6492f837f606ccadc1a25d36088. Bugbot is set up for automated code reviews on this repo. Configure here.

Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f3c31f7b-c598-4238-adee-9242d001ce25

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

apps/desktop/src/preview/BrowserImport/Sources.tsline 156Outdated · bc1215bOpen thread
……@@ -1,38 +1,167 @@
153+ {
154+ id: "firefox",
155+ name: "Firefox",
156+ engine: "firefox",
MmacroscopeappBoton line 156Aug 16, 2026

🟠 High BrowserImport/Sources.ts:156

A running Firefox is reported as available, so cookie import proceeds while its database is being written and can produce readFailed or an inconsistent cookie set instead of browserRunning. isSourceRunning checks lock/parent.lock under the Firefox root, but Firefox creates them inside each profile directory returned by listSourceProfiles; check the profile directories before allowing import.

Also found in 1 other location(s)

apps/desktop/src/preview/BrowserImport/BrowserImport.ts:75

unavailableReason trusts isSourceRunning for Firefox, but that helper checks lock/parent.lock under the Firefox installation root while Firefox creates them inside each profile directory (the same directories returned by listSourceProfiles). Consequently a normally running Firefox is treated as available, and importing can copy the live database/WAL files non-atomically, yielding readFailed or an inconsistent cookie set instead of reporting browserRunning.

✅ Resolved in 1b132e45546ca54fb62fd0bde080ac4d3f656263

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire—13.6 KiB—15.1 KiB✅
CodexThread snapshot wire—7.0 KiB—7.3 KiB✅
CodexLive turn WebSocket wire—6.6 KiB—7.8 KiB✅
CodexLive turn WebSocket decoded—57.1 KiB—66.4 KiB✅
CodexLive turn messages—10—21✅
ClaudeTotal thread wire—13.8 KiB—15.1 KiB✅
ClaudeThread snapshot wire—7.0 KiB—7.3 KiB✅
ClaudeLive turn WebSocket wire—6.7 KiB—7.8 KiB✅
ClaudeLive turn WebSocket decoded—59.3 KiB—66.4 KiB✅
ClaudeLive turn messages—10—21✅

Baseline: unavailable · PR result: eea4224 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

One convention finding: the new Firefox cookie read path models its failure as an anonymous object literal instead of a Schema.TaggedErrorClass. Details inline.

Posted via Macroscope — Effect Service Conventions

apps/desktop/src/preview/BrowserImport/Sources.tsline 138Outdated · 973b17eOpen thread
……@@ -1,62 +1,263 @@
135+ keychainService: "Opera Safe Storage",
136+ keychainAccount: "Opera",
137+ macSegments: ["com.operasoftware.Opera"],
138+ windowsSegments: ["Programs", "Opera"],
MmacroscopeappBoton line 138Aug 16, 2026

🟡 Medium BrowserImport/Sources.ts:138

Opera is never detected on Windows because windowsSegments resolves it to %LOCALAPPDATA%\Programs\Opera\User Data, while Opera stores profiles under %APPDATA%\Opera Software\Opera Stable (or Opera GX Stable). Update the Windows path resolution to use the roaming AppData locations.

✅ Resolved in 1b132e45546ca54fb62fd0bde080ac4d3f656263

……@@ -0,0 +1,77 @@
50+ const rows = yield* Effect.gen(function* () {
51+ const sql = yield* SqlClient.SqlClient;
52+ const raw = yield* sql`
53+ select host, name, value, path, expiry, isSecure, isHttpOnly, sameSite

🟡 Medium BrowserImport/FirefoxCookies.ts:53

Firefox cookies from different containers with the same host, name, and path overwrite one another during rows.map, so the imported profile receives an arbitrary container's session and mixes isolated browsing identities. The query omits originAttributes, so the importer cannot distinguish these rows; carry that attribute through extraction and define a collision policy for Electron, which has no equivalent container key.

✅ Resolved in 1b132e45546ca54fb62fd0bde080ac4d3f656263

……@@ -0,0 +1,52 @@
45+ // The sidecars only exist while the browser holds the database open, so a
46+ // missing one is normal rather than a failure.
47+ yield* Effect.forEach(["-wal", "-shm"], (suffix) =>
48+ fileSystem.copyFile(`${cookiePath}${suffix}`, `${target}${suffix}`).pipe(Effect.ignore),

🟡 Medium BrowserImport/CookieDatabase.ts:48

An existing -wal or -shm sidecar that fails to copy is silently ignored, so SQLite reads the snapshot without those transactions and the import can omit current cookies instead of failing. Effect.ignore suppresses permission and I/O errors as well as file-not-found; only suppress the expected missing-sidecar error.

✅ Resolved in 1b132e45546ca54fb62fd0bde080ac4d3f656263

apps/desktop/src/preview/BrowserImport/Sources.tsline 337Outdated · 973b17eOpen thread
……@@ -75,60 +276,71 @@ const DEFAULT_PROFILES: ReadonlyArray<BrowserImportSourceProfile> = [
334+ const found = yield* Effect.forEach(locks, (lock) =>
335+ entryExists(context.path.join(root, lock)),
123336 );
337+ return found.some(Boolean);
CcursorBoton line 337Aug 16, 2026

Firefox running check misses locks

High Severity

isSourceRunning looks for Firefox lock / parent.lock under the Firefox user-data root, but those files live inside each profile directory (and on macOS/Linux the persistent file is .parentlock). A running Firefox therefore never reports browserRunning, so import can proceed against a live cookies.sqlite and observe torn WAL state or fail the read.

Reviewed by Cursor Bugbot for commit 973b17eae96dbcfe60139c81d6ee680b98933508. Configure here.

apps/desktop/src/preview/BrowserImport/Sources.tsline 140Outdated · 973b17eOpen thread
……@@ -1,62 +1,263 @@
137+ macSegments: ["com.operasoftware.Opera"],
138+ windowsSegments: ["Programs", "Opera"],
139+ linuxSegments: ["opera"],
140+ }),
CcursorBoton line 140Aug 16, 2026

Wrong Opera Windows data path

Low Severity

Opera on Windows is registered as %LOCALAPPDATA%\Programs\Opera\User Data, but stable Opera keeps its Chromium profile under %APPDATA%\Opera Software\Opera Stable (no trailing User Data). The shared chromiumSource helper always uses localAppData and appends User Data, so this layout cannot be expressed as written and will mis-resolve once non-macOS Chromium import is enabled.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit 973b17eae96dbcfe60139c81d6ee680b98933508. Configure here.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces substantial production behavior for discovering browser profiles and importing authentication cookies from multiple browsers and platforms, including new database, keychain, process-lock, and DNS interactions. It also adds a static-analysis suppression directive, so the scope and review-sensitive handling require human review.

No code changes detected at 4916613. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

……@@ -0,0 +1,83 @@
72+ url: `${secure ? "https" : "http"}://${host}${row.path}`,
73+ name: row.name,
74+ value: row.value,
75+ domain: row.host,

🟠 High BrowserImport/FirefoxCookies.ts:75

Host-only Firefox cookies are imported with domain: row.host, so Electron normalizes example.com to a domain cookie and exposes it to every subdomain. Omit domain when row.host has no leading dot (and update the cookie type if needed) to preserve the original scope.

✅ Resolved in 5f17f5b058ed672e12c08ce390038b23f18fe7a2

apps/desktop/src/preview/BrowserImport/Sources.tsline 180Outdated · 3343565Open thread
……@@ -1,62 +1,275 @@
177+ ? context.path.join(context.appData, "Mozilla", "Firefox")
178+ : undefined;
179+ }
180+ return context.path.join(context.home, ".mozilla", "firefox");
CcursorBoton line 180Aug 16, 2026

Firefox Linux path ignores XDG

High Severity

Firefox’s Linux userDataDirectory always resolves to ~/.mozilla/firefox. Since Firefox 147 (Jan 2026), fresh installs keep profiles.ini and profiles under $XDG_CONFIG_HOME/mozilla/firefox (usually ~/.config/mozilla/firefox) when the legacy tree is absent, so those installs are reported as notInstalled and never importable on Linux.

Reviewed by Cursor Bugbot for commit 33435655377e6cd6f406368baff28613d4c7308c. Configure here.

MmacroscopeappBotreviewed · 1 inline threadAug 16, 2026
Synced from GitHub

Effect service conventions: one finding on the new FirefoxCookieReadError. The tagged-error union fix from the previous round looks good; what remains is the structural context the error captures.

Posted via Macroscope — Effect Service Conventions

apps/desktop/src/preview/BrowserImport/FirefoxCookies.tslines 33–44Outdated · 56f1705Open thread
……@@ -0,0 +1,109 @@
41+ override get message(): string {
42+ return `Could not read Firefox cookies: ${this.reason}.`;
43+ }
44+}
MmacroscopeappBoton lines 33–44Aug 16, 2026

BrowserImport/FirefoxCookies.ts:44

FirefoxCookieReadError ends up carrying no variable context: reason has a single literal value, so it only restates what the tag already says, and the cookieDatabasePath that is in scope at both wrapping sites is dropped — ChromiumCookieReadError keeps that field precisely so a user with several browsers can tell which read failed. cause is also optional even though every construction here wraps a real failure.

Consider dropping the singleton reason, adding a required cookieDatabasePath: Schema.String plus a required cause: Schema.Defect(), and deriving message from the path. BrowserImport.importCookies would then map the two engines with Effect.catchTags (supplying reason: "readFailed" for the Firefox tag) instead of reading cause.reason off the union.

No longer relevant as of 7a8cf253130c2b6b13101e72a3b6b113077ac98c

apps/desktop/src/preview/BrowserImport/Sources.tsline 200Outdated · 7a8cf25Open thread
……@@ -1,62 +1,275 @@
197+ const fileName = definition.engine === "firefox" ? "cookies.sqlite" : "Cookies";
198+ return context.path.isAbsolute(profileDirectory)
199+ ? context.path.join(profileDirectory, fileName)
200+ : context.path.join(root, profileDirectory, fileName);
CcursorBoton line 200Aug 16, 2026

Wrong Chromium cookie database path

High Severity

cookieDatabasePath always joins the Chromium profile directory with Cookies, but current Chrome, Edge, Brave, Vivaldi, and Arc keep the live database under Network/Cookies. Helium (and possibly some Opera builds) still use the legacy root path, so a single hardcoded filename breaks import for the newly added mainstream forks—either as readFailed, or as a silent zero-cookie success if a stale empty root Cookies file remains.

Reviewed by Cursor Bugbot for commit 7a8cf253130c2b6b13101e72a3b6b113077ac98c. Configure here.

Synced from GitHub

One retained finding in the Firefox error model; the rest of the Effect service conventions look satisfied (namespace subpath imports, tagged errors at the failure boundary, Context.Service/make/layer order preserved, no new runtimes or injected service instances).

Posted via Macroscope — Effect Service Conventions

apps/desktop/src/preview/BrowserImport/BrowserImport.tslines 186–191Outdated · 7487b3cOpen thread
……@@ -144,17 +152,45 @@ export const make = Effect.gen(function* BrowserImportMake() {
188+ // Firefox has one failure mode — its plaintext database would not
189+ // open — so its error carries no reason of its own and the
190+ // user-facing one is supplied here.
191+ reason: cause._tag === "FirefoxCookieReadError" ? "readFailed" : cause.reason,
MmacroscopeappBoton lines 186–191Aug 17, 2026

BrowserImport/BrowserImport.ts:191

Both failures are statically known tagged errors, so the branch belongs in Effect.catchTags rather than a hand-rolled cause._tag ternary inside mapError. That also keeps each handler's reason next to the tag it applies to.

The comment above read ("Both branches fail with a tagged error carrying a reason") is now stale too — FirefoxCookieReadError deliberately has no reason.

diff
1 const cookies = yield* read.pipe(
2 Effect.scoped,
3 Effect.provide(platformServices),
4- Effect.mapError(
5- (cause) =>
6- new BrowserImportFailedError({
7- sourceId: definition.id,
8- reason: cause._tag === "FirefoxCookieReadError" ? "readFailed" : cause.reason,
9- cause,
10- }),
11- ),
12+ Effect.catchTags({
13+ // Firefox has one failure mode — its plaintext database would not
14+ // open — so the user-facing reason is supplied here.
15+ FirefoxCookieReadError: (cause) =>
16+ new BrowserImportFailedError({ sourceId: definition.id, reason: "readFailed", cause }),
17+ ChromiumCookieReadError: (cause) =>
18+ new BrowserImportFailedError({ sourceId: definition.id, reason: cause.reason, cause }),
19+ }),
20 );

No longer relevant as of 8538b6cede1f0d126bce980caf52b2dda2e9c359

apps/desktop/src/preview/BrowserImport/Sources.tsline 328Outdated · 18520a8Open thread
……@@ -79,26 +295,44 @@ const isSafeProfileDirectory = (directory: string): boolean =>
325+ entries.map((entry) => ({ directory: context.path.join("Profiles", entry), name: entry })),
326+ ),
327+ Effect.orElseSucceed(() => [] as ReadonlyArray<BrowserImportSourceProfile>),
328+ );
CcursorBoton line 328Aug 17, 2026

Firefox fallback lists empty profiles

Low Severity

When profiles.ini is missing or unreadable, the Firefox fallback lists every entry under Profiles/ without checking for cookies.sqlite. The Chromium fallback only keeps directories that actually hold a cookie database, so Firefox can surface phantom profiles that later fail with readFailed.

Reviewed by Cursor Bugbot for commit 18520a83abfea8f7c05fcf2c7f85bcd885b9f4b3. Configure here.

apps/desktop/src/preview/BrowserImport/Sources.tsline 413Outdated · 11c7e86Open thread
……@@ -153,58 +361,56 @@ export const listSourceProfiles = Effect.fn("BrowserImportSources.listSourceProf
410+ entryExists(context.path.join(directory, lock)),
411+ ).pipe(Effect.map((results) => results.some(Boolean)));
412+ });
413+ return found.some(Boolean);
CcursorBoton line 413Aug 28, 2026

Firefox lock files block all imports

High Severity

isSourceRunning treats existence of .parentlock or parent.lock as Firefox being open. Firefox leaves those files behind after quit so it can read last-used time; only the Unix lock symlink is removed. After a profile has been used once, listing and import both fail with browserRunning, so Firefox import cannot succeed.

Reviewed by Cursor Bugbot for commit 11c7e86a3d4d784ae486b9a3acc55acd577dc5f8. Configure here.

apps/desktop/src/preview/BrowserImport/Sources.tsline 153Outdated · 3cac053Open thread
……@@ -1,64 +1,257 @@
150+ engine: "firefox",
151+ platforms: ["darwin", "win32", "linux"],
152+ userDataDirectory: (context) => {
153+ if (context.platform === "darwin") return macApplicationSupport(context, "Firefox");
MmacroscopeappBoton line 153Aug 28, 2026

🟡 Medium BrowserImport/Sources.ts:153

Firefox profiles returned from listSourceProfiles never receive a cookieCount, so the wizard shows an unknown count even when cookies.sqlite is readable. The declared-profile branch returns before calling withCookieCounts; pass those profiles through the same counting step used for Chromium.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/Sources.ts around line 153:
2
3Firefox profiles returned from `listSourceProfiles` never receive a `cookieCount`, so the wizard shows an unknown count even when `cookies.sqlite` is readable. The declared-profile branch returns before calling `withCookieCounts`; pass those profiles through the same counting step used for Chromium.

Fixed in the updated #7260. Declared and fallback Firefox profiles receive cookie counts from moz_cookies filtered to originAttributes = empty string, matching the importer’s default-container policy. Unreadable schemas retain an unknown count. Fixtures cover all paths.

apps/desktop/src/preview/BrowserImport/Sources.tsline 160Outdated · 5622018Open thread
……@@ -1,64 +1,257 @@
157+ : undefined;
158+ }
159+ return context.path.join(context.home, ".mozilla", "firefox");
160+ },
CcursorBoton line 160Aug 28, 2026

Linux Firefox path misses Snap installs

Medium Severity

Linux Firefox resolution only looks at ~/.mozilla/firefox. Default Ubuntu Snap profiles live under ~/snap/firefox/common/.mozilla/firefox (and Flatpak under ~/.var/app/org.mozilla.firefox/.mozilla/firefox), so the source is reported notInstalled on the most common Linux desktop even when Firefox is present.

Reviewed by Cursor Bugbot for commit 56220187d6f51dc4eb89e5772a328c4934314390. Configure here.

apps/desktop/src/preview/BrowserImport/Sources.tslines 342–347Outdated · c2131c3Open thread
……@@ -91,59 +284,74 @@ const decodeCookieCount = Schema.decodeUnknownEffect(Schema.Array(CookieCountRow
344+ entries.map((entry) => ({ directory: context.path.join("Profiles", entry), name: entry })),
345+ ),
346+ Effect.orElseSucceed(() => [] as ReadonlyArray<BrowserImportSourceProfile>),
347+ );
MmacroscopeappBoton lines 342–347Aug 29, 2026

🟠 High BrowserImport/Sources.ts:342

When profiles.ini is missing or malformed, the Firefox fallback presents every Profiles entry, including stale profiles and files, as selectable. Selecting one without cookies.sqlite then reaches readFirefoxCookies and fails instead of omitting the profile. Filter the fallback entries by checking for their cookies.sqlite database, as the Chromium fallback does.

diff
1- return yield* fileSystem.readDirectory(context.path.join(root, "Profiles")).pipe(
2- Effect.map((entries) =>
3- entries.map((entry) => ({ directory: context.path.join("Profiles", entry), name: entry })),
4- ),
5- Effect.orElseSucceed(() => [] as ReadonlyArray<BrowserImportSourceProfile>),
6- );
7+ return yield* fileSystem.readDirectory(context.path.join(root, "Profiles")).pipe(
8+ Effect.flatMap((entries) =>
9+ Effect.forEach(entries, (entry) =>
10+ entryExists(context.path.join(root, "Profiles", entry, "cookies.sqlite")).pipe(
11+ Effect.map((exists) => exists
12+ ? { directory: context.path.join("Profiles", entry), name: entry }
13+ : undefined),
14+ )
15+ )
16+ ),
17+ Effect.map((profiles) => profiles.filter((profile) => profile !== undefined)),
18+ Effect.orElseSucceed(() => [] as ReadonlyArray<BrowserImportSourceProfile>),
19+ );
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/Sources.ts around lines 342-347:
2
3When `profiles.ini` is missing or malformed, the Firefox fallback presents every `Profiles` entry, including stale profiles and files, as selectable. Selecting one without `cookies.sqlite` then reaches `readFirefoxCookies` and fails instead of omitting the profile. Filter the fallback entries by checking for their `cookies.sqlite` database, as the Chromium fallback does.
……@@ -0,0 +1,112 @@
72+export const readFirefoxCookies = Effect.fn("FirefoxCookies.readFirefoxCookies")(function* (
73+ cookieDatabasePath: string,
74+) {
75+ const snapshotPath = yield* snapshotCookieDatabase(cookieDatabasePath).pipe(

🟠 High BrowserImport/FirefoxCookies.ts:75

readFirefoxCookies can fail or silently omit committed cookies when Firefox writes or checkpoints between the separate copies performed by snapshotCookieDatabase. Because the main database and -wal sidecar can come from different SQLite generations, the later read is not a consistent snapshot; use SQLite's backup/read-transaction facility (or otherwise hold a stable snapshot) instead of independent file copies.

Also found in 1 other location(s)

apps/desktop/src/preview/BrowserImport/CookieDatabase.ts:77

snapshotCookieDatabase does not take a consistent SQLite snapshot when a browser starts (or is already writing) between the earlier running-state check and these copies: it copies the main database at line 77 and the WAL later at line 83. A concurrent checkpoint can change the database/WAL boundary between those reads, so SQLite may open a mismatched pair and either fail the import or silently omit committed cookies. The lock preflight cannot close this time-of-check/time-of-use race; use SQLite's backup/snapshot facility or acquire a stable read transaction before copying.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 75:
2
3`readFirefoxCookies` can fail or silently omit committed cookies when Firefox writes or checkpoints between the separate copies performed by `snapshotCookieDatabase`. Because the main database and `-wal` sidecar can come from different SQLite generations, the later read is not a consistent snapshot; use SQLite's backup/read-transaction facility (or otherwise hold a stable snapshot) instead of independent file copies.
4
5Also found in 1 other location(s):
6- apps/desktop/src/preview/BrowserImport/CookieDatabase.ts:77 -- `snapshotCookieDatabase` does not take a consistent SQLite snapshot when a browser starts (or is already writing) between the earlier running-state check and these copies: it copies the main database at line 77 and the WAL later at line 83. A concurrent checkpoint can change the database/WAL boundary between those reads, so SQLite may open a mismatched pair and either fail the import or silently omit committed cookies. The lock preflight cannot close this time-of-check/time-of-use race; use SQLite's backup/snapshot facility or acquire a stable read transaction before copying.
apps/desktop/src/preview/BrowserImport/Sources.tslines 342–342Outdated · fcc9630Open thread
……@@ -91,59 +284,74 @@ const decodeCookieCount = Schema.decodeUnknownEffect(Schema.Array(CookieCountRow
339+
340+ // No readable `profiles.ini`, so fall back to scanning the directory the
341+ // profiles actually live in.
342+ return yield* fileSystem.readDirectory(context.path.join(root, "Profiles")).pipe(
MmacroscopeappBoton lines 342–342Aug 29, 2026

🟠 High BrowserImport/Sources.ts:342

On Linux, the Firefox fallback returns no profiles even when cookies.sqlite exists, so isSourceInstalled reports Firefox as absent and the browser cannot be imported. The fallback always scans <root>/Profiles, but Linux profiles are direct children of <root>; scan <root> on Linux instead.

diff
1- return yield* fileSystem.readDirectory(context.path.join(root, "Profiles")).pipe(
2+ const profilesRoot = context.platform === "linux" ? root : context.path.join(root, "Profiles");
3+ return yield* fileSystem.readDirectory(profilesRoot).pipe(
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/Sources.ts around line 342:
2
3On Linux, the Firefox fallback returns no profiles even when `cookies.sqlite` exists, so `isSourceInstalled` reports Firefox as absent and the browser cannot be imported. The fallback always scans `<root>/Profiles`, but Linux profiles are direct children of `<root>`; scan `<root>` on Linux instead.

Fixed in the updated #7260. Linux Firefox fallback scans the root directory; macOS and Windows retain Profiles/. Fixtures cover the platform layouts and require cookies.sqlite.

apps/desktop/src/preview/BrowserImport/ChromiumCookies.tslines 278–281Outdated · ba4ce16Open thread
……@@ -343,13 +278,6 @@ export const readChromiumCookieDatabase = Effect.fn("ChromiumCookies.readChromiu
350- readonly undecryptableHosts: ReadonlyArray<string>;
351-}
352-
353281export interface ChromiumCookieSource {
MmacroscopeappBoton lines 278–281Aug 29, 2026

This doc block described CookieReadResult, which moved to CookieDatabase.ts; it stayed behind and now reads as documentation for ChromiumCookieSource, which it does not describe. Consider deleting it here and folding the "count reaches the user as part of the skipped total" note into CookieReadResult's (currently one-line) comment in CookieDatabase.ts.

diff
1-/**
2- * What a reader produces: the cookies it could recover, and how many stored
3- * rows it could not. The count reaches the user as part of the skipped total
4- * rather than disappearing.
5- */
6 export interface ChromiumCookieSource {

Posted via Macroscope — Effect Service Conventions

apps/desktop/src/preview/BrowserImport/Sources.tslines 536–540Outdated · d4849a9Open thread
……@@ -280,11 +531,12 @@ export const isSourceRunning = Effect.fn("BrowserImportSources.isSourceRunning")
537+ const found = yield* Effect.forEach(profiles, (profile) => {
538+ const database = cookieDatabasePath(definition, context, profile.directory);
539+ return database === undefined ? Effect.succeed(false) : entryExists(database);
540+ });
MmacroscopeappBoton lines 536–540Aug 29, 2026

🟠 High BrowserImport/Sources.ts:536

isSourceInstalled returns true for a Firefox profile whose cookies.sqlite is only a dangling symlink, so the source is shown as importable but selecting it later fails when the reader opens the missing target. Use a target-following stat check for Firefox databases while retaining entryExists for the Safari/TCC case.

diff
1 const profiles = yield* listSourceProfiles(definition, context);
2+ const fileSystem = yield* FileSystem.FileSystem;
3- const found = yield* Effect.forEach(profiles, (profile) => {
4+ const found = yield* Effect.forEach(profiles, (profile) => {
5 const database = cookieDatabasePath(definition, context, profile.directory);
6- return database === undefined ? Effect.succeed(false) : entryExists(database);
7+ return database === undefined
8+ ? Effect.succeed(false)
9+ : definition.engine === "firefox"
10+ ? fileSystem.stat(database).pipe(Effect.as(true), Effect.orElseSucceed(() => false))
11+ : entryExists(database);
12 });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/Sources.ts around lines 536-540:
2
3`isSourceInstalled` returns `true` for a Firefox profile whose `cookies.sqlite` is only a dangling symlink, so the source is shown as importable but selecting it later fails when the reader opens the missing target. Use a target-following `stat` check for Firefox databases while retaining `entryExists` for the Safari/TCC case.
apps/desktop/src/preview/BrowserImport/Sources.tsline 519Outdated · f6a437dOpen thread
……@@ -237,28 +487,36 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
516+ entryExists(context.path.join(directory, lock)),
517+ ).pipe(Effect.map((results) => results.some(Boolean)));
518+ });
519+ return found.some(Boolean);
CcursorBoton line 519Sep 2, 2026

Firefox lock files false-positive running

High Severity

isSourceRunning treats the presence of .parentlock or parent.lock as proof Firefox still holds the profile. Firefox leaves those files on disk after a clean exit and only releases the OS lock (fcntl/flock or a Windows handle). After Firefox has been used once, the source stays browserRunning and cookie import never proceeds.

Reviewed by Cursor Bugbot for commit f6a437d70cf414f2749037e2062edb39956e4374. Configure here.

Fixed in 1ce9a337af. Verified against Firefox's nsProfileLock.cpp: .parentlock/parent.lock are deliberately left on disk after a clean exit (as a last-used marker) and only the OS-level fcntl/handle lock is released, so their presence is not evidence. Only the Linux lock symlink (target <ip>:[+]<pid>) is unlinked on exit — that plus a live pid is now the POSIX signal, and a dead pid means a crash. Node exposes no fcntl probe, so .parentlock carries no signal on POSIX; on Windows the held handle denies our open as Busy, which is the signal there. Regression tests cover the stale-file, live-symlink, and crashed-pid cases.

apps/desktop/src/preview/BrowserImport/FirefoxCookies.tslines 108–108Outdated · b5a0628Open thread
……@@ -0,0 +1,112 @@
105+ path: row.path,
106+ secure,
107+ httpOnly: row.isHttpOnly === 1,
108+ expirationDate: row.expiry > 0 ? row.expiry : undefined,
MmacroscopeappBoton lines 108–108Sep 2, 2026

🟠 High BrowserImport/FirefoxCookies.ts:108

Persistent Firefox cookies are imported with expiration timestamps about 1000× too far in the future because row.expiry is stored in milliseconds while Electron's expirationDate expects seconds. Convert row.expiry to seconds before assigning it.

suggestion
1 expirationDate: row.expiry > 0 ? row.expiry / 1000 : undefined,
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 108:
2
3Persistent Firefox cookies are imported with expiration timestamps about 1000× too far in the future because `row.expiry` is stored in milliseconds while Electron's `expirationDate` expects seconds. Convert `row.expiry` to seconds before assigning it.

Fixed in 5b13dd381e. Verified against Firefox's CookiePersistentStorage.cpp: schema 16 (Firefox 129) migrated expiry to milliseconds (UPDATE moz_cookies SET expiry = expiry * 1000), while older profiles still hold seconds — so the reader now reads PRAGMA user_version and divides only for schema ≥16, rather than assuming either unit. Regression test covers both a schema-16 and a schema-15 database.

apps/desktop/src/preview/BrowserImport/Sources.tslines 555–558Outdated · 1ce9a33Open thread
……@@ -250,28 +492,104 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
555+ if (context.platform === "win32") {
556+ return yield* isLockHeld(context.path.join(directory, "parent.lock"), context.platform);
557+ }
558+ return yield* fileSystem.readLink(context.path.join(directory, "lock")).pipe(
MmacroscopeappBoton lines 555–558Sep 2, 2026

🟠 High BrowserImport/Sources.ts:555

On macOS, firefoxProfileIsHeld returns false while Firefox is running, so unavailableReason allows importing a live, mid-write cookie database. The non-Windows branch only reads lock, but macOS uses .parentlock; handle the macOS lock marker in this branch.

diff
1 if (context.platform === "win32") {
2 return yield* isLockHeld(context.path.join(directory, "parent.lock"), context.platform);
3 }
4+ if (context.platform === "darwin") {
5+ return yield* entryExists(context.path.join(directory, ".parentlock"));
6+ }
7 return yield* fileSystem.readLink(context.path.join(directory, "lock")).pipe(
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/Sources.ts around lines 555-558:
2
3On macOS, `firefoxProfileIsHeld` returns `false` while Firefox is running, so `unavailableReason` allows importing a live, mid-write cookie database. The non-Windows branch only reads `lock`, but macOS uses `.parentlock`; handle the macOS lock marker in this branch.
apps/desktop/src/preview/BrowserImport/Sources.tslines 504–505Outdated · 341268fOpen thread
……@@ -250,28 +491,167 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
502+ * reveals an active holder, which surfaces as `Busy`.
503+ */
504+const isLockHeld = Effect.fnUntraced(function* (lockPath: string, platform: NodeJS.Platform) {
505+ if (platform !== "win32") return yield* entryExists(lockPath);
MmacroscopeappBoton lines 504–505Sep 2, 2026

isLockHeld's only caller guards on win32 (line 606), so the platform !== "win32" branch never runs — and it is the last remaining use of entryExists, whose documented rationale (the dangling Chromium SingletonLock and Firefox lock symlinks) no longer matches any caller either, since both probes now call readLink directly.

Consider narrowing this to the Windows probe its doc block describes — drop the platform parameter and the early return — and deleting entryExists along with it.

Posted via Macroscope — Effect Service Conventions

Fixed in ba690c491a. Narrowed to windowsLockIsHeld (no platform parameter, no dead POSIX branch) and deleted entryExists, which that branch was the last use of.

🟡 Medium

https://github.com/pingdotgg/t3code/blob/341268fce7fd31b07c92b124226c7e1323573cec/apps/desktop/src/preview/BrowserImport/BrowserImport.ts#L204

Every Firefox import logs Reading browser cookie key from the keychain and includes executablePath, even though Firefox uses readFirefoxCookies and never accesses a keychain. Move this log into the Chromium branch so support and audit logs do not report a false security-sensitive operation.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/BrowserImport.ts around line 204:
2
3Every Firefox import logs `Reading browser cookie key from the keychain` and includes `executablePath`, even though Firefox uses `readFirefoxCookies` and never accesses a keychain. Move this log into the Chromium branch so support and audit logs do not report a false security-sensitive operation.
apps/desktop/src/preview/BrowserImport/Sources.tsline 514Outdated · ba690c4Open thread
……@@ -235,28 +472,166 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
511+ // — nor could a reused local pid vouch for it. Anything but loopback stays
512+ // conservatively held.
513+ const owner = target.slice(0, separator);
514+ if (owner !== "127.0.0.1" && owner !== "::1") return true;
CcursorBoton line 514Sep 2, 2026

Local Firefox locks treated as foreign

Medium Severity

firefoxSymlinkLockIsHeld treats only 127.0.0.1 and ::1 as local owners. Firefox writes the IP it resolved for the machine hostname, which is often 127.0.1.1, 127.0.0.2, or a LAN address. A leftover lock symlink after a crash then stays held and blocks import.

Reviewed by Cursor Bugbot for commit ba690c491aaf5852c02a5f0b91a1c2edbeb9457a. Configure here.

Fixed in 8360b0f6ca. Verified against nsProfileLock.cpp: the address is whatever PR_GetHostByName returns for the hostname, falling back to 127.0.0.1 only when the lookup fails — so 127.0.1.1 and LAN addresses are routine. The owner is now matched against every address the machine answers to (new injectable HostProcessAddresses, mirroring HostProcessHostname), so a crashed Firefox on such a host no longer leaves a permanently-held lock. Tests cover loopback, 127.0.1.1, a LAN address, dead pids on each, and a genuinely foreign owner.

CcursorBotreviewed · 2 inline threadsSep 2, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 8360b0f6ca4aac215cbc9e119bf1601999f75205. Configure here.

packages/shared/src/hostProcess.tsline 70Outdated · 8360b0fOpen thread
……@@ -51,6 +51,24 @@ export const HostProcessArguments = Context.Reference<ReadonlyArray<string>>(
67+ .flatMap((entry) => (entry ? [entry.address] : [])),
68+ ),
69+ },
70+);
CcursorBoton line 70Sep 2, 2026

Hostname IPs treated as foreign locks

Medium Severity

HostProcessAddresses only collects addresses from os.networkInterfaces(), so Debian-style hostname IPs such as 127.0.1.1 never count as local. firefoxSymlinkLockIsHeld then treats that owner as foreign and reports the profile held without checking the pid, so a leftover lock symlink after a crash blocks Firefox import indefinitely.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit 8360b0f6ca4aac215cbc9e119bf1601999f75205. Configure here.

Fixed in 1f8e906e7f. HostProcessAddresses now unions the interface addresses with whatever the resolver returns for the machine's own hostname — which is exactly what Firefox writes into the lock — so a /etc/hosts mapping like 127.0.1.1 counts as local. It's an Effect now (the lookup is async), still injectable for tests.

apps/desktop/src/preview/BrowserImport/Sources.tsline 564Outdated · 8360b0fOpen thread
……@@ -233,28 +471,169 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
561+ if (Number(exitCode) !== 0) return true;
562+ return stdout.trim() !== "free";
563+ }),
564+ ).pipe(Effect.orElseSucceed(() => true));
CcursorBoton line 564Sep 2, 2026

macOS Firefox import needs python3

High Severity

macOS Firefox running detection always probes .parentlock with a python3 child. That file is left on disk after a clean exit, and any probe failure — including a missing interpreter — counts as held. Packaged apps launched from the Dock rarely have python3 on PATH, so Firefox import stays blocked after the browser has been used once.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit 8360b0f6ca4aac215cbc9e119bf1601999f75205. Configure here.

Fixed in 1f8e906e7f. Two changes: /usr/bin/python3 is named absolutely first so launchd's bare PATH still finds it; and "held" is now only ever the script's own printed verdict. A probe that never ran — no interpreter on any candidate, or Apple's shim on a Mac without the developer tools (exits non-zero after an install prompt, never reaching the script) — is the probe being unavailable, not evidence about the lock, and falls back to not-held rather than blocking Firefox import on that machine for good; the SQLite snapshot copes with a live WAL as it does for every other engine. Tests cover a real fcntl holder, a missing interpreter, and a shim-like non-zero exit.

apps/desktop/src/preview/BrowserImport/Sources.tslines 172–174Outdated · 1f8e906Open thread
……@@ -1,87 +1,292 @@
56171
172+/**
57173/**
58174 * Where a profile's cookie database may live, most current first. Chromium 96
MmacroscopeappBoton lines 172–174Sep 2, 2026

A stray second /** opener was introduced above this doc block while the registry was reworked, so the comment now starts twice. Consider dropping the extra line.

suggestion
1/**
2 * Where a profile's cookie database may live, most current first. Chromium 96

Posted via Macroscope — Effect Service Conventions

……@@ -0,0 +1,131 @@
52+const sameSiteFromColumn = (value: number): ImportedCookie["sameSite"] => {
53+ if (value === 0) return "no_restriction";
54+ if (value === 2) return "strict";
55+ return "lax";
MmacroscopeappBoton line 55Sep 2, 2026

🟠 High BrowserImport/FirefoxCookies.ts:55

Cookies with Firefox’s valid SAMESITE_UNSET value (0) are imported as sameSite: "no_restriction", changing ordinary cookies with no SameSite attribute to cross-site unrestricted cookies. Map SAMESITE_UNSET to Electron’s "unspecified" value instead, and update ImportedCookie to allow that value.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 55:
2
3Cookies with Firefox’s valid `SAMESITE_UNSET` value (`0`) are imported as `sameSite: "no_restriction"`, changing ordinary cookies with no `SameSite` attribute to cross-site unrestricted cookies. Map `SAMESITE_UNSET` to Electron’s `"unspecified"` value instead, and update `ImportedCookie` to allow that value.
……@@ -0,0 +1,139 @@
58+
59+const sameSiteFromColumn = (value: number): ImportedCookie["sameSite"] => {
60+ if (value === SAMESITE_NONE) return "no_restriction";
61+ if (value === SAMESITE_LAX) return "lax";
MmacroscopeappBoton line 61Sep 2, 2026

🟡 Medium BrowserImport/FirefoxCookies.ts:61

Pre-15 Firefox profiles import cookies with sameSite = 1 and rawSameSite = 0 as Electron lax, even though those rows had no SameSite attribute and must be imported as unspecified. sameSiteFromColumn ignores rawSameSite, so importing a schema 10–14 database before Firefox runs its schema-15 migration changes the cookies’ SameSite semantics. Read rawSameSite and apply the migration’s SAMESITE_UNSET rule before mapping the value.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 61:
2
3Pre-15 Firefox profiles import cookies with `sameSite = 1` and `rawSameSite = 0` as Electron `lax`, even though those rows had no SameSite attribute and must be imported as `unspecified`. `sameSiteFromColumn` ignores `rawSameSite`, so importing a schema 10–14 database before Firefox runs its schema-15 migration changes the cookies’ SameSite semantics. Read `rawSameSite` and apply the migration’s `SAMESITE_UNSET` rule before mapping the value.

Confirmed and fixed in 89623849ea. The reader now selects rawSameSite only when user_version is 10–14 (the column does not exist outside that range) and applies Firefox's own schema-15 migration rule: sameSite = LAX && rawSameSite = NONE → unspecified. Test covers defaulted-Lax, declared-Lax and explicit-None rows on a schema-14 fixture.

……@@ -0,0 +1,139 @@
75+ isHttpOnly: Schema.Number,
76+ sameSite: Schema.Number,
77+});
78+const decodeCookieRows = Schema.decodeUnknownEffect(Schema.Array(CookieRow));
MmacroscopeappBoton line 78Sep 2, 2026

🟠 High BrowserImport/FirefoxCookies.ts:78

Reading a valid Firefox schema-9 database fails for the entire cookie set when any pre-existing cookie has sameSite = NULL, so no cookies are imported. The schema-9 migration adds sameSite without a default, and decodeCookieRows requires every value to be a number; allow NULL and normalize it to "unspecified" (for example, with COALESCE in the query or in the decoder).

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 78:
2
3Reading a valid Firefox schema-9 database fails for the entire cookie set when any pre-existing cookie has `sameSite = NULL`, so no cookies are imported. The schema-9 migration adds `sameSite` without a default, and `decodeCookieRows` requires every value to be a `number`; allow `NULL` and normalize it to `"unspecified"` (for example, with `COALESCE` in the query or in the decoder).
apps/desktop/src/preview/BrowserImport/Sources.tslines 628–628Outdated · dc81cdaOpen thread
……@@ -254,28 +487,195 @@ export const chromiumSingletonLockIsHeld = Effect.fnUntraced(function* (
625+ context: BrowserImportPathContext,
626+) {
627+ const fileSystem = yield* FileSystem.FileSystem;
628+ const localAddresses = yield* yield* HostProcessAddresses;
MmacroscopeappBoton lines 628–628Sep 2, 2026

HostProcessAddresses now holds an unmemoized Effect, so this yield* yield* re-runs the hostname resolution on every call — isSourceRunning invokes firefoxProfileIsHeld once per profile inside Effect.forEach, so a profile list of n costs n DNS lookups. It also runs before the win32 early return, where windowsLockIsHeld never uses the set.

Consider acquiring the addresses once in isSourceRunning and passing them into firefoxProfileIsHeld, the way firefoxSymlinkLockIsHeld already takes localAddresses as a parameter; that also removes the yield* yield* from the per-profile path.

Posted via Macroscope — Effect Service Conventions

Fixed in a0bec9dd1c: isSourceRunning resolves HostProcessAddresses once (and not at all on win32, where only parent.lock is consulted) and passes the set into firefoxProfileIsHeld, so the hostname lookup no longer runs per profile.

apps/desktop/src/preview/BrowserImport/FirefoxCookies.tslines 128–136Outdated · a0bec9dOpen thread
……@@ -0,0 +1,169 @@
133+ const hasRawSameSite =
134+ schemaVersion >= FIREFOX_RAW_SAMESITE_FIRST_SCHEMA &&
135+ schemaVersion <= FIREFOX_RAW_SAMESITE_LAST_SCHEMA;
136+ const raw = hasRawSameSite
MmacroscopeappBoton lines 128–136Sep 2, 2026

The default-container rationale now heads the hasRawSameSite computation, which it does not describe — the where originAttributes = '' predicate it explains moved down to the queries below. Consider keeping it next to that predicate.

suggestion
1 const hasRawSameSite =
2 schemaVersion >= FIREFOX_RAW_SAMESITE_FIRST_SCHEMA &&
3 schemaVersion <= FIREFOX_RAW_SAMESITE_LAST_SCHEMA;
4 // Only the default container. Firefox isolates cookies per container and
5 // per private window via `originAttributes` (`^userContextId=2`,
6 // `^privateBrowsingId=1`); Electron has no equivalent, so importing them
7 // all would collapse several identities onto one host/name/path and hand
8 // the profile an arbitrary container's session.
9 const raw = hasRawSameSite

Posted via Macroscope — Effect Service Conventions

Synced from GitHub

Macroscope skipped reviewing this pull request. Per-PR cost limit exceeded (workspace setting).

Reviews on this PR have cost $47.31 so far. This review would add an estimated $3.00, bringing the total to $50.31 — above your per-PR limit of $50.00.

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude large or generated files from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

Added 23 commits

  • refactor(desktop): give the Firefox read path a tagged error0e0133b
  • refactor(desktop): share browser cookie result types0f057ea
  • refactor(desktop): drop the redundant reason from the Firefox read error187b43d
  • fix(desktop): count Firefox profile cookies26aba8d
  • fix(desktop): detect a running Firefox and keep containers apart3656866
  • refactor(desktop): handle the cookie read failures by tag428d226
  • feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc, Firefox4f6fb87
  • docs(desktop): describe shared cookie read results604ded8
  • fix(desktop): validate Firefox profile paths9266fc1
  • fix(desktop): find Linux Firefox profilesb188689
  • refactor(desktop): name the database a Firefox read failed onc5d326b
  • fix(desktop): import an unset SameSite as unspecified, not none02e3fc0
  • chore(desktop): keep the container comment beside its predicate0cf1076
  • fix(desktop): stop reading Firefox's leftover lock files as a running browser266ec2a
  • fix(desktop): read Firefox SameSite from every schema5ddf1b8
  • fix(desktop): follow browser database symlinks7bc002c
  • fix(desktop): find the fcntl probe from a Dock launch, and count hostname-resolved addresses as local92bfdf8
  • fix(desktop): log the keychain read only for Chromium, and narrow the Windows lock probea220676
  • fix(desktop): convert Firefox cookie expiries by schema versiona45827c
  • perf(desktop): resolve host addresses once per Firefox lock scanb37548a
  • fix(desktop): recognise any local address as the owner of a Firefox lock symlinkb42d1a8
  • fix(desktop): probe Firefox's fcntl lock so a running macOS Firefox is detectedc44e959
  • fix(desktop): discover Firefox Snap profiles for browser importeea4224