@askjo/camofox-browser 1.9.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Dockerfile CHANGED
@@ -58,6 +58,7 @@ RUN --mount=type=bind,source=dist,target=/dist \
58
58
  WORKDIR /app
59
59
 
60
60
  COPY package.json ./
61
+ COPY scripts/ ./scripts/
61
62
  RUN npm install --production
62
63
 
63
64
  COPY server.js ./
package/lib/reporter.js CHANGED
@@ -593,6 +593,20 @@ function formatIssueBody(type, detail) {
593
593
  }
594
594
 
595
595
  // Context (misc extra data)
596
+ if (detail.nativeMemory) {
597
+ const nm = detail.nativeMemory;
598
+ sections.push('', '## Native Memory Details');
599
+ sections.push(`- **baseline:** ${nm.baselineMb} MB`);
600
+ sections.push(`- **current:** ${nm.currentMb} MB`);
601
+ sections.push(`- **high-water:** ${nm.highWaterMb} MB`);
602
+ sections.push(`- **growth:** ${nm.growthMb} MB`);
603
+ sections.push(`- **node RSS:** ${nm.rssMb} MB`);
604
+ sections.push(`- **heap used:** ${nm.heapUsedMb} MB`);
605
+ sections.push(`- **external:** ${nm.externalMb} MB`);
606
+ if (nm.lastSeenBrowserRssMb != null) sections.push(`- **browser RSS (last seen):** ${nm.lastSeenBrowserRssMb} MB`);
607
+ else sections.push(`- **browser RSS (last seen):** not captured (browser already dead)`);
608
+ }
609
+
596
610
  if (detail.context && Object.keys(detail.context).length > 0) {
597
611
  sections.push('', '<details><summary>Context</summary>', '', '```json', anonymize(JSON.stringify(detail.context, null, 2)), '```', '', '</details>');
598
612
  }
@@ -806,22 +820,26 @@ export function createReporter(config) {
806
820
 
807
821
  // --- Native memory leak tracking ---
808
822
  // Track RSS minus JS heap over time to detect native/external memory leaks.
809
- // Sample every 30s, alert if native memory stays >200MB above baseline for
823
+ // Sample every 30s, alert if native memory stays >400MB above baseline for
810
824
  // 3 consecutive checks (~90s). This avoids false positives from:
811
825
  // - Browser initialization spikes (first 2 min)
812
826
  // - One-time allocations that stabilize
813
827
  // - Post-session RSS that hasn't been reclaimed by the OS yet
828
+ // - Self-healing restart (kills browser at 200MB growth when sessions=0)
829
+ // The memory pressure restart in server.js fires at 200MB when idle.
830
+ // We only report at 400MB to catch cases where self-healing FAILED.
814
831
  let nativeMemBaseline = null; // RSS - heapUsed at first measurement
815
832
  let nativeMemHighWater = 0;
816
833
  let lastNativeMemCheck = 0;
817
834
  const NATIVE_MEM_CHECK_INTERVAL_MS = 30_000;
818
- const NATIVE_MEM_LEAK_THRESHOLD_MB = 200; // alert if native mem exceeds baseline by this much
835
+ const NATIVE_MEM_LEAK_THRESHOLD_MB = 400; // alert only when growth exceeds self-healing threshold
819
836
  const NATIVE_MEM_MIN_UPTIME_S = 120; // don't measure until process has been up 2 min
820
837
  const NATIVE_MEM_CONSECUTIVE_REQUIRED = 3; // require 3 consecutive checks above threshold
821
838
  const NATIVE_MEM_GRACE_CHECKS = 2; // skip 2 checks after baseline reset (let memory settle)
822
839
  let nativeMemAlertFired = false;
823
840
  let nativeMemConsecutiveAbove = 0; // consecutive checks above threshold
824
841
  let nativeMemGraceRemaining = 0; // checks to skip after baseline reset
842
+ let lastSeenBrowserRssMb = null; // captured during growth checks while browser is alive
825
843
 
826
844
  // SIGCONT detection -- macOS sends SIGCONT on wake from sleep/suspend
827
845
  let lastSigcont = 0;
@@ -896,6 +914,20 @@ export function createReporter(config) {
896
914
  // Require sustained growth -- one-time spikes aren't leaks.
897
915
  // Must exceed threshold on 3 consecutive checks (~90s).
898
916
  nativeMemConsecutiveAbove++;
917
+
918
+ // Capture browser RSS NOW while it may still be alive.
919
+ // By report time the browser is often killed by memory pressure restart,
920
+ // making browserRssMb null. This preserves the last-seen value.
921
+ try {
922
+ if (getContext) {
923
+ const ctx = getContext();
924
+ if (ctx.resourceOpts?.browserPid) {
925
+ const snap = collectResourceSnapshot(ctx.resourceOpts);
926
+ if (snap.browserRssMb != null) lastSeenBrowserRssMb = snap.browserRssMb;
927
+ }
928
+ }
929
+ } catch { /* swallow */ }
930
+
899
931
  if (nativeMemConsecutiveAbove >= NATIVE_MEM_CONSECUTIVE_REQUIRED) {
900
932
  nativeMemAlertFired = true;
901
933
  let extra = {};
@@ -903,6 +935,18 @@ export function createReporter(config) {
903
935
  const resources = collectResourceSnapshot(extra.resourceOpts || {});
904
936
  delete extra.resourceOpts;
905
937
 
938
+ // Skip report if sessions=0 — memory pressure restart handles idle leaks.
939
+ // Only report when sessions are active (restart CAN'T fire) or restart failed.
940
+ const sessionCount = resources.browserContexts ?? 0;
941
+ if (sessionCount === 0 && resources.browserRssMb == null) {
942
+ // Browser already dead, restart mechanism handled it. Don't spam.
943
+ // But if growth is extreme (>600MB), report anyway — restart may have failed.
944
+ if (growth < 600) {
945
+ // Self-healing. Skip report.
946
+ return;
947
+ }
948
+ }
949
+
906
950
  fileReport('leak:native-memory', ['auto-report', 'memory-leak'], {
907
951
  message: `Native memory grew by ${growth}MB (baseline: ${nativeMemBaseline}MB, current: ${nativeMemMb}MB, high-water: ${nativeMemHighWater}MB)`,
908
952
  uptimeMinutes: Math.round(process.uptime() / 60),
@@ -915,6 +959,7 @@ export function createReporter(config) {
915
959
  rssMb: Math.round(mem.rss / 1048576),
916
960
  heapUsedMb: Math.round(mem.heapUsed / 1048576),
917
961
  externalMb: Math.round(mem.external / 1048576),
962
+ lastSeenBrowserRssMb,
918
963
  },
919
964
  context: extra,
920
965
  });
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "camofox-browser",
2
+ "id": "@skyfallsin/camofox-browser",
3
3
  "name": "Camofox Browser",
4
4
  "description": "Anti-detection browser automation for AI agents using Camoufox (Firefox-based)",
5
- "version": "1.9.0",
5
+ "version": "1.9.1",
6
6
  "envVars": {
7
7
  "CAMOFOX_API_KEY": {
8
8
  "description": "Secret key for the cookie-import endpoint. Cookie import is disabled when unset. Only set this if you need to import browser cookies and the server is local or access-controlled.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askjo/camofox-browser",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Headless browser automation server and OpenClaw plugin for AI agents - anti-detection, element refs, and session isolation",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -4494,11 +4494,13 @@ setInterval(() => {
4494
4494
  if (reaped > 0) log('warn', 'orphan page reaper closed leaked pages', { reaped });
4495
4495
  }, 60_000);
4496
4496
 
4497
- // Native memory pressure restart -- when all sessions are gone and Firefox's
4498
- // native memory has grown beyond threshold, kill the browser immediately instead
4499
- // of waiting for the idle timer. Firefox/Camoufox doesn't fully reclaim native
4500
- // memory after context.close() due to jemalloc fragmentation, JIT caches, and
4501
- // NSS/TLS session caches. See #1032.
4497
+ // Native memory pressure restart -- when all sessions are gone and the Node
4498
+ // process's native memory (RSS minus V8 heap) has grown beyond threshold, kill
4499
+ // the browser process immediately instead of waiting for the idle timer.
4500
+ // Note: This measures Node/Playwright internal state (CDP buffers, glibc arenas),
4501
+ // NOT Firefox's own memory (which is a separate child process). Firefox jemalloc
4502
+ // fragmentation is tracked separately via browser RSS in /proc/<pid>/status.
4503
+ // The restart reclaims Playwright state; Firefox's process dies with it.
4502
4504
  setInterval(() => {
4503
4505
  if (sessions.size > 0 || !browser) return;
4504
4506
  const mem = process.memoryUsage();