How Camofox-Browser Tab Recycling Manages Per-Session Limits

When a session reaches its tab limit, camofox-browser automatically recycles the least-used tab by closing the page, releasing locks, and updating metrics to accommodate new requests instead of rejecting them.

Camofox-browser enforces configurable per-session and global tab limits to prevent resource exhaustion. The camofox-browser tab recycling mechanism activates automatically when these thresholds are reached, ensuring continuous operation by reclaiming the oldest resources first. This approach maintains system stability without rejecting valid user requests during high-load scenarios.

Configuring Tab Limits in lib/config.js

The recycling thresholds are defined through environment variables and exposed as constants in server.js.

Environment Variable Defaults

In lib/config.js, the system reads MAX_TABS_PER_SESSION and MAX_TABS_GLOBAL from the environment:

// lib/config.js
maxTabsPerSession: parseInt(process.env.MAX_TABS_PER_SESSION) || 10,
maxTabsGlobal:      parseInt(process.env.MAX_TABS_GLOBAL)    || 50,

These values are then imported in server.js:

// server.js
const MAX_TABS_PER_SESSION = CONFIG.maxTabsPerSession; // → 10 by default
const MAX_TABS_GLOBAL      = CONFIG.maxTabsGlobal;    // → 50 by default

Trigger Points for Tab Recycling

Two primary request handlers invoke the recycling logic when limits are exceeded.

Tab Creation (POST /tabs)

When creating a new tab via POST /tabs, the handler checks limits before allocation. If totalTabs >= MAX_TABS_PER_SESSION or the global count exceeds MAX_TABS_GLOBAL, the system calls recycleOldestTab (implemented in server.js lines 887-894).

Navigation Requests (POST /tabs/:tabId/navigate)

For navigation to non-existent tabs, the handler at server.js lines 42-48 checks if the session is already at its limit. If so, it recycles the oldest tab before creating the new navigation context.

Both handlers use the same invocation pattern:

const recycled = await recycleOldestTab(session, req.reqId);

If recycling fails, the request aborts with a 429 or 500 error.

The recycleOldestTab Selection Algorithm

The recycleOldestTab function in server.js (lines 874-899) implements a least-recently-used strategy based on toolCalls counters.

Selection Criteria

The function iterates through session.tabGroups to find the tab with the fewest toolCalls:

async function recycleOldestTab(session, reqId) {
  let oldestTab = null;
  let oldestGroup = null;
  let oldestGroupKey = null;
  let oldestTabId = null;

  // Scan every group (sessionKey) and every tab inside it
  for (const [gKey, group] of session.tabGroups) {
    for (const [tid, ts] of group) {
      // `toolCalls` counts how many times the tab has been used
      if (!oldestTab || ts.toolCalls < oldestTab.toolCalls) {
        oldestTab = ts;
        oldestGroup = group;
        oldestGroupKey = gKey;
        oldestTabId = tid;
      }
    }
  }
  // ... cleanup logic
}

Recycling Steps

Once identified, the function performs four critical operations:

  1. Close the page using safePageClose(oldestTab.page)
  2. Remove the tab from its group via oldestGroup.delete(oldestTabId)
  3. Clean up locks by draining any pending locks in tabLocks
  4. Update metrics by incrementing tabsRecycledTotal and logging the event

The function returns the reclaimed tabId and group key, allowing the caller to proceed with fresh resource allocation.

Monitoring Recycling Events

Recycling activity is tracked in lib/metrics.js (lines 78-81) using a Prometheus counter:

// lib/metrics.js
tabsRecycledTotal: new client.Counter({
  name: 'camofox_tabs_recycled_total',
  help: 'Tabs recycled when tab limit reached',
}),

Operators can monitor how often the server discards tabs to stay within limits by scraping the metrics endpoint.

Test Coverage

The unit test suite in tests/unit/tabRecycling.test.js validates the recycling behavior:

  • Verifies that creating tabs beyond the limit triggers recycling of the earliest tab
  • Confirms that the tab with the fewest toolCalls is selected when multiple tabs exist
  • Tests automatic recycling during navigation to non-existent tabs

These tests ensure the recycling logic functions correctly under various load conditions.

Practical Code Examples

Example 1: Exceeding the Per-Session Limit

The following client code demonstrates recycling when creating more than the default five tabs (assuming a reduced limit for demonstration):

const client = createClient('http://localhost:9377');

// Fill the session to its limit
const tabIds = [];
for (let i = 0; i < 5; i++) {
  const { tabId } = await client.createTab(`https://example.com/page${i}`);
  tabIds.push(tabId);
}

// The 6th tab triggers recycling of the oldest tab (tabIds[0])
const { tabId: newId } = await client.createTab('https://example.com/extra');
console.log('New tab created:', newId);

// The recycled tab is no longer reachable
try {
  await client.getSnapshot(tabIds[0]);
} catch (e) {
  console.log('Oldest tab was recycled (404)', e.status); // → 404
}

Example 2: Automatic Recycling During Navigation

When navigating to a non-existent tab while at the session limit, the server automatically recycles:

// Assume the session is already at its limit (5 tabs)
const fakeId = 'does-not-exist';

// Navigate – server will recycle the least-used tab and create a fresh one
const result = await client.navigate(fakeId, 'https://example.com/new-page');
console.log('Navigate succeeded, new tab URL:', result.url);

Example 3: Scraping Recycling Metrics

With PROMETHEUS_ENABLED=1, monitor recycling frequency:

curl http://localhost:9377/metrics | grep camofox_tabs_recycled_total

# Example output:

# camofox_tabs_recycled_total 3

Summary

  • Camofox-browser tab recycling prevents request rejection by reclaiming the least-used tab when MAX_TABS_PER_SESSION (default 10) or MAX_TABS_GLOBAL (default 50) is reached.
  • The recycleOldestTab function in server.js selects targets based on the lowest toolCalls count, ensuring the most active tabs remain available.
  • Recycling triggers during both tab creation (POST /tabs) and navigation to missing tabs (POST /tabs/:tabId/navigate).
  • Each recycling event closes the page, removes the tab from session.tabGroups, drains associated locks, and increments the camofox_tabs_recycled_total Prometheus metric.
  • The behavior is verified by the test suite in tests/unit/tabRecycling.test.js.

Frequently Asked Questions

How does camofox-browser determine which tab to recycle?

The system selects the tab with the fewest toolCalls within the session's tabGroups map. This metric tracks how many times the tab has been used for navigation, snapshots, or other operations, ensuring the least-active tab is reclaimed first.

What happens if tab recycling fails?

If recycleOldestTab returns null (indicating no tab could be identified or reclaimed), the requesting handler aborts the operation. The client receives either a 429 Too Many Requests or 500 Internal Server Error response, depending on the failure context.

Can I disable tab recycling?

No, tab recycling is integral to the resource management strategy in camofox-browser. However, you can effectively prevent it by setting MAX_TABS_PER_SESSION and MAX_TABS_GLOBAL to sufficiently high values in lib/config.js via environment variables, though this risks memory exhaustion under heavy load.

Where are the recycling limits configured?

The limits are defined in lib/config.js using the MAX_TABS_PER_SESSION and MAX_TABS_GLOBAL environment variables, then consumed in server.js. The default values are 10 tabs per session and 50 globally, but operators can adjust these based on available system resources.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →