# How Camofox-Browser Tab Recycling Manages Per-Session Limits

> Discover how Camofox-Browser tab recycling automatically closes inactive tabs when session limits are met, freeing up resources efficiently. Learn more.

- Repository: [jo/camofox-browser](https://github.com/jo-inc/camofox-browser)
- Tags: internals
- Published: 2026-04-15

---

**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`](https://github.com/jo-inc/camofox-browser/blob/main/server.js).

### Environment Variable Defaults

In [`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js), the system reads `MAX_TABS_PER_SESSION` and `MAX_TABS_GLOBAL` from the environment:

```javascript
// 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`](https://github.com/jo-inc/camofox-browser/blob/main/server.js):

```javascript
// 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`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) lines 887-894).

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

For navigation to non-existent tabs, the handler at [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/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:

```javascript
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`](https://github.com/jo-inc/camofox-browser/blob/main/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`:

```javascript
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`](https://github.com/jo-inc/camofox-browser/blob/main/lib/metrics.js) (lines 78-81) using a Prometheus counter:

```javascript
// 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`](https://github.com/jo-inc/camofox-browser/blob/main/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):

```javascript
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:

```javascript
// 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:

```bash
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`](https://github.com/jo-inc/camofox-browser/blob/main/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`](https://github.com/jo-inc/camofox-browser/blob/main/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`](https://github.com/jo-inc/camofox-browser/blob/main/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`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js) using the `MAX_TABS_PER_SESSION` and `MAX_TABS_GLOBAL` environment variables, then consumed in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js). The default values are 10 tabs per session and 50 globally, but operators can adjust these based on available system resources.